Skip to content

NextDouble(min, max) can return maxExclusive, and returns Infinity for wide finite bounds (Uniform distribution too) #61

Closed

Description

@matt-edmondson

What's wrong

IRandomProvider.NextDouble(minInclusive, maxExclusive) promises a value in [min, max). It is defined in Essentials/IRandomProvider.cs, around line 188:

return minInclusive + (NextDouble() * (maxExclusive - minInclusive));

This has two separate problems.

1. It can return the excluded upper bound

When the unit draw is close to 1, rounding the final addition can land exactly on maxExclusive.

  • NextDouble(1.0, Math.BitIncrement(1.0)) returned maxExclusive in 495 of 1000 draws.
  • The top unit draw also returns max for (1, 2), (0.3, 0.7), (5, 5.5) and (0.9, 1.0).

Callers that rely on the exclusive bound can index out of range or double-count a boundary bucket. Examples are binning, array[(int)NextDouble(0, n)]-style code, and half-open interval partitioning.

2. It overflows for finite bounds that pass validation

maxExclusive - minInclusive overflows to +Infinity when the bounds are far apart:

  • NextDouble(double.MinValue, double.MaxValue) returns Infinity on every call.
  • It returns NaN when the unit draw is 0, because 0 * Inf is NaN.

UniformDistributionProvider has the same flaw (Essentials.DistributionProviders.Uniform/UniformDistributionProvider.cs, around line 43, width = maximum - minimum). With new UniformDistributionProvider(-1e308, 1e308):

Member Actual Expected
Mean Infinity 0
Median Infinity 0
Cdf(0) 0 0.5
Quantile(0.5) Infinity 0
Sample Infinity a finite value

All of the above was reproduced with a console app against the net10.0 builds.

Suggested fix / acceptance criteria

  • Clamp the result: if it is >= maxExclusive (and max > min), return Math.BitDecrement(maxExclusive).
  • When the width is not finite, interpolate without computing the width, for example min * (1 - u) + max * u or 2 * (min/2 + u * (max/2 - min/2)).
  • In UniformDistributionProvider:
    • compute Mean and Median as Minimum/2 + Maximum/2;
    • handle an infinite width in Cdf and Quantile the same way.
  • Add tests:
    • NextDouble(1.0, Math.BitIncrement(1.0)) never returns max (a stub provider returning ulong.MaxValue makes this deterministic);
    • NextDouble(double.MinValue, double.MaxValue) is finite;
    • Uniform(-1e308, 1e308) has Mean == 0 and Cdf(0) == 0.5.

Activity

  1. matt-edmondson commented on Sep 28, 2026

    @matt-edmondson
    ContributorAuthor

    Triage

    Next step: Clamp to Math.BitDecrement(maxExclusive) when the result reaches max. Use a width-free interpolation when max - min is not finite, and give Uniform the same treatment (Minimum/2 + Maximum/2 for Mean and Median). Add the deterministic stub-provider tests listed in the issue.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't workingreadyFully specified; implement as written

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions