我参加聚会有点晚了,但我需要实施一个通用解决方案,结果发现没有一个解决方案可以满足我的需求。
公认的解决方案适用于小范围;但是,maximum - minimum 对于大范围可能是无穷大。所以修正后的版本可以是这个版本:
public static double NextDoubleLinear(this Random random, double minValue, double maxValue)
{
// TODO: some validation here...
double sample = random.NextDouble();
return (maxValue * sample) + (minValue * (1d - sample));
}
即使在double.MinValue 和double.MaxValue 之间也能很好地生成随机数。但这引入了另一个“问题”,很好地呈现了in this post:如果我们使用如此大的范围,这些值可能看起来太“不自然”。例如,在 0 和 double.MaxValue 之间生成 10,000 个随机双精度后,所有值都在 2.9579E+304 和 1.7976E+308 之间。
所以我还创建了另一个版本,它以对数刻度生成数字:
public static double NextDoubleLogarithmic(this Random random, double minValue, double maxValue)
{
// TODO: some validation here...
bool posAndNeg = minValue < 0d && maxValue > 0d;
double minAbs = Math.Min(Math.Abs(minValue), Math.Abs(maxValue));
double maxAbs = Math.Max(Math.Abs(minValue), Math.Abs(maxValue));
int sign;
if (!posAndNeg)
sign = minValue < 0d ? -1 : 1;
else
{
// if both negative and positive results are expected we select the sign based on the size of the ranges
double sample = random.NextDouble();
var rate = minAbs / maxAbs;
var absMinValue = Math.Abs(minValue);
bool isNeg = absMinValue <= maxValue ? rate / 2d > sample : rate / 2d < sample;
sign = isNeg ? -1 : 1;
// now adjusting the limits for 0..[selected range]
minAbs = 0d;
maxAbs = isNeg ? absMinValue : Math.Abs(maxValue);
}
// Possible double exponents are -1022..1023 but we don't generate too small exponents for big ranges because
// that would cause too many almost zero results, which are much smaller than the original NextDouble values.
double minExponent = minAbs == 0d ? -16d : Math.Log(minAbs, 2d);
double maxExponent = Math.Log(maxAbs, 2d);
if (minExponent == maxExponent)
return minValue;
// We decrease exponents only if the given range is already small. Even lower than -1022 is no problem, the result may be 0
if (maxExponent < minExponent)
minExponent = maxExponent - 4;
double result = sign * Math.Pow(2d, NextDoubleLinear(random, minExponent, maxExponent));
// protecting ourselves against inaccurate calculations; however, in practice result is always in range.
return result < minValue ? minValue : (result > maxValue ? maxValue : result);
}
一些测试:
以下是使用这两种策略生成 0 到 Double.MaxValue 之间的 10,000 个随机双精度数的排序结果。结果使用对数刻度显示:
虽然线性随机值乍一看似乎是错误的,但统计数据表明它们都没有比另一个“更好”:即使线性策略也有均匀分布,并且值之间的平均差异几乎相同两种策略。
玩不同的范围向我展示了线性策略变得“理智”,范围在 0 和 ushort.MaxValue 之间,“合理”的最小值为 10.78294704
(对于ulong 范围,最小值为 3.03518E+15;int: 353341)。以下是以不同比例显示的两种策略的相同结果:
编辑:
最近我将我的libraries 开源了,欢迎查看带有完整验证的RandomExtensions.NextDouble 方法。