Skip to content

Fix #349: overflow-safe scalar_number arithmetic - #402

Open
petlenz wants to merge 3 commits into
fix-345-pow-distribute-guardfrom
fix-349-scalar-number-overflow
Open

Fix #349: overflow-safe scalar_number arithmetic#402
petlenz wants to merge 3 commits into
fix-345-pow-distribute-guardfrom
fix-349-scalar-number-overflow

Conversation

@petlenz

@petlenz petlenz commented Jul 24, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #349. Stacked on #401.

Before After
pow(10,30)5076944270305263616 (mod 2⁶⁴, silent) 1e30 (double demotion)
rat_add at ~2⁴⁰ magnitudes → signed-overflow UB (UBSan abort) checked; demotes to double, value ≈ correct
1/2 ÷ 0 → stored 1/0 rational (invariant violation) +inf double, matching the int/int path
normalize_rational/negation at INT64_MIN → UB demotes to double

Design

  • Overflow-checked int64 helpers: __builtin_*_overflow on GCC/Clang, manual range checks on MSVC (which is in the CI matrix and lacks both the builtins and __int128).
  • On overflow the operation demotes to double — value stays correct, exactness is lost only at the extremes. In-range exact arithmetic is unchanged and lock-in tested (1/3 + 1/6 stays the exact rational 1/2).
  • pow needed no change: it composes operator*, so the checked multiply fixes the wraparound for free.
  • Related: scalar_number: rational comparison can overflow on large num/den #142 fixed the comparison side (rat_less) with the same demotion philosophy; this completes the arithmetic side. The follow-up idea of keeping exactness via __int128 where available is left out deliberately (MSVC portability, and no physics workload needs exact >2⁶³ rationals).

Tests

4 lock-ins: pow wraparound, rational-add UB repro (value ≈ 2.0), div-by-zero → +inf double, exactness preservation. Full suite: 2437/2437 pass. gcc-14 -Werror compile of the TU clean. The UBSan CI leg (once #356's -fno-sanitize-recover lands) becomes the standing regression net for this class.

@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch from 9a9d168 to 70408d5 Compare July 25, 2026 12:16
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch from 339d1f9 to 88df476 Compare July 25, 2026 12:19
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch from 70408d5 to 66e0c9e Compare July 25, 2026 13:32
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch from 88df476 to c591552 Compare July 25, 2026 13:36
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch from 66e0c9e to 96a1ace Compare July 25, 2026 22:33
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch 2 times, most recently from 21f6bab to 7b227ce Compare July 26, 2026 12:14
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch 2 times, most recently from 413cd76 to 733ccec Compare July 26, 2026 12:43
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch from 7b227ce to 8f6a90c Compare July 26, 2026 12:43
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch from 733ccec to 95e48d6 Compare July 26, 2026 17:18
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch from 8f6a90c to 651e725 Compare July 26, 2026 17:18
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch from 95e48d6 to ee0639e Compare July 26, 2026 17:42
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch 2 times, most recently from 0a7379c to bbd0983 Compare July 26, 2026 18:16
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch 2 times, most recently from fa383f0 to c99a14a Compare July 26, 2026 18:40
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch from bbd0983 to 02b8d10 Compare July 26, 2026 18:40
petlenz added 3 commits July 26, 2026 20:58
int64 arithmetic in scalar_number wrapped silently (UB): pow(10,30)
folded to 5076944270305263616 (10^30 mod 2^64), rat_add/rat_sub cross
products were signed-overflow UB at ~2^40 magnitudes (UBSan-confirmed),
rat_div could construct an invariant-violating 1/0 rational, and
normalize_rational/negation hit UB at INT64_MIN.

All int64 paths are now overflow-checked (builtin overflow intrinsics
on GCC/Clang, manual range checks on MSVC) and demote to double when
the exact value does not fit — value stays correct, exactness is lost
only at the extremes. rat_div routes zero denominators to +-inf double
like the int/int path, and INT64_MIN operands demote instead of
negating. pow needs no change: it composes operator*, so the checked
multiply fixes the wraparound.

Exact behavior in range is unchanged and lock-in tested (1/3 + 1/6
stays the exact rational 1/2).

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
to_rational promotes int64 operands without normalization, so
scalar_number(INT64_MIN) * scalar_number(1,2) reached
std::abs(INT64_MIN) in rat_mul's gcd cross-cancel (UBSan-confirmed
abort) - the exact class #349 eliminates elsewhere. rat_mul (and
rat_div through it) now demotes to double when any component is
INT64_MIN, matching the existing normalize_rational guard.
Regression test covers mul both ways and div.

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
pow's repeated-squaring negated the exponent with -n before the
uint64 cast - signed-negation UB for INT64_MIN (round-2 review;
UBSan abort on pow(1/2, INT64_MIN)). |n| is now computed as
0u - unsigned(n). Also repairs conflict markers committed during the
stack rebase.

Signed-off-by: petlenz <peterlenz89.pl@gmail.com>
@petlenz
petlenz force-pushed the fix-349-scalar-number-overflow branch from 02b8d10 to 33a9689 Compare July 26, 2026 19:03
@petlenz
petlenz force-pushed the fix-345-pow-distribute-guard branch from c99a14a to 32988a7 Compare July 26, 2026 19:03
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