Why does a computer sometimes store a value close to 0.3 but print exactly “0.3”? Converting binary floating-point numbers to decimal text is not a trivial formatting problem. A correct routine must print a decimal string that, when parsed again, returns the exact same binary floating-point value—and users usually expect that string to be the shortest such representation.
Ryu, introduced by Ulf Adams at PLDI 2018, showed that shortest round-trip formatting can be done quickly using fixed-size integer arithmetic. This Learning Hall article builds from IEEE 754 representation and rounding intervals through the core Ryu ideas, explains why powers of two and five dominate the conversion, and finishes with production testing, fixed/scientific formatting and engineering trade-offs.
Quick Read
- Binary floating-point values usually do not equal the decimal literals that created them.
- Shortest formatting asks for the shortest decimal string that parses back to exactly the same binary value.
- The correct answer is determined by the rounding interval around the source float.
- Ryu transforms that interval problem into fixed-width integer arithmetic.
- Binary powers become decimal scaling through powers of 5 and powers of 2.
- Precomputed or efficiently derived multiplication constants replace expensive arbitrary-precision operations in common IEEE formats.
- The algorithm finds decimal digits that lie inside the accepted interval and removes trailing digits while preserving round-trip safety.
- Correct handling of boundaries depends on the IEEE rounding rule, significand parity and exact divisibility conditions.
- Ryu has separate work for shortest conversion and Ryu Printf for fixed/scientific formatting.
- Production implementations should be validated exhaustively where possible and differential-tested against trusted parsers and formatters.
1. Beginner Level: Why 0.3 Is Not Usually 0.3
IEEE 754 binary floating point stores values in a form resembling:
value = sign × significand × 2^exponent
Most decimal fractions cannot be represented exactly as a finite binary fraction. The nearest binary32 value to decimal 0.3 is slightly different from the mathematical real number 0.3. Yet printing the full exact decimal expansion would be noisy and unnecessary.
The useful output is the shortest decimal that identifies the stored binary number unambiguously under the language’s parsing rules. For that float, “0.3” may be enough.
2. The Round-Trip Contract
Let f be the original floating-point value and S(f) the emitted string. A shortest round-trip formatter wants:
parse(S(f)) == f
and among decimal strings satisfying that property, it wants one with as few significant digits as possible, subject to the chosen formatting and tie-breaking rules.
This is a stronger and more precise goal than “print many enough digits.” Printing 17 digits for a double can often guarantee round trips, but shortest conversion asks which of those digits are actually necessary.
3. Every Float Owns a Decimal Interval
Imagine the two neighbouring representable floating-point values around f. The real-number midpoints between f and those neighbours define boundaries. Any decimal number inside the appropriate rounding interval will parse to f under round-to-nearest rules.
So the conversion problem becomes geometric:
find the shortest decimal value d such that
lower_boundary ≤ d ≤ upper_boundary
and parsing d rounds to f
Whether a boundary is inclusive can depend on ties-to-even behaviour and the parity of the significand. This is why a formatter cannot simply truncate a decimal expansion.
4. Decode IEEE 754 First
For a normal binary floating-point value, extract the sign, exponent field and fraction field. Restore the implicit leading significand bit where appropriate, and adjust the exponent so the value is represented as an integer significand multiplied by a power of two.
f = m2 × 2^e2
Subnormal numbers require a different exponent/significand setup because the hidden leading bit is absent. Zero, infinities and NaNs should normally be handled as special cases before the core integer path.
5. Work With Scaled Integers
Ryu forms integer quantities representing the central value and its lower and upper boundaries at a common scale. Expositions often use names such as mv, mp and mm for the scaled value and margins.
The key is that after careful scaling, interval comparisons can be performed exactly with integers. Floating-point arithmetic is no longer used to decide which decimal digits are safe.
6. Why Powers of Five Appear
Decimal scaling uses powers of 10, and:
10^q = 2^q × 5^q
The binary source already contains powers of two. Converting between binary and decimal therefore becomes a controlled problem of multiplying or dividing by powers of five while shifting powers of two. Ryu chooses q so the scaled interval lands near the desired decimal magnitude.
7. Avoid Big Integers on the Fast Path
Older shortest-conversion algorithms often relied on multiprecision arithmetic or complicated variable-length digit generation. Ryu’s breakthrough was to derive bounds tight enough that common IEEE binary32 and binary64 conversion can use fixed-size integer operations with precomputed powers or reciprocals of powers of five.
Conceptually, a large multiplication followed by a carefully selected shift extracts the exact quotient bits required for decimal interval computation. Wide intermediates—such as 128-bit products when converting a 64-bit significand—are valuable here.
8. Determine the Decimal Exponent
The algorithm estimates how many decimal powers are needed from the binary exponent using integer approximations to logarithms such as log10(2) or log10(5). These approximations are designed with proven bounds so the chosen exponent is exact for the supported input range.
This is a useful professional lesson: replacing floating logarithms with integer approximations is safe only when the approximation error and valid domain are proved, not guessed.
9. Produce an Integer Decimal Interval
After multiplying by the appropriate power-of-five factor and shifting, Ryu obtains integer decimal candidates representing the lower boundary, central value and upper boundary at the chosen decimal scale.
At this point the difficult binary-to-decimal problem has been transformed into a simpler question: how many least-significant decimal digits can we remove while the remaining number still represents some decimal inside the valid round-trip interval?
10. Remove Digits, But Track Exactness
Repeated division by 10 narrows the candidate. But boundary cases depend on whether discarded digits were exactly zero, whether a boundary is inclusive and whether the final rounding digit is exactly halfway.
Efficient tests for divisibility by powers of 2 or 5 allow Ryu to know when a boundary corresponds exactly to a decimal grid point. That information controls whether one more digit may safely disappear.
11. Ties-to-Even Matters
IEEE round-to-nearest, ties-to-even means a value exactly halfway between adjacent floats goes to the one with an even significand. Therefore two adjacent floating-point values do not simply own permanently closed intervals. Boundary inclusion depends on which side wins the tie.
A correct shortest formatter must reflect this rule when deciding whether the lower or upper boundary itself is an allowed decimal. Many subtle conversion bugs live precisely at halfway cases.
12. Choose the Final Rounded Decimal
Once no more safe digits can be removed, the formatter chooses the integer decimal value within the remaining lower/upper interval that is nearest to the exact source value, applying the required tie policy. The decimal exponent recorded earlier determines where the decimal point or scientific exponent belongs.
digits = shortest_safe_integer
exp10 = decimal_scale
format(digits, exp10, sign)
13. Safe High-Level Skeleton
RYU_SHORTEST(bits):
handle_zero_inf_nan(bits)
sign, m2, e2 = decode_ieee754(bits)
lower, value, upper, boundary_flags = build_scaled_binary_interval(m2, e2)
q = choose_decimal_scale(e2)
dl, dv, du = scale_interval_with_pow5_and_shifts(
lower, value, upper, q)
while another decimal digit can be removed safely:
dl, dv, du = divide_interval_by_10(dl, dv, du)
update_exactness_and_trailing_zero_state()
output = choose_correct_rounded_integer(dl, dv, du, boundary_flags)
return format_decimal(sign, output, q_adjusted)
This skeleton teaches the architecture but is not a replacement for the paper’s arithmetic lemmas. In production, use a reviewed implementation or derive every multiplication constant and shift from the proven formulas.
14. Shortest Is Different From printf
Shortest formatting answers “what is the smallest round-tripping decimal?” A printf-style request such as %.6f or %.10e asks for a specified number of decimal places or significant digits. Those are different output contracts.
Adams later published Ryu Printf, extending the same fixed-integer philosophy to fixed and scientific decimal formatting. Do not assume a shortest-only routine automatically handles arbitrary precision formatting correctly.
15. Why Casting the Input Can Be Wrong
The reference Ryu project explicitly warns against casting a float to a wider floating type before shortest conversion. Shortest output is defined relative to the precision and rounding interval of the original format. Widening preserves the numeric value but changes the set of neighbouring representable numbers, so it can change the shortest-string problem.
16. Failure Modes
- Printing the exact decimal expansion instead of the shortest round-trip decimal. Correct but needlessly long output is a different contract.
- Using floating arithmetic to test boundaries. Conversion correctness should not depend on a second layer of rounding error.
- Ignoring subnormals. Their significand/exponent decoding differs from normal numbers.
- Mishandling powers of two. Spacing between neighbours changes at exponent boundaries.
- Assuming both interval boundaries are inclusive. Ties-to-even affects ownership.
- Overflowing fixed-width intermediate products. The proof assumes sufficiently wide multiplication and exact shifts.
- Changing precomputed tables without regenerating proofs/tests. A single incorrect power-of-five constant can corrupt rare inputs.
- Testing only ordinary human-sized decimals. Hard cases cluster near extreme exponents and boundaries.
17. Professional Testing Strategy
- For every produced string s, assert parse(s) reproduces the exact original bit pattern.
- Check that removing the final emitted significant digit cannot still round-trip, except where formatting syntax changes representation.
- Exhaustively test all binary16 values and, where feasible, all binary32 values.
- Target zeros, signed zero, smallest/largest subnormals, smallest normals, powers of two, maximum finite values, infinities and NaNs.
- Generate values adjacent to decimal powers and binary exponent transitions.
- Differential-test against the reference Ryu implementation and another correctly rounded formatter.
- Run sanitizers and integer-overflow checks on ports where wide multiplication semantics differ.
18. How to Learn It Efficiently
Begin with tiny toy formats. Create a four- or five-bit floating system whose neighbouring values can be drawn on a number line. Mark the midpoint interval owned by one value and ask which short decimals fall inside it. This teaches the real problem before powers-of-five arithmetic appears.
Then use a subgoal sequence: decode → build interval → choose decimal scale → integer-scale interval → remove digits → round → format. Worked examples and code tracing are especially valuable because the production implementation is dense; learners need conceptual anchors before reading optimized constants and shifts.
19. Professional Applications
- Language runtimes and standard libraries.
- JSON serializers and parsers.
- Databases and data interchange.
- Logging and observability systems.
- Scientific software requiring reproducible textual round trips.
- Compilers, debuggers and REPLs.
- High-throughput telemetry where number formatting is on a hot path.
20. Practice Problems
- For a toy binary format, draw the rounding interval for one representable value.
- Find two different decimals that parse to the same binary float and identify the shorter one.
- Explain algebraically why decimal conversion introduces powers of five.
- Implement a slow arbitrary-precision shortest converter to use as an oracle.
- Compare the original bit pattern after formatting and parsing one million random binary32 values.
- Investigate cases where a float and the same numeric value widened to double require different shortest strings.
- Benchmark Ryu-style formatting against a general-purpose big-integer converter and a standard library formatter.
21. Sources and Further Reading
- Ulf Adams, Ryū: Fast Float-to-String Conversion, PLDI 2018.
- Official Ryu reference implementation, tests and implementation notes.
- Ulf Adams, Ryū Revisited: Printf Floating Point Conversion.
- Widely used pure-Rust implementation of Ryu.
- Muldner, Jennings and Chiarelli, A Review of Worked Examples in Programming Activities, ACM TOCE, 2023.
Final idea: Ryu is not mainly a clever way to print digits. It is a lesson in turning a numerical specification into an exact integer problem. Once the formatter defines the interval of decimals that safely round back to the source float, the rest of the algorithm is about finding the shortest inhabitant of that interval without introducing new rounding uncertainty.
