Merge 0097a06d42277a4e248fdeb4f42e3f01b28900c8 into a8a02f77480d10c5dc90d39f7b890bc1dff9c1b9

This commit is contained in:
Perfloop Agent 2026-08-20 19:30:26 +00:00 committed by GitHub
commit ac012974eb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 292 additions and 2 deletions

View File

@ -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 {

View File

@ -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);

View File

@ -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",