mirror of
https://github.com/fastfloat/fast_float.git
synced 2026-09-14 14:42:26 +08:00
Merge 0097a06d42277a4e248fdeb4f42e3f01b28900c8 into a8a02f77480d10c5dc90d39f7b890bc1dff9c1b9
This commit is contained in:
commit
ac012974eb
@ -3,6 +3,7 @@
|
||||
#endif
|
||||
#include "counters/event_counter.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include "fast_float/fast_float.h"
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
@ -226,7 +227,202 @@ void fileload(std::string filename) {
|
||||
process(lines, volume);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr size_t truncated_fraction_integer_digits =
|
||||
fast_float::binary_format<double>::max_digits() + 1;
|
||||
constexpr size_t truncated_fraction_max_digits =
|
||||
fast_float::binary_format<double>::max_digits();
|
||||
constexpr size_t truncated_fraction_digits = 4 * 1024 * 1024;
|
||||
constexpr size_t truncated_fraction_batches = 9;
|
||||
constexpr size_t truncated_fraction_zero_batches = 17;
|
||||
constexpr size_t truncated_fraction_direct_iterations = 2048;
|
||||
constexpr size_t truncated_fraction_direct_zero_iterations = 8192;
|
||||
constexpr size_t truncated_fraction_from_chars_iterations = 256;
|
||||
constexpr size_t truncated_fraction_from_chars_zero_iterations = 512;
|
||||
constexpr double truncated_fraction_expected_value = 0x0.607b00a417628p-1022;
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define FASTFLOAT_BENCH_NOINLINE __declspec(noinline)
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
#define FASTFLOAT_BENCH_NOINLINE __attribute__((noinline))
|
||||
#else
|
||||
#define FASTFLOAT_BENCH_NOINLINE
|
||||
#endif
|
||||
|
||||
struct truncated_fraction_input {
|
||||
std::string text{};
|
||||
fast_float::parsed_number_string parsed{};
|
||||
};
|
||||
|
||||
[[noreturn]] void truncated_fraction_fail(char const *message) {
|
||||
std::fputs(message, stderr);
|
||||
std::fputc('\n', stderr);
|
||||
std::exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
void truncated_fraction_usage() {
|
||||
std::fputs("usage: realbenchmark --truncated-fraction "
|
||||
"{parse_mantissa|from_chars} {nonzero|zero}\n",
|
||||
stderr);
|
||||
}
|
||||
|
||||
std::string make_truncated_fraction_input(char final_integer_digit) {
|
||||
std::string result = "8385788696668661046";
|
||||
result.append(truncated_fraction_integer_digits - result.size() - 1, '0');
|
||||
result.push_back(final_integer_digit);
|
||||
result.push_back('.');
|
||||
result.append(truncated_fraction_digits, '0');
|
||||
result += "e-1078";
|
||||
return result;
|
||||
}
|
||||
|
||||
void initialize_truncated_fraction_input(truncated_fraction_input &input,
|
||||
char final_integer_digit) {
|
||||
input.text = make_truncated_fraction_input(final_integer_digit);
|
||||
fast_float::parse_options options;
|
||||
input.parsed = fast_float::parse_number_string<false>(
|
||||
input.text.data(), input.text.data() + input.text.size(), options, true);
|
||||
if (!input.parsed.valid || !input.parsed.too_many_digits ||
|
||||
input.parsed.integer.len() != truncated_fraction_integer_digits ||
|
||||
input.parsed.fraction.len() != truncated_fraction_digits) {
|
||||
truncated_fraction_fail("unexpected parsed input");
|
||||
}
|
||||
|
||||
double parsed_value = 0;
|
||||
auto const parsed = fast_float::from_chars(
|
||||
input.text.data(), input.text.data() + input.text.size(), parsed_value);
|
||||
if (parsed.ec != std::errc() ||
|
||||
parsed.ptr != input.text.data() + input.text.size() ||
|
||||
parsed_value != truncated_fraction_expected_value) {
|
||||
truncated_fraction_fail("unexpected conversion result");
|
||||
}
|
||||
}
|
||||
|
||||
FASTFLOAT_BENCH_NOINLINE uint64_t
|
||||
parse_mantissa_once(truncated_fraction_input const &input) {
|
||||
fast_float::parsed_number_string number = input.parsed;
|
||||
fast_float::bigint result;
|
||||
size_t digits = 0;
|
||||
fast_float::parse_mantissa(result, number, truncated_fraction_max_digits,
|
||||
digits);
|
||||
bool truncated = false;
|
||||
return result.hi64(truncated) ^ (uint64_t(digits) << 1) ^ uint64_t(truncated);
|
||||
}
|
||||
|
||||
FASTFLOAT_BENCH_NOINLINE uint64_t
|
||||
from_chars_once(truncated_fraction_input const &input) {
|
||||
double result = 0;
|
||||
auto const parsed = fast_float::from_chars(
|
||||
input.text.data(), input.text.data() + input.text.size(), result);
|
||||
if (parsed.ec != std::errc() ||
|
||||
parsed.ptr != input.text.data() + input.text.size()) {
|
||||
truncated_fraction_fail("unexpected conversion result");
|
||||
}
|
||||
|
||||
uint64_t bits = 0;
|
||||
static_assert(sizeof(bits) == sizeof(result), "unexpected double size");
|
||||
std::memcpy(&bits, &result, sizeof(bits));
|
||||
return bits;
|
||||
}
|
||||
|
||||
inline void do_not_optimize(uint64_t value) {
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
asm volatile("" : : "r"(value) : "memory");
|
||||
#else
|
||||
volatile uint64_t sink = value;
|
||||
(void)sink;
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename Function>
|
||||
double measure_truncated_fraction(
|
||||
std::array<truncated_fraction_input, 2> const &inputs, size_t iterations,
|
||||
size_t batches, Function function) {
|
||||
std::array<double, truncated_fraction_zero_batches> samples{};
|
||||
for (size_t batch = 0; batch < batches; ++batch) {
|
||||
uint64_t sink = 0;
|
||||
auto const start = std::chrono::steady_clock::now();
|
||||
for (size_t index = 0; index < iterations; ++index) {
|
||||
sink += function(inputs[index & 1]);
|
||||
}
|
||||
auto const finish = std::chrono::steady_clock::now();
|
||||
do_not_optimize(sink);
|
||||
samples[batch] =
|
||||
double(
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(finish - start)
|
||||
.count()) /
|
||||
double(iterations);
|
||||
}
|
||||
std::sort(samples.begin(), samples.begin() + batches);
|
||||
return samples[batches / 2];
|
||||
}
|
||||
|
||||
void print_truncated_fraction_measurement(double nanoseconds_per_operation) {
|
||||
std::printf("{\"metric\":\"ns/op\",\"value\":%.17g}\n",
|
||||
nanoseconds_per_operation);
|
||||
}
|
||||
|
||||
int run_truncated_fraction_benchmark(char const *operation,
|
||||
char const *integer_suffix) {
|
||||
bool direct = false;
|
||||
if (std::strcmp(operation, "parse_mantissa") == 0) {
|
||||
direct = true;
|
||||
} else if (std::strcmp(operation, "from_chars") != 0) {
|
||||
truncated_fraction_usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
bool zero_suffix = false;
|
||||
if (std::strcmp(integer_suffix, "zero") == 0) {
|
||||
zero_suffix = true;
|
||||
} else if (std::strcmp(integer_suffix, "nonzero") != 0) {
|
||||
truncated_fraction_usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
std::array<truncated_fraction_input, 2> inputs{};
|
||||
initialize_truncated_fraction_input(inputs[0], zero_suffix ? '0' : '1');
|
||||
initialize_truncated_fraction_input(inputs[1], zero_suffix ? '0' : '2');
|
||||
|
||||
// The zero-suffix route retains the long fractional scan. Use larger batches
|
||||
// and more independent samples there so that its unchanged path has a precise
|
||||
// check.
|
||||
size_t const iterations =
|
||||
zero_suffix ? (direct ? truncated_fraction_direct_zero_iterations
|
||||
: truncated_fraction_from_chars_zero_iterations)
|
||||
: (direct ? truncated_fraction_direct_iterations
|
||||
: truncated_fraction_from_chars_iterations);
|
||||
size_t const batches = zero_suffix ? truncated_fraction_zero_batches
|
||||
: truncated_fraction_batches;
|
||||
|
||||
if (direct) {
|
||||
print_truncated_fraction_measurement(measure_truncated_fraction(
|
||||
inputs, iterations, batches, [](truncated_fraction_input const &input) {
|
||||
return parse_mantissa_once(input);
|
||||
}));
|
||||
} else {
|
||||
print_truncated_fraction_measurement(measure_truncated_fraction(
|
||||
inputs, iterations, batches, [](truncated_fraction_input const &input) {
|
||||
return from_chars_once(input);
|
||||
}));
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#undef FASTFLOAT_BENCH_NOINLINE
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
if (argc > 1 && std::strcmp(argv[1], "--truncated-fraction") == 0) {
|
||||
if (argc != 4) {
|
||||
truncated_fraction_usage();
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
return run_truncated_fraction_benchmark(argv[2], argv[3]);
|
||||
}
|
||||
|
||||
if (collector.has_events()) {
|
||||
std::cout << "# Using hardware counters" << std::endl;
|
||||
} else {
|
||||
|
||||
@ -285,8 +285,9 @@ parse_mantissa(bigint &result, parsed_number_string_t<UC> &num,
|
||||
// add the temporary value, then check if we've truncated any digits
|
||||
add_native(result, limb(powers_of_ten_uint64[counter]), value);
|
||||
bool truncated = is_truncated(p, pend);
|
||||
if (num.fraction.ptr != nullptr) {
|
||||
truncated |= is_truncated(num.fraction);
|
||||
// A nonzero integer suffix already determines the rounding direction.
|
||||
if (!truncated && num.fraction.ptr != nullptr) {
|
||||
truncated = is_truncated(num.fraction);
|
||||
}
|
||||
if (truncated) {
|
||||
round_up_bigint(result, digits);
|
||||
|
||||
@ -1153,6 +1153,99 @@ TEST_CASE("double.inf") {
|
||||
std::errc::result_out_of_range);
|
||||
}
|
||||
|
||||
TEST_CASE("truncated integer mantissa") {
|
||||
constexpr size_t max_digits = fast_float::binary_format<double>::max_digits();
|
||||
constexpr size_t integer_digits = max_digits + 1;
|
||||
constexpr size_t fraction_length = 1024;
|
||||
|
||||
auto make_mantissa = [=](char final_integer_digit,
|
||||
char final_fraction_digit) {
|
||||
std::string input = "1234567890123456789";
|
||||
input.append(integer_digits - input.size() - 1, '0');
|
||||
input.push_back(final_integer_digit);
|
||||
input.push_back('.');
|
||||
input.append(fraction_length - 1, '0');
|
||||
input.push_back(final_fraction_digit);
|
||||
return input;
|
||||
};
|
||||
|
||||
auto parse_mantissa = [=](std::string const &input,
|
||||
fast_float::bigint &result) -> size_t {
|
||||
fast_float::parse_options options;
|
||||
auto number = fast_float::parse_number_string<false>(
|
||||
input.data(), input.data() + input.size(), options, true);
|
||||
CHECK(number.valid);
|
||||
CHECK(number.integer.len() == integer_digits);
|
||||
CHECK(number.fraction.len() == fraction_length);
|
||||
|
||||
size_t digits = 0;
|
||||
fast_float::parse_mantissa(result, number, max_digits, digits);
|
||||
return digits;
|
||||
};
|
||||
|
||||
auto const nonzero_integer_zero_fraction = make_mantissa('1', '0');
|
||||
auto const nonzero_integer_nonzero_fraction = make_mantissa('1', '1');
|
||||
fast_float::bigint nonzero_integer_zero_result;
|
||||
fast_float::bigint nonzero_integer_nonzero_result;
|
||||
CHECK(parse_mantissa(nonzero_integer_zero_fraction,
|
||||
nonzero_integer_zero_result) == max_digits + 1);
|
||||
CHECK(parse_mantissa(nonzero_integer_nonzero_fraction,
|
||||
nonzero_integer_nonzero_result) == max_digits + 1);
|
||||
CHECK(nonzero_integer_zero_result.compare(nonzero_integer_nonzero_result) ==
|
||||
0);
|
||||
|
||||
auto const zero_integer_zero_fraction = make_mantissa('0', '0');
|
||||
auto const zero_integer_nonzero_fraction = make_mantissa('0', '1');
|
||||
fast_float::bigint zero_integer_zero_result;
|
||||
fast_float::bigint zero_integer_nonzero_result;
|
||||
CHECK(parse_mantissa(zero_integer_zero_fraction, zero_integer_zero_result) ==
|
||||
max_digits);
|
||||
CHECK(parse_mantissa(zero_integer_nonzero_fraction,
|
||||
zero_integer_nonzero_result) == max_digits + 1);
|
||||
CHECK(zero_integer_zero_result.compare(zero_integer_nonzero_result) < 0);
|
||||
|
||||
auto make_exact_conversion_input = [=](char final_fraction_digit) {
|
||||
std::string input = "8385788696668661046";
|
||||
input.append(integer_digits - input.size() - 1, '0');
|
||||
input.push_back('1');
|
||||
input.push_back('.');
|
||||
input.append(fraction_length - 1, '0');
|
||||
input.push_back(final_fraction_digit);
|
||||
input += "e-1078";
|
||||
return input;
|
||||
};
|
||||
|
||||
auto const exact_input = make_exact_conversion_input('0');
|
||||
fast_float::parse_options options;
|
||||
auto const number = fast_float::parse_number_string<false>(
|
||||
exact_input.data(), exact_input.data() + exact_input.size(), options,
|
||||
true);
|
||||
REQUIRE(number.too_many_digits);
|
||||
REQUIRE(number.integer.len() == integer_digits);
|
||||
REQUIRE(number.fraction.len() == fraction_length);
|
||||
auto const approximate =
|
||||
fast_float::compute_float<fast_float::binary_format<double>>(
|
||||
number.exponent, number.mantissa);
|
||||
auto const next =
|
||||
fast_float::compute_float<fast_float::binary_format<double>>(
|
||||
number.exponent, number.mantissa + 1);
|
||||
REQUIRE(approximate != next);
|
||||
REQUIRE(fast_float::compute_error<fast_float::binary_format<double>>(
|
||||
number.exponent, number.mantissa)
|
||||
.power2 < 0);
|
||||
|
||||
for (char final_fraction_digit = '0'; final_fraction_digit <= '1';
|
||||
++final_fraction_digit) {
|
||||
auto const input = make_exact_conversion_input(final_fraction_digit);
|
||||
double value = 0;
|
||||
auto const result = fast_float::from_chars(
|
||||
input.data(), input.data() + input.size(), value);
|
||||
CHECK(result.ec == std::errc());
|
||||
CHECK(result.ptr == input.data() + input.size());
|
||||
CHECK(value == 0x0.607b00a417628p-1022);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("double.general") {
|
||||
verify("0.95000000000000000000", 0.95);
|
||||
verify("22250738585072012e-324",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user