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 | 870.478 | 1.00x |
| sprintf | 734.033 | 1.19x |
| double-conversion | 82.903 | 10.50x |
| to_chars | 42.537 | 20.46x |
| ryu | 36.805 | 23.65x |
| schubfach | 24.653 | 35.31x |
| fmt | 22.201 | 39.21x |
| dragonbox | 20.544 | 42.37x |
| yy | 13.963 | 62.34x |
| xjb64 | 10.500 | 82.90x |
| zmij | 8.895 | 97.87x |
| null | 0.929 | 936.55x |
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.