From 161dedde6c0ddb8f7a3a9035006a821005bb2980 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Schl=C3=BCter?= Date: Wed, 20 May 2026 17:03:17 +0900 Subject: [PATCH 1/2] Add a fixed benchmark that also covers negative exponents --- test/benchmark.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/benchmark.cc b/test/benchmark.cc index 073b78d..a00b4a5 100644 --- a/test/benchmark.cc +++ b/test/benchmark.cc @@ -161,6 +161,49 @@ static void run_to_chars_canada(benchmark::State& state, benchmark::Counter::kInvert); } +// Doubles drawn uniformly from zmij's fixed-notation decimal-exponent range, +// dec_exp in [-4, 15]: each decade is equally weighted, so the negative- +// exponent side (which canada.json doesn't cover) gets ~25% of samples. Sized +// to roughly match canada_numbers for comparable per-iteration cost. +static const std::vector& get_fixed_range_numbers() { + static const std::vector v = [] { + constexpr size_t count = + sizeof(canada_numbers) / sizeof(canada_numbers[0]); + std::mt19937_64 rng(0); + std::uniform_int_distribution exp_dist(-4, 15); + std::uniform_real_distribution sig_dist(1.0, 10.0); + std::vector out; + out.reserve(count); + for (size_t i = 0; i < count; ++i) { + double sig = sig_dist(rng); + int e = exp_dist(rng); + out.push_back(sig * std::pow(10.0, e)); + } + return out; + }(); + return v; +} + +static void run_to_chars_fixed_range( + benchmark::State& state, auto (*to_chars)(double, char*)->char*) { + const auto& nums = get_fixed_range_numbers(); + char buffer[256]; + for (auto _ : state) { + for (double x : nums) { + char* end = to_chars(x, buffer); + benchmark::DoNotOptimize(end); + benchmark::ClobberMemory(); + } + } + state.counters["Throughput"] = benchmark::Counter( + static_cast(nums.size()), + benchmark::Counter::kIsIterationInvariantRate); + state.counters["Time/double"] = benchmark::Counter( + static_cast(nums.size()), + benchmark::Counter::kIsIterationInvariantRate | + benchmark::Counter::kInvert); +} + // Formats a counter value with 2 fractional digits, applying SI auto-scaling // so the mantissa always sits in [1, 1000) (or in [0.01, 1) for tiny values). static auto format_counter(double n) -> std::string { @@ -234,6 +277,9 @@ static void register_all(bool per_digit) { auto canada_name = m.name + "/canada"; benchmark::RegisterBenchmark(canada_name.c_str(), run_to_chars_canada, m.to_chars); + auto fr_name = m.name + "/fixed_range"; + benchmark::RegisterBenchmark(fr_name.c_str(), run_to_chars_fixed_range, + m.to_chars); } } } From 947aea48ddb0b3f5fbcdce32ee3bcb6b13076821 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tobias=20Schl=C3=BCter?= Date: Sun, 7 Jun 2026 22:40:52 +0900 Subject: [PATCH 2/2] Benchmark with random sign --- test/benchmark.cc | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/benchmark.cc b/test/benchmark.cc index a00b4a5..7375017 100644 --- a/test/benchmark.cc +++ b/test/benchmark.cc @@ -163,8 +163,9 @@ static void run_to_chars_canada(benchmark::State& state, // Doubles drawn uniformly from zmij's fixed-notation decimal-exponent range, // dec_exp in [-4, 15]: each decade is equally weighted, so the negative- -// exponent side (which canada.json doesn't cover) gets ~25% of samples. Sized -// to roughly match canada_numbers for comparable per-iteration cost. +// exponent side (which canada.json doesn't cover) gets ~25% of samples. Signs +// are randomized so the negative path is exercised too. Sized to roughly match +// canada_numbers for comparable per-iteration cost. static const std::vector& get_fixed_range_numbers() { static const std::vector v = [] { constexpr size_t count = @@ -172,12 +173,15 @@ static const std::vector& get_fixed_range_numbers() { std::mt19937_64 rng(0); std::uniform_int_distribution exp_dist(-4, 15); std::uniform_real_distribution sig_dist(1.0, 10.0); + std::bernoulli_distribution sign_dist(0.5); std::vector out; out.reserve(count); for (size_t i = 0; i < count; ++i) { double sig = sig_dist(rng); int e = exp_dist(rng); - out.push_back(sig * std::pow(10.0, e)); + double val = sig * std::pow(10.0, e); + if (sign_dist(rng)) val = -val; + out.push_back(val); } return out; }();