Skip to content

fix(runtime): Number("0x…") wider than u64 rounds to f64 instead of NaN#6123

Merged
proggeramlug merged 1 commit into
mainfrom
fix/6079-number-hex-overflow
Jul 8, 2026
Merged

fix(runtime): Number("0x…") wider than u64 rounds to f64 instead of NaN#6123
proggeramlug merged 1 commit into
mainfrom
fix/6079-number-hex-overflow

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Another #6079 residual.

Problem

Number("0x…"/"0o…"/"0b…") used u64::from_str_radix, which returns Err (→ NaN) once the value exceeds u64::MAX. But ECMAScript's NonDecimalIntegerLiteral has no width limit, so oversized literals must round to the nearest double:

Number("0xFFFFFFFFFFFFFFFFF"); // node 295147905179352830000   perry NaN

Fix

Accumulate the digits directly in f64 (value = value * radix + digit), matching ToNumber rounding. Out-of-radix digits ("0b12") and empty bodies ("0x") still yield NaN.

Test

test-files/test_gap_number_nondecimal_overflow.ts — overflow values, small values, error cases, and uppercase prefixes, all byte-matched vs node --experimental-strip-types.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed Number(...) handling for non-decimal (hexadecimal, octal, binary) numeric literals, including very large magnitudes.
    • Oversized inputs now follow standard floating-point rounding behavior (with correct Infinity overflow) instead of returning NaN.
    • Incomplete or malformed prefixed values (e.g., only 0x/0b/0o) still return NaN.
  • Tests
    • Added new cases covering oversized and boundary values, uppercase base prefixes, and correctness checks for large results.

@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e37b2051-dfb8-41a0-89d9-b6e57b07e36c

📥 Commits

Reviewing files that changed from the base of the PR and between 9ce27d4 and 3a4b1b2.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/builtins/numbers.rs
  • test-files/test_gap_number_nondecimal_overflow.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • test-files/test_gap_number_nondecimal_overflow.ts
  • crates/perry-runtime/src/builtins/numbers.rs

📝 Walkthrough

Walkthrough

Modifies js_number_coerce so non-decimal integer string literals are converted with correctly rounded f64 parsing instead of u64 parsing. Adds tests for overflow, boundary, invalid-prefix, uppercase-prefix, and rounding behavior.

Changes

Non-decimal number parsing overflow handling

Layer / File(s) Summary
Correctly rounded non-decimal parsing
crates/perry-runtime/src/builtins/numbers.rs, test-files/test_gap_number_nondecimal_overflow.ts
js_number_coerce now uses a helper that accumulates valid 0x/0o/0b digits into a rounded f64, returns NaN for empty or invalid digit bodies, and keeps overflowed magnitudes as floating-point values; the new test file covers oversized literals, exact boundaries, invalid prefixes, uppercase prefixes, and Infinity cases.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

Possibly related PRs

  • PerryTS/perry#5565: Shares the Expr::NumberCoerce lowering path that feeds into this runtime coercion logic.
  • PerryTS/perry#5822: Uses js_number_coerce before ToInt32, so this parsing change affects the same conversion path.
  • PerryTS/perry#6030: Routes radix coercion through js_number_coerce, directly overlapping with the updated non-decimal parsing behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: non-decimal Number parsing now rounds to f64 instead of returning NaN.
Description check ✅ Passed The description covers the problem, fix, and test evidence, but it does not follow the template headings exactly and omits a related issue and checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/6079-number-hex-overflow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/perry-runtime/src/builtins/numbers.rs`:
- Around line 441-444: The non-decimal parsing loop in numbers::parse_float
currently accumulates into an f64 per digit with value = value * radix_f + ...,
which can round incorrectly for long base-2/8/16 literals. Update the conversion
path in the digits handling logic to preserve exact integer-to-f64 behavior,
either by using an exact conversion path or by tracking guard/round/sticky
information instead of repeated f64 accumulation. Add boundary tests around the
relevant parsing function to cover long non-decimal literals and ensure they
match exact conversion results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 85bcfa05-35e7-471c-9e4b-553dc45e97fd

📥 Commits

Reviewing files that changed from the base of the PR and between 3f64786 and 9ce27d4.

📒 Files selected for processing (2)
  • crates/perry-runtime/src/builtins/numbers.rs
  • test-files/test_gap_number_nondecimal_overflow.ts

Comment thread crates/perry-runtime/src/builtins/numbers.rs Outdated
… of NaN

The non-decimal literal path used `u64::from_str_radix`, which overflows (→
NaN) once the value exceeds u64::MAX — but ECMAScript's NonDecimalIntegerLiteral
has no width limit, so `Number("0xFFFFFFFFFFFFFFFFF")` must round to the nearest
double (node: 295147905179352830000), not NaN.

Convert with a correctly-rounded (round-to-nearest, ties-to-even) bit
accumulation: collect significant bits into a u128, fold anything that overflows
into a sticky bit, then round — a naive `value*radix + digit` in f64 rounds at
every step and mis-rounds past 2^53 (e.g. 0x3fffffffffffff8). Verified the exact
doubles match node via `===` for 2^53 tie boundaries, 2^58, and >f64::MAX →
Infinity. Adds test_gap_number_nondecimal_overflow.
@proggeramlug proggeramlug force-pushed the fix/6079-number-hex-overflow branch from 9ce27d4 to 3a4b1b2 Compare July 8, 2026 12:26
@proggeramlug

Copy link
Copy Markdown
Contributor Author

Good catch — addressed. The parse is now correctly-rounded: I replaced the naive f64 accumulation with a round-to-nearest, ties-to-even conversion that accumulates significant bits into a u128, folds overflow into a sticky bit, then rounds (parse_nondecimal_to_f64).

Verified the exact doubles match node via === (not the printed strings): 0x1fffffffffffff=2^53-1, 0x20000000000001=2^53 (ties down), 0x20000000000003=2^53+4 (rounds up), 0x3fffffffffffff8=2^58, and 0x+f×260 = Infinity — all true, and the gap test asserts them with ===.

Note on your script's decimal diff: the value your cases produce is already the correct double (e.g. Number('0x3fffffffffffff8') === 2**58 is true, bits 4390000000000000). The visible …744 vs …740 is a separate number→string issue — Perry prints the exact integer instead of V8's shortest round-trip — filed as #6127.

@proggeramlug proggeramlug merged commit 042c4a9 into main Jul 8, 2026
23 of 24 checks passed
@proggeramlug proggeramlug deleted the fix/6079-number-hex-overflow branch July 8, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant