Skip to content

Number Rendering

The normative rule is in the Bars specification; this page covers how it appears in fixtures and in the reference implementation.

{{ n }} renders a number exactly as ECMA-262 Number.prototype.toString() (radix 10) would: the shortest decimal string that round-trips to the same IEEE-754 double, with ECMA's fixed/exponential notation selection. Every kBars target, and every port to another language, must produce byte-identical output for the same numeric value, rather than delegating to a host language's native Double.toString() (which, on the JVM and Kotlin/Native, is not always shortest-round-trip).

1.0   -> "1"        2.0  -> "2"      100.0 -> "100"
1e20  -> "100000000000000000000"     1e21  -> "1e+21"
1e-6  -> "0.000001"                  1e-7  -> "1e-7"
-0.0  -> "0"        0.1+0.2 -> "0.30000000000000004"
Double.MAX_VALUE -> "1.7976931348623157e+308"   Double.MIN_VALUE -> "5e-324"
NaN -> "NaN"   Infinity -> "Infinity"   -Infinity -> "-Infinity"

The reference implementation (:kbars commonMain, impl/number/Ryu.kt and EcmaNumberFormat.kt) generates shortest round-trip digits by porting the digit-generation core of ulfjack/ryu (Apache-2.0-or-Boost) and layers a hand-written ECMA-262 notation selector on top. A port does not need to reuse Ryu specifically — any correct shortest-round-trip digit generator (Grisu3, Dragon4, a bundled bignum library, or your host language's own Number.prototype.toString() if it happens to be JavaScript) is acceptable, provided the output text matches these fixtures.

Notation selection

Given the shortest round-trip digit string and its decimal exponent, ECMA-262 chooses between fixed and exponential notation using fixed thresholds (not "however many digits look reasonable"):

  • Magnitudes from 1e-6 (inclusive) up to 1e21 (exclusive) use fixed notation.
  • Everything else — very large or very small magnitudes — uses exponential notation, formatted as <digit>[.<digits>]e<+|->EXP.

-0.0 renders "0" (the sign is dropped); NaN, Infinity, and -Infinity render as those exact literal words, not numeric notation.

Integer vs. float is not observable in rendered output

2 and 2.0 both render "2" — kBars does not echo the source literal's int/float distinction. The distinction survives only in comparison (==, <, etc.), which already coerces both operands to Double before comparing, so equality and truthiness treat 2 and 2.0 identically as well. A port that instead echoes the source JSON literal (preserving "2.0" because the source said 2.0), or that delegates digit generation to its host language's native number-to-string conversion without checking it against shortest-round-trip, will diverge from these fixtures.

Fixtures

  • conformance/render/number-rendering.json locks the ECMA-262 notation-boundary cases shown above (whole doubles, over-Long integers, the 1e20/1e21 and 1e-6/1e-7 fixed/exponential thresholds, -0.0, 0.1 + 0.2, Double.MAX_VALUE/Double.MIN_VALUE).
  • conformance/render/int-float-parity.json locks that 2 and 2.0 render identically and compare/truthy identically.

See also the consumer-facing summary at Number Rendering in the kBars usage guide.