Skip to content

Fix Uniform::new_inclusive overflow on large finite float ranges - #1809

Closed
teddytennant wants to merge 3 commits into
rust-random:masterfrom
teddytennant:uniform-float-inclusive-overflow
Closed

Fix Uniform::new_inclusive overflow on large finite float ranges#1809
teddytennant wants to merge 3 commits into
rust-random:masterfrom
teddytennant:uniform-float-inclusive-overflow

Conversation

@teddytennant

Copy link
Copy Markdown
Contributor
  • Added a CHANGELOG.md entry

Summary

Fix Uniform::new_inclusive returning a spurious Error::NonFinite for large finite float ranges such as 0.0..=f64::MAX, as noted by @dhardy in #1603.

Motivation

new_inclusive computes scale = (high - low) / (1 - EPSILON). The division can round to infinity even when high - low is finite (e.g. low = 0.0, high = f64::MAX), so the all_finite() check rejects the range. Meanwhile Uniform::new and sample_single_inclusive both accept the same bounds, which is the inconsistency reported in #1603.

Details

new_inclusive now checks high - low for finiteness first (preserving the NonFinite error for genuinely infinite ranges like f64::MIN..=f64::MAX), then clamps any infinite lanes of scale down to the largest finite value using the existing decrease_masked helper. new_bounded then reduces scale as usual, so samples still cannot exceed high.

Tests: 0.0..=MAX and -MAX..=0.0 are added to the test_floats range list (checking bounds and value ordering for both scalar and SIMD lanes), and test_float_overflow gains an assertion that f64::MIN..=f64::MAX still errors.

Copilot AI review requested due to automatic review settings July 11, 2026 17:34

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Comment thread src/distr/uniform_float.rs Outdated
Comment on lines 136 to 143
if !scale.all_finite() {
return Err(Error::NonFinite);
// The division above may overflow to infinity even though
// `range` is finite (e.g. `low = 0.0`, `high = f64::MAX`).
// Replace infinite lanes with the largest finite value;
// `new_bounded` reduces `scale` as required to ensure that
// samples can never exceed `high`.
scale = scale.decrease_masked(scale.gt_mask(<$ty>::splat($f_scalar::MAX)));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We shouldn't need this if branch at all assuming we change let mask = (scale * max_rand + low).gt_mask(high) (in new_bounded) to handle NaNs by replacing gt_mask with not_le_mask.

Dividing high - low by 1 - EPSILON can round to infinity even when the
range itself is finite (e.g. 0.0..=f64::MAX), yielding a spurious
NonFinite error while Uniform::new and sample_single_inclusive both
accept the same range. Clamp infinite lanes to the largest finite value
and let new_bounded reduce scale as usual, so that samples still cannot
exceed high.

Noted by dhardy in rust-random#1603.
Per review: drop the explicit non-finite scale branch in new_inclusive and
let new_bounded reduce the scale instead. gt_mask compares false for a
non-finite product, so the loop would accept it silently; not_le_mask treats
it as out of bounds and reduces the scale, which decrease_masked already
handles for infinity.

new_inclusive still rejects a non-finite range up front, so f64::MIN..=f64::MAX
remains an error, matching the exclusive range.
not_le_mask replaced its only call site, so gt_mask was dead code and failed
the -D warnings clippy job.
@teddytennant

Copy link
Copy Markdown
Contributor Author

Good call, that's much cleaner. Dropped the special case in new_inclusive and switched new_bounded to not_le_mask, so a non-finite product gets reduced instead of slipping through. gt_mask is unused now so I removed it. Tests pass.

@dhardy

dhardy commented Aug 10, 2026

Copy link
Copy Markdown
Member

Sorry but I already opened a replacement PR (#1821); I just forgot to close this one.

@dhardy dhardy closed this Aug 10, 2026
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.

3 participants