This project is a complete rewrite of Milo Yip’s dtoa-benchmark, featuring an updated set of algorithms that reflect the current state of the art and a simplified workflow.
This benchmark evaluates the performance of converting double-precision
IEEE-754 floating-point values (double) to ASCII strings. The function
signature is:
void dtoa(double value, char* buffer);The resulting string must be round-trip convertible: it should parse back
to the original value exactly via a correct implementation of strtod.
Note: dtoa is not a standard C or C++ function.
The benchmark consists of two phases:
-
Correctness verification
All implementations are first validated to ensure round-trip correctness. -
Performance measurement
The benchmark case is:
- RandomDigit
- Generate 100,000 random
doublevalues (excluding±infandNaN). - Reduce precision to 1–17 decimal digits in the significand.
- Convert each value to an ASCII string.
- Generate 100,000 random
Each digit group is executed 10 times.
For each configuration, 10 trials are run and the minimum elapsed time is recorded. - RandomDigit
cmake .
make run-benchmarkResults are written in CSV format to:
results/<cpu>_<os>_<compiler>_<commit>.csv
They are also automatically converted to HTML with the same base name.
The following results were measured on a MacBook Pro (Apple M1 Pro) using:
- Compiler: Apple clang 17.0.0 (clang-1700.0.13.5)
- OS: macOS
| Function | Time (ns) | Speedup |
|---|---|---|
| ostringstream | 874.884 | 1.00× |
| sprintf | 743.801 | 1.18× |
| double-conversion | 83.519 | 10.48× |
| to_chars | 43.672 | 20.03× |
| ryu | 36.865 | 23.73× |
| schubfach | 24.879 | 35.16× |
| fmt | 22.338 | 39.17× |
| dragonbox | 20.641 | 42.39× |
| yy | 14.335 | 61.03× |
| xjb64 | 10.724 | 81.58× |
| zmij | 10.087 | 86.73× |
| null | 0.930 | 940.73× |
Conversion time (smaller is better):
ostringstream and sprintf are excluded due to their significantly slower
performance.
nullperforms no conversion and measures loop + call overhead.sprintfandostringstreamdo not generate shortest representations (e.g.0.1→0.10000000000000001).ryu,dragonbox, andschubfachalways emit exponential notation (e.g.0.1→1E-1).
Additional benchmark results are available in the
results
directory and viewable online using
Google Charts:
| Function | Description |
|---|---|
| asteria | rocket::ascii_numput::put_DD |
| double-conversion | EcmaScriptConverter::ToShortest which implements Grisu3 with bignum fallback |
| dragonbox | jkj::dragonbox::to_chars with full tables |
| fmt | fmt::format_to with compile-time format strings (uses Dragonbox). |
| null | no-op implementation |
| ostringstream | std::ostringstream with setprecision(17) |
| ryu | d2s_buffered |
| schubfach | C++ Schubfach implementation |
| sprintf | C sprintf("%.17g", value) |
| to_chars | std::to_chars |
| zmij | zmij::write. |
std::to_string is excluded because it does not guarantee round-trip
correctness (until C++26).
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.