xjb: Fast Float to String Algorithm

May 8, 2026 · View on GitHub

xjb is a high-performance algorithm for converting floating-point numbers (float/double) to strings, designed for speed and correctness. It provides single-file implementations for both general use and JSON-specific formatting.

Background

In the field of floating-point number printing, Steele & White established several key principles in their seminal paper "How to Print Floating-Point Numbers Accurately":

  1. Information Preservation: The conversion must produce a decimal string that, when parsed back, yields the original floating-point number or the closest possible value.
  2. Shortest Representation: Use the minimal number of digits necessary to uniquely identify the floating-point number.
  3. Proper Rounding: When multiple representations are equally valid, choose the one that rounds correctly.

1. Key Features

  • High Performance: Optimized algorithm with low branch prediction failure rate and SIMD acceleration
  • Correctness: Satisfies RFC8259 and ECMAScript standards for JSON number formatting
  • Small Footprint: Compact lookup tables for both float and double conversions
  • Versatility: Support for both float (IEEE754-binary32) and double (IEEE754-binary64) types
  • Modern Design: Low instruction dependency and high IPC (Instructions Per Cycle)

2. Implementation Files

2.1 Core Implementations

  • General float/double to string: src/ftoa.cpp
  • JSON-specific formatting: src/ftoa_json.cpp (satisfies RFC8259 and ECMAScript standards)

2.2 Algorithm Variants

Float to Decimal Conversion Algorithms

  • xjb32: For float (IEEE754-binary32) - bench/xjb/float_to_decimal/xjb32_i.cpp
  • xjb64: For double (IEEE754-binary64) - bench/xjb/float_to_decimal/xjb64_i.cpp

Float to String Conversion Algorithms

  • Full lookup table: bench/xjb/float_to_string/ftoa.cpp
  • Compressed lookup table: bench/xjb/float_to_string/ftoa_comp.cpp

2.3 Lookup Table Sizes

TypeFull Table (ftoa.cpp)Compressed Table (ftoa_comp.cpp)
float1352 bytes64 bytes
double15336 bytes368 bytes

3. Usage Examples

Here are some examples of how the double to string algorithm handles various inputs:

InputOutput
123.45"123.45"
1000"1000.0"
123"123.0"
123000"123000.0"
1.2e23"1.2e23"
1e100"1e100"
0.0123"0.0123"
0.001"0.001"
0.000123"0.000123"
1.2e-08"1.2e-08"
0"0.0"
NaN"nan"
Inf"inf"

4. Algorithm Characteristics

xjb is based on the schubfach algorithm and inspired by other state-of-the-art algorithms like yy, dragonbox, and grisu. Key characteristics include:

  1. Dual Precision Support: Handles both float and double types
  2. Optimized Process: Streamlined algorithm flow for maximum performance
  3. Low Branch Misprediction: Designed to minimize pipeline stalls
  4. SIMD Acceleration: Leverages SIMD instruction sets for faster processing
  5. High Instruction Level Parallelism: Low dependency between instructions
  6. Efficient Instruction Count: Minimizes the number of operations

5. Algorithm Principle

The following algorithmic process is derived from a variant of the Schubfach algorithm.


algorithm : The xjb Algorithm for Float-to-Decimal Conversion

Input : c,qc,q ; Floating-point components cc (significand) and qq (exponent)
Output : d,kd,k ; Decimal representation d10kd \cdot 10^k satisfying the SW principle

  1. c2qvc \cdot 2^q \gets v
  2. if v is regular\text{if } v \text{ is regular}
  3. kqlog10(2)\quad k \gets \lfloor q \cdot \log_{10}(2) \rfloor
  4. else\text{else}
  5. kqlog10(2)log10(4/3)\quad k \gets \lfloor q \cdot \log_{10}(2) - \log_{10}(4/3) \rfloor
  6. end if\text{end if}
  7. mv10k1m \gets \lfloor v \cdot 10^{-k-1} \rfloor
  8. nv10k1mn \gets v \cdot 10^{-k-1} - m
  9. ten10mten \gets 10m
  10. δ10n10n\delta \gets 10n - \lfloor 10n \rfloor // fractional part of $10n$
  11. if δ=0.5\text{if } \delta = 0.5
  12. if 10nmod2=0\quad \text{if } \lfloor 10n \rfloor \bmod 2 = 0
  13. one10n\quad\quad one \gets \lfloor 10n \rfloor // round to even
  14. else\quad \text{else}
  15. one10n+1\quad\quad one \gets \lfloor 10n \rfloor + 1
  16. end if\quad \text{end if}
  17. elif δ<0.5\text{elif } \delta < 0.5
  18. one10n\quad one \gets \lfloor 10n \rfloor // round to nearest
  19. else\text{else}
  20. one10n+1\quad one \gets \lfloor 10n \rfloor + 1 // round to nearest
  21. end if\text{end if}
  22. if v is irregular\text{if } v \text{ is irregular}
  23. if δ>2q210k\quad \text{if } \delta > 2^{q-2} \cdot 10^{-k}
  24. one10n+1\quad\quad one \gets \lfloor 10n \rfloor + 1
  25. end if\quad \text{end if}
  26. if 2q210k1n\quad \text{if } 2^{q-2} \cdot 10^{-k-1} \geq n
  27. one0\quad\quad one \gets 0
  28. end if\quad \text{end if}
  29. else\text{else}
  30. if 2q110k1>n or (2q110k1=n and cmod2=0)\quad \text{if } 2^{q-1} \cdot 10^{-k-1} > n \text{ or } \left( 2^{q-1} \cdot 10^{-k-1} = n \text{ and } c \bmod 2 = 0 \right)
  31. one0\quad\quad one \gets 0 // minimum length
  32. end if\quad \text{end if}
  33. end if\text{end if}
  34. if 2q110k1>1n or (2q110k1=1n and cmod2=0)\text{if } 2^{q-1} \cdot 10^{-k-1} > 1-n \text{ or } \left( 2^{q-1} \cdot 10^{-k-1} = 1-n \text{ and } c \bmod 2 = 0 \right)
  35. one10\quad one \gets 10 // minimum length
  36. end if\text{end if}
  37. dten+oned \gets ten + one // information preservation
  38. return d,k\text{return } d, k

Algorithm Principle Introduction Document : xjb
Note : There is a mistake in Algorithm 1 on page 10 of the article. Please refer to the above implementation.

Our algorithm produced the same output results as the Schubfach algorithm. To verify the correctness of the algorithm, run the following command:

cd bench
make check

6. Benchmark Results

Benchmark tests are located in the bench directory. Build the benchmark program with make and run bench/main.cpp to test performance.

Latest benchmark results (as of 2026.4.13 v1.5.0) on various CPUs:

on Apple M5:
ramdom float on Apple M5
compiler: apple clang 21.0.0
ramdom double on Apple M5
compiler: apple clang 21.0.0
fixed-length double on Apple M5
compiler: apple clang 21.0.0
on Apple M1:
ramdom float on Apple M1
compiler: apple clang 21.0.0
ramdom double on Apple M1
compiler: apple clang 21.0.0
fixed-length double on Apple M1
compiler: apple clang 21.0.0
on AMD R7-7840H:
ramdom float on AMD R7-7840H
compiler: icpx 2025.0.4
ramdom double on AMD R7-7840H
compiler: icpx 2025.0.4
fixed-length double on AMD R7-7840H
compiler: icpx 2025.0.4

For detailed benchmark results and charts, please refer to the bench directory.

AlgorithmDescription
schubfachRaffaello Giulietti's algorithm. Original source: https://github.com/c4f7fcce9cb06515/Schubfach
schubfach_xjbImproved version of schubfach with identical output results
ryuUlf Adams's Ryū algorithm
dragonboxJunekey Jeon's C++ implementation
fmtVictor Zverovich's formatting library (version: 12.1.0)
yy_doubleyy's algorithm from https://github.com/ibireme/c_numconv_benchmark/blob/master/vendor/yy_double/yy_double.c
yy_jsonyy's algorithm for JSON
tejuAlgorithm with upcoming academic paper for correctness proof
xjbXiangJunBo's algorithm (this project)
schubfach_vitautVitaut's implementation of schubfach
zmijVitaut's algorithm
jnumJing Leng's algorithm
uscaleRuss Cox's algorithm ,document

8. Acknowledgements

Special thanks to the following contributors and inspirations:

  • Yaoyuan Guo (@ibireme) - Author of yyjson and yy_double algorithms, provided benchmark data and test code. This project was inspired by the yy algorithm.

  • Dougall Johnson (@dougallj) - Authored the NEON implementation used in xjb.

  • Daniel Lemire (@lemire) - Authored the AVX512IFMA implementation for converting integers to decimal strings, used in xjb.

  • Raffaello Giulietti (@rgiulietti) - Author of the Schubfach algorithm, which forms the foundation of xjb.

  • Victor Zverovich (@vitaut) - Author of the zmij algorithm and fmt library. Parts of this implementation are derived from zmij.

9. Projects Using xjb

  • ssrJSON - A SIMD-boosted high-performance Python JSON parsing library
  • jsoniter-scala - Scala macros for compile-time generation of fast JSON codecs

10. Roadmap

  • Big-endian support
  • f16 support
  • f32 support
  • f64 support
  • f80 support
  • f128 support
  • f256 support

11. Contributing

Contributions are welcome! Please feel free to submit issues or pull requests to help improve this project.

12. License

MIT License