C++ double-to-string conversion benchmark
30
stars
310
commits
C++
primary language
Aug 2, 2026
updated
This project is a rewrite of Milo Yip’s dtoa-benchmark with an updated set of algorithms reflecting the current state of the art and a simplified workflow.
This benchmark measures the performance of converting double-precision
IEEE-754 floating-point values (double) to ASCII strings. Each
implementation exposes a function with the signature:
char* dtoa(double value, char* buffer);
that writes a textual representation of value into buffer and returns a
pointer to one past the last written character. The resulting string must
round-trip: parsing it back through a correct strtod must yield exactly the
original double.
Note: dtoa is not a standard C or C++ function.
The benchmark runs in two phases:
Correctness verification. Every implementation is validated against a
set of edge cases and 100,000 random double values (excluding ±inf and
NaN) to confirm round-trip correctness.
Performance measurement. For each implementation the benchmark runs:
double values reduced to a fixed precision of 1–17 significant decimal
digits. These produce the time vs. digit count chart.Time (ns) in the results table; this is
the metric to use for an at-a-glance comparison.Iteration counts and statistical stabilization are handled by Google Benchmark.
cmake .
make run-benchmark
Results are written in Google Benchmark's JSON format to:
results/<cpu>_<os>_<compiler>_<commit>.json
and automatically converted to a self-contained HTML report with the same
base name. The JSON context block carries CPU/cache info, library
version, and commit_hash/machine/os/compiler keys for downstream
analysis.
The following results were measured on a MacBook Pro (Apple M1 Pro) using:
| Method | Time (ns) | Speedup |
|---|---|---|
| zmij | 6.45 | 115.440x |
| xjb64 | 6.99 | 106.465x |
| yy | 24.63 | 30.235x |
| dragonbox | 28.95 | 25.723x |
| fmt | 36.84 | 20.214x |
| uscale | 45.86 | 16.239x |
| ryu | 46.07 | 16.164x |
| to_chars | 51.35 | 14.503x |
| schubfach | 53.62 | 13.889x |
| double-conversion | 87.43 | 8.518x |
| sprintf | 744.72 | 1.000x |
| ostringstream | 885.30 | 0.841x |
Time per double (smaller is better):
ostringstream and sprintf omitted; they are an order of magnitude slower than the rest.
Time vs digit count (log scale):
null performs no conversion and measures loop + call overhead.sprintf and ostringstream do not generate shortest representations
(e.g. 0.1 → 0.10000000000000001).ryu, dragonbox, and schubfach always emit exponential notation
(e.g. 0.1 → 1E-1).Additional benchmark results are available in the results directory and
viewable online.
| Method | Description |
|---|---|
| asteria | rocket::ascii_numput::put_DD |
| double-conversion | EcmaScriptConverter::ToShortest which implements Grisu3 with bignum fallback |
| dragonbox | jkj::dragonbox::to_chars_n with the full cache table |
| fmt | fmt::format_to with compile-time format strings (uses Dragonbox) |
| null | no-op implementation; measures benchmark loop overhead |
| ostringstream | std::ostringstream with setprecision(17) |
| ryu | d2s_buffered |
| schubfach | C++ Schubfach implementation |
| sprintf | C sprintf("%.17g", value) |
| to_chars | std::to_chars |
| yy | yy_double_to_string from yyjson |
| zmij | zmij::write |
std::to_string is excluded because it does not guarantee round-trip
correctness (until C++26).
dtoa important?Floating-point formatting is ubiquitous in text output.
Standard facilities such as sprintf and std::stringstream are often slow.
This benchmark originated from performance work in
RapidJSON.
C++
54.7%
C
26.4%
HTML
16.7%
Python
1.9%
C++ double-to-string conversion benchmark
30
stars
310
commits
C++
primary language
Aug 2, 2026
updated
This project is a rewrite of Milo Yip’s dtoa-benchmark with an updated set of algorithms reflecting the current state of the art and a simplified workflow.
This benchmark measures the performance of converting double-precision
IEEE-754 floating-point values (double) to ASCII strings. Each
implementation exposes a function with the signature:
char* dtoa(double value, char* buffer);
that writes a textual representation of value into buffer and returns a
pointer to one past the last written character. The resulting string must
round-trip: parsing it back through a correct strtod must yield exactly the
original double.
Note: dtoa is not a standard C or C++ function.
The benchmark runs in two phases:
Correctness verification. Every implementation is validated against a
set of edge cases and 100,000 random double values (excluding ±inf and
NaN) to confirm round-trip correctness.
Performance measurement. For each implementation the benchmark runs:
double values reduced to a fixed precision of 1–17 significant decimal
digits. These produce the time vs. digit count chart.Time (ns) in the results table; this is
the metric to use for an at-a-glance comparison.Iteration counts and statistical stabilization are handled by Google Benchmark.
cmake .
make run-benchmark
Results are written in Google Benchmark's JSON format to:
results/<cpu>_<os>_<compiler>_<commit>.json
and automatically converted to a self-contained HTML report with the same
base name. The JSON context block carries CPU/cache info, library
version, and commit_hash/machine/os/compiler keys for downstream
analysis.
The following results were measured on a MacBook Pro (Apple M1 Pro) using:
| Method | Time (ns) | Speedup |
|---|---|---|
| zmij | 6.45 | 115.440x |
| xjb64 | 6.99 | 106.465x |
| yy | 24.63 | 30.235x |
| dragonbox | 28.95 | 25.723x |
| fmt | 36.84 | 20.214x |
| uscale | 45.86 | 16.239x |
| ryu | 46.07 | 16.164x |
| to_chars | 51.35 | 14.503x |
| schubfach | 53.62 | 13.889x |
| double-conversion | 87.43 | 8.518x |
| sprintf | 744.72 | 1.000x |
| ostringstream | 885.30 | 0.841x |
Time per double (smaller is better):
ostringstream and sprintf omitted; they are an order of magnitude slower than the rest.
Time vs digit count (log scale):
null performs no conversion and measures loop + call overhead.sprintf and ostringstream do not generate shortest representations
(e.g. 0.1 → 0.10000000000000001).ryu, dragonbox, and schubfach always emit exponential notation
(e.g. 0.1 → 1E-1).Additional benchmark results are available in the results directory and
viewable online.
| Method | Description |
|---|---|
| asteria | rocket::ascii_numput::put_DD |
| double-conversion | EcmaScriptConverter::ToShortest which implements Grisu3 with bignum fallback |
| dragonbox | jkj::dragonbox::to_chars_n with the full cache table |
| fmt | fmt::format_to with compile-time format strings (uses Dragonbox) |
| null | no-op implementation; measures benchmark loop overhead |
| ostringstream | std::ostringstream with setprecision(17) |
| ryu | d2s_buffered |
| schubfach | C++ Schubfach implementation |
| sprintf | C sprintf("%.17g", value) |
| to_chars | std::to_chars |
| yy | yy_double_to_string from yyjson |
| zmij | zmij::write |
std::to_string is excluded because it does not guarantee round-trip
correctness (until C++26).
dtoa important?Floating-point formatting is ubiquitous in text output.
Standard facilities such as sprintf and std::stringstream are often slow.
This benchmark originated from performance work in
RapidJSON.
C++
54.7%
C
26.4%
HTML
16.7%
Python
1.9%