Skip to content

Number ToString uses scientific notation prematurely for integers >= 1e15 #400

@frostney

Description

@frostney

Summary

Number.prototype.toString() (and the abstract ToString(number) operation invoked via String(n), template literals, property access via numeric keys, etc.) emits scientific notation for integers in the range `[1e15, 1e21)`, where the ECMAScript spec mandates fixed-point decimal output.

Per ES2026 §6.1.6.1.13 Number::toString, when `n - k <= 21` (and `k <= n`) the algorithm appends `(n - k)` zeros to the significant digits. Scientific notation is only used when the value's exponent forces it — concretely, integers `<` `1e21` always have a fixed-point representation.

Repro

const cases = [
  1e14, 1e15, 1e16, 1e20, 1e21,
  9007199254740991,         // Number.MAX_SAFE_INTEGER
  999999999999999,          // 1e15 - 1
  4294967295,               // 2^32 - 1
  4294967296,               // 2^32
  1234567890123456,
  -1e15,
];
for (const n of cases) console.log(n);
Input GocciaScript output ECMAScript-correct output
`1e14` `100000000000000` `100000000000000`
`1e15` `1E15` `1000000000000000`
`1e16` `1E16` `10000000000000000`
`1e20` `1E20` `100000000000000000000`
`1e21` `1E21` `1e+21` (lowercase `e+`)
`Number.MAX_SAFE_INTEGER` (`9007199254740991`) `9.00719925474099E15` `9007199254740991`
`999999999999999` `999999999999999` `999999999999999`
`1234567890123456` `1.23456789012346E15` `1234567890123456`
`-1e15` `-1E15` `-1000000000000000`

Two distinct deviations are visible:

  1. The threshold for switching to scientific notation kicks in at `1e15` instead of `1e21`. Any integer with magnitude `>=` `1e15` and `<` `1e21` should be rendered as fixed-point.
  2. When scientific notation is used (correctly, for `>= 1e21`), the format is `1E21` (uppercase `E`, no `+`) but the spec mandates lowercase `e+` — `1e+21`.

Impact

  • Surfaced while implementing sparse iteration for `Array.prototype` methods on huge-length array-likes. test262 fixtures like `built-ins/Array/prototype/indexOf/length-near-integer-limit.js` set `arrayLike[Number.MAX_SAFE_INTEGER - N] = value`. Because `ToString(MAX_SAFE_INTEGER - N)` produces a scientific-notation string, the property key is non-canonical and array-index lookups (including the new sparse path's `TryParseArrayIndex`) correctly skip it.
  • Any user-visible `String(n)`, `${n}`, `JSON.stringify`, etc. for large integers prints incorrect values that don't roundtrip with `parseFloat`/`Number`.

Where to look

The fix lives in whatever implements ToString for `TGocciaNumberLiteralValue`. The spec algorithm to reproduce is ES2026 §6.1.6.1.13 — find the unique `n`, `k`, `s` triple and dispatch based on `k` vs `n` rather than always emitting scientific form past some heuristic.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions