You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
NextDouble(min, max) can return maxExclusive, and returns Infinity for wide finite bounds (Uniform distribution too) #61
IRandomProvider.NextDouble(minInclusive, maxExclusive) promises a value in [min, max). It is defined in Essentials/IRandomProvider.cs, around line 188:
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.
Priority: Medium. NextDouble(min, max) breaks its [min, max) contract. For ordinary ranges it happens only on the top unit draw, but for very narrow ranges it happens about half the time. Finite bounds whose width overflows return Infinity/NaN on every call, and UniformDistributionProvider inherits the same overflow in Mean, Median, Cdf, Quantile and Sample. Callers get wrong values without any error, but only at edge inputs.
Area / suggested owner: Essentials random and distributions: IRandomProvider.NextDouble(double, double) and Essentials.DistributionProviders.Uniform/UniformDistributionProvider.cs
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.
Activity
matt-edmondson commentedon Sep 28, 2026
Triage
NextDouble(min, max)breaks its[min, max)contract. For ordinary ranges it happens only on the top unit draw, but for very narrow ranges it happens about half the time. Finite bounds whose width overflows returnInfinity/NaNon every call, andUniformDistributionProviderinherits the same overflow inMean,Median,Cdf,QuantileandSample. Callers get wrong values without any error, but only at edge inputs.IRandomProvider.NextDouble(double, double)andEssentials.DistributionProviders.Uniform/UniformDistributionProvider.csNextDoubleExclusive()can return 1.0). Both are boundary bugs inIRandomProvider's unit-interval mapping and are worth fixing together in one PR, with shared stub-provider boundary tests. It is in the same family as Poisson and Binomial CDF, median, quantile and Sample are wrong at large parameters (e.g. Poisson(1e5).Cdf(1e5) = 0.671, true value 0.5008) #45/Binomial SurvivalFunction rounds upper tails to 0: Binomial(100, 0.01).SurvivalFunction(20) = 0 (exact 9.58e-22) #50/Geometric with tiny p: Cdf is 0 everywhere and Quantile/Median/Sample spin ~22 s to return int.MaxValue (Geometric(1e-17).Quantile(1e-9), true ≈ 1e8) #51 (distribution edge cases) but touches different code.Next step: Clamp to
Math.BitDecrement(maxExclusive)when the result reachesmax. Use a width-free interpolation whenmax - minis not finite, and giveUniformthe same treatment (Minimum/2 + Maximum/2forMeanandMedian). Add the deterministic stub-provider tests listed in the issue.Generated by Claude Code