#5305: reject numbers outside long range in number.as-i64 instead of saturating#5315
Conversation
🚀 Performance AnalysisAll benchmarks are within the acceptable range. No critical degradation detected (threshold is 100%). Please refer to the detailed report for more information. Click to see the detailed report
|
|
The |
|
@morphqdd build is broken here |
7edbb7f to
af8957b
Compare
|
@yegor256 the failing build here isn't a defect in this change itself — it's a blocking design dependency I documented in #5318. This PR makes So this can't go green until we decide how to handle #5318:
I kept this PR open rather than force it through, since the right fix depends on that decision. Happy to implement whichever direction you prefer. |
gemshrine
left a comment
There was a problem hiding this comment.
One boundary case slips through the range check as written.
number <= Long.MAX_VALUE compares against (double) Long.MAX_VALUE, which is exactly 9.223372036854776E18 == 2^63 — Long.MAX_VALUE has no exact double representation, so the promotion overshoots by 1. That means 9.223372036854776E18.as-i64 still slips through: the range check passes and Double::longValue then saturates to Long.MAX_VALUE — the exact behavior the PR is aiming to reject. Reproduced locally:
d = 9.223372036854776E18
passes range check: true
Double.valueOf(d).longValue() = 9223372036854775807 // == Long.MAX_VALUE
Swapping the upper bound to a strict < against 0x1p63 (the smallest double that's already above Long.MAX_VALUE) closes this without perturbing the low end, which is exact:
.must(number -> number >= Long.MIN_VALUE && number < 0x1p63)A 9.223372036854776e18.as-i64 test alongside the 1.0e30 one would lock this in.
The sibling Expect.Int fix in #5307 uses <= Integer.MAX_VALUE — but (double)Integer.MAX_VALUE is exact, so that check is unaffected by the same class of bug.
31edaed to
84e707a
Compare
|
@gemshrine excellent analysis, thank you — you're exactly right. Switched the upper bound to a strict .must(number -> number >= Long.MIN_VALUE && number < 0x1p63)and added a The remaining red on this PR is only |
|
@morphqdd the master is clean now, please update your branch |
84e707a to
aadd28b
Compare
… instead of saturating
aadd28b to
63d4a8a
Compare
|
|
@morphqdd Thanks for the contribution! You've earned +12 points for this: +16 as a basis; -4 for too few (19) hits-of-code. Please, keep them coming. Your running score is +744; don't forget to check your Zerocracy account too). |
|
@gemshrine Thanks for the review! You've earned +4 points for this: +12 as a basis; -8 for absolutely no comments posted; -4 for too few (19) hits-of-code; +4 to give you at least something. Your running score is +68; don't forget to check your Zerocracy account too). |



Closes #5305.
number.as-i64(EOnumber$EOas_i64.java) converted a genericnumbertoi64vianew Expect.Number(Expect.at(this, Phi.RHO)).it().longValue().Expect.Number.it()onlyvalidates the value is a number at all — it doesn't check the value is a whole number, nor that
it fits in
longrange.Double.longValue()doesn't throw for a value outsidelongrange — itsaturates, returning
Long.MAX_VALUE/Long.MIN_VALUE. So1e30.as-i64— vastly larger than anyi64can represent — silently producedLong.MAX_VALUEinstead of raising a domain error. Thisis the same class of defect already reported for
Expect.Natural/Expect.Intin #5295, here viathe separate
Expect.Numberwrapper that backs this very commonly used generic-to-i64conversion.
Rather than modifying
Expect.Numberitself (which is used broadly wherever code just needs "isthis a number", with no integer/range assumptions — changing it would affect every caller),
inlined the same
Expect.at(...)pipeline directly inEOnumber$EOas_i64with an added rangecheck before narrowing, mirroring the style already used in
Expect.Int/Expect.Natural(#5295/#5307):
Added
throws-on-converting-huge-number-to-i64tonumber.eo(1.0e30.as-i64), the exactreproduction value from the issue.
Ran the full EO-generated number test suite locally (
EOnumberEOAtomTest) — all 202 tests pass.The verbose log confirms the exact fix in action:
replacing what was previously a silent saturation to
Long.MAX_VALUE.