Tôi đến bữa tiệc hơi muộn nhưng tôi cần thực hiện một giải pháp chung và hóa ra không có giải pháp nào có thể thỏa mãn nhu cầu của tôi.
Các giải pháp được chấp nhận là tốt cho phạm vi nhỏ; tuy nhiên, maximum - minimum
có thể là vô hạn cho phạm vi lớn. Vì vậy, một phiên bản sửa chữa có thể là phiên bản này:
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));
}
Điều này tạo ra số ngẫu nhiên độc đáo ngay cả giữa double.MinValue
và double.MaxValue
. Nhưng điều này giới thiệu một "vấn đề" khác, được trình bày độc đáo trong bài viết này : nếu chúng ta sử dụng phạm vi lớn như vậy, các giá trị có vẻ quá "không tự nhiên". Ví dụ: sau khi tạo 10.000 nhân đôi ngẫu nhiên giữa 0 và double.MaxValue
tất cả các giá trị nằm trong khoảng từ 2.9579E + 304 đến 1.7976E + 308.
Vì vậy, tôi cũng tạo ra một phiên bản khác, tạo ra các số theo thang logarit:
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);
}
Một số xét nghiệm:
Dưới đây là kết quả được sắp xếp của việc tạo 10.000 số kép ngẫu nhiên trong khoảng từ 0 đến Double.MaxValue
với cả hai chiến lược. Các kết quả được hiển thị với việc sử dụng thang đo logarit:
Mặc dù các giá trị ngẫu nhiên tuyến tính dường như sai ngay từ cái nhìn đầu tiên, các thống kê cho thấy rằng không có giá trị nào trong số chúng "tốt hơn" so với cái khác: ngay cả chiến lược tuyến tính có phân phối đồng đều và sự khác biệt trung bình giữa các giá trị khá giống nhau với cả hai chiến lược .
Chơi với các phạm vi khác nhau cho tôi thấy rằng chiến lược tuyến tính trở nên "lành mạnh" với phạm vi từ 0 đến ushort.MaxValue
với giá trị tối thiểu "hợp lý" là 10,78294704 (đối với ulong
phạm vi giá trị tối thiểu là 3.03518E + 15 ; int
: 353341). Đây là cùng một kết quả của cả hai chiến lược được hiển thị với các tỷ lệ khác nhau:
Biên tập:
Gần đây tôi đã làm cho các thư viện của mình là nguồn mở, hãy xem RandomExtensions.NextDouble
phương thức với xác nhận hoàn chỉnh.