Skip to content

Fix checked_mul off-by-one, non-finite rem platform dependence, and relax Statistics compat - #330

Merged
bjarthur merged 5 commits into
JuliaMath:masterfrom
bjarthur:bja/v0.9.1
Aug 31, 2026
Merged

Fix checked_mul off-by-one, non-finite rem platform dependence, and relax Statistics compat#330
bjarthur merged 5 commits into
JuliaMath:masterfrom
bjarthur:bja/v0.9.1

Conversation

@bjarthur

@bjarthur bjarthur commented Aug 31, 2026

Copy link
Copy Markdown
Collaborator

Description:

Three small fixes for v0.9.1, found while reviewing the v0.8.5...v0.9.0 diff.

Fix off-by-one in the overflow check of checked_mul for Normed

The specialized integer path rejects a widened product z when z >= m where m = typemax(N).i * rawone(N) + (rawone(N) >> 1). But rawone(N) = 2^f - 1 is odd, so z == m still rounds to exactly typemax(N) and should not throw:

julia> checked_mul(1.228N1f7, 1.638N1f7)  # exact result 2.0117 rounds to typemax = 2.008
ERROR: OverflowError: 1.228N1f7 * 1.638N1f7 overflowed for type N1f7

This disagreed with both wrapping_mul (returns typemax, no wrap) and the generic float fallback (z < typemax(N) + eps(N)/2, which accepts values that round to typemax). Changed the comparison to z <= m and added regression tests for boundary cases in N1f7 and N7f9. Verified exhaustively for all Normed{UInt8} types that checked_mul now throws exactly when the rounded result exceeds typemax, and agrees with wrapping_mul otherwise.

Make rem of non-finite values platform-independent for small types

_rem(x::Real, ...) skipped the isfinite guard when bitwidth(T) < 32, leaving the result to unspecified float-to-int conversion behavior (the "reduced likelihood" residue of #291): Inf % N0f8 returned 1.0N0f8 on aarch64 but 0.0N0f8 on x86_64. The guard is now unconditional for both Fixed and Normed, so NaN/±Inf map to zero on all platforms, matching the 32- and 64-bit types. This also makes saturating_fdiv(x, zero(x)) well-defined. test_rem_nan now covers ±Inf and its "TODO: avoid undefined behavior" is resolved.

Relax Statistics compat to allow v1.11.2 and later

Project.toml said "Update this version specifier when Statistics.jl v1.11.2 is released" (JuliaStats/Statistics.jl#165) — Statistics v1.11.4 is now registered, so the "< 1.11.2" pin has become a trap: any environment requiring newer Statistics silently downgrades FixedPointNumbers to 0.8.6. Statistics v1.11.4 still provides _mean_promote, the extension already guards its use with isdefined, and the full test suite passes against v1.11.4. Relaxed to Statistics = "1".

Fix version spoof in the Invalidations workflow

The invalidations CI job was failing on this PR (and would fail on any PR since the 0.9.0 version bump) for reasons unrelated to the code: the workaround from #287 rewrites the version in Project.toml to 0.8.4 so the resolver can co-install SnoopCompile, whose dependency FlameGraphs pins FixedPointNumbers = "0.6.1-0.8". That rewrite matched the literal line version = "0.9.0-dev", which no longer exists, so the real 0.9.x version reached the resolver and produced "Unsatisfiable requirements ... FlameGraphs". The spoof now matches any version = line. Only the version label is spoofed — the invalidation measurement still loads this branch's source. Both Overwrite Package Version # FIXME steps can be deleted entirely once FlameGraphs publishes a release whose compat allows FixedPointNumbers 0.9.

bjarthur and others added 5 commits August 31, 2026 16:30
The divisor 2^f-1 is odd, so a widened product equal to
typemax(N).i * rawone(N) + (rawone(N) >> 1) still rounds to typemax(N)
and must not throw. Previously such products (e.g.
checked_mul(1.228N1f7, 1.638N1f7)) spuriously raised OverflowError,
disagreeing with both wrapping_mul and the generic float fallback.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
_rem skipped the isfinite guard when bitwidth(T) < 32, leaving the
result to unspecified float-to-int conversion behavior: Inf % N0f8
returned 1.0N0f8 on aarch64 but 0.0N0f8 on x86_64. Check isfinite
unconditionally so NaN/Inf map to zero on all platforms, matching the
wider types. This also makes saturating_fdiv(0, 0) well-defined.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The pin below v1.11.2 was awaiting the next Statistics.jl release
(JuliaStats/Statistics.jl#165). Statistics v1.11.4 is now registered,
still provides _mean_promote, and the extension already guards its use
with isdefined; the full test suite passes against v1.11.4. Without
this, environments requiring newer Statistics downgrade
FixedPointNumbers to 0.8.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The workaround from JuliaMath#287 rewrote the literal line
'version = "0.9.0-dev"' to 0.8.4 so that the invalidations action can
co-install SnoopCompile (whose dependency FlameGraphs pins
FixedPointNumbers to <= 0.8). The literal no longer matches now that
the version is 0.9.x, so the resolver hit unsatisfiable requirements.
Match any version line instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.42%. Comparing base (0bd7124) to head (f24a6e0).

Additional details and impacted files
@@           Coverage Diff           @@
##           master     #330   +/-   ##
=======================================
  Coverage   96.42%   96.42%           
=======================================
  Files           7        7           
  Lines         784      784           
=======================================
  Hits          756      756           
  Misses         28       28           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bjarthur
bjarthur merged commit d1a812f into JuliaMath:master Aug 31, 2026
13 checks passed
@bjarthur
bjarthur deleted the bja/v0.9.1 branch August 31, 2026 21:04
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