.NET 標準庫 Random.NextDouble() 僅返回 [0.0, 1.0) 區間值,實際開發中頻繁需要「指定範圍的 double 隨機數」。本文提供一個線程安全、高效能、語義清晰的擴充方法實現,並解析其數學原理、執行緒安全陷阱與測試驗證方法。
NextDouble(min, max)using System;
public static class RandomExtensions
{
///
/// 返回 [minValue, maxValue) 區間的雙精度隨機數。
///
/// Random 實例(不可為 null)
/// 包含下界
/// 不包含上界
/// 均勻分佈的隨機 double
/// random 為 null
/// minValue >= maxValue
public static double NextDouble(this Random random, double minValue, double maxValue)
{
if (random == null)
throw new ArgumentNullException(nameof(random));
if (minValue >= maxValue)
throw new ArgumentException("minValue 必須小於 maxValue", nameof(minValue));
// 數學原理:[0,1) * (max-min) + min = [min, max)
return random.NextDouble() * (maxValue - minValue) + minValue;
}
}
Random.NextDouble() 返回 [0.0, 1.0)(含 0,不含 1)。(max - min) → [0, max-min)。min → [min, max)。min,永遠取不到 max(除非浮點誤差導致極小機率等於 max,可接受)。| 方案 | 適用場景 | 效能 | 代碼示例 |
|---|---|---|---|
ThreadLocal<Random> | 高併發、無共享狀態 | 極高(無鎖) | ThreadLocal<Random> local = ThreadLocal.withInitial(Random::new); local.Value.NextDouble(1,5); |
lock (random) | 低併發、legacy 代碼 | 中(鎖爭用) | lock(rng) { return rng.NextDouble(min,max); } |
Random.Shared (.NET 6+) | 全域單例、線程安全 | 高(內部無鎖實現) | Random.Shared.NextDouble(min,max); |
ThreadStatic Random | 老專案相容 | 高 | [ThreadStatic] static Random tls; if(tls==null) tls=new Random(); |
建議:.NET 6+ 直接用 Random.Shared;舊版本用 ThreadLocal<Random>。**嚴禁多線程直接共用單一 Random 實例**(內部種子更新非原子,會導致返回 0 或效能崩塌)。
[TestClass]
public class RandomExtensionsTests
{
[TestMethod]
public void NextDouble_Range_UniformDistribution()
{
const int N = 1_000_000;
var rng = new Random(42); // 固定種子可復現
double min = 1.5, max = 2.5;
double sum = 0;
int outOfRange = 0;
for (int i = 0; i < N; i++)
{
double v = rng.NextDouble(min, max);
if (v < min || v >= max) outOfRange++;
sum += v;
}
Assert.AreEqual(0, outOfRange, "所有值必須在 [min, max) 區間內");
double mean = sum / N;
double expectedMean = (min + max) / 2.0; // 2.0
Assert.IsTrue(Math.Abs(mean - expectedMean) < 0.005, $"均值應接近 {expectedMean},實際 {mean}");
}
[TestMethod]
[ExpectedException(typeof(ArgumentNullException))]
public void NextDouble_NullRandom_Throws() => ((Random)null).NextDouble(0, 1);
[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void NextDouble_MinGtMax_Throws() => new Random().NextDouble(5, 1);
}
RandomNumberGenerator若用於 Token、Session ID、加密鹽值,**必須用密碼學安全隨機源**:
using System.Security.Cryptography;
public static class SecureRandomExtensions
{
public static double NextDouble(double min, double max)
{
if (min >= max) throw new ArgumentException();
// 取 8 字節 → ulong → [0, 2^64-1] → 除以 2^64 得 [0,1)
Span bytes = stackalloc byte[8];
RandomNumberGenerator.Fill(bytes);
ulong rand = BinaryPrimitives.ReadUInt64LittleEndian(bytes);
double unit = rand / (double)ulong.MaxValue; // 約 [0,1)
return unit * (max - min) + min;
}
}
NextDouble(min, max):語義清晰、可測試、可復用。Random.Shared,舊版用 ThreadLocal<Random>,**禁用共享實例**。RandomNumberGenerator 實現加密級隨機。將擴充方法納入團隊共用庫,下次需要「1.5 到 2.5 之間的隨機延遲」、「模擬溫度傳感器 20.0~30.0 度」時,一行代碼搞定——規範、安全、可維護。