未验证 提交 ee32ee22 编写于 作者: S Stephen Toub 提交者: GitHub

Fix Random.Next(int, int) on 64-bit when max-min overflows int.MaxValue (#50922)

* Fix Random.Next(int, int) on 64-bit when max-min overflow int

The fix is a missing cast.  Everything else is asserts and test changes.

* Update Random.cs
上级 f0f3b845
......@@ -112,7 +112,7 @@ public override int Next(int maxValue)
public override int Next(int minValue, int maxValue)
{
ulong exclusiveRange = (ulong)(maxValue - minValue);
ulong exclusiveRange = (ulong)((long)maxValue - minValue);
if (exclusiveRange > 1)
{
......
......@@ -52,7 +52,12 @@ protected private Random(bool isThreadSafeRandom)
/// <summary>Returns a non-negative random integer.</summary>
/// <returns>A 32-bit signed integer that is greater than or equal to 0 and less than <see cref="int.MaxValue"/>.</returns>
public virtual int Next() => _impl.Next();
public virtual int Next()
{
int result = _impl.Next();
AssertInRange(result, 0, int.MaxValue);
return result;
}
/// <summary>Returns a non-negative random integer that is less than the specified maximum.</summary>
/// <param name="maxValue">The exclusive upper bound of the random number to be generated. <paramref name="maxValue"/> must be greater than or equal to 0.</param>
......@@ -68,7 +73,9 @@ public virtual int Next(int maxValue)
ThrowMaxValueMustBeNonNegative();
}
return _impl.Next(maxValue);
int result = _impl.Next(maxValue);
AssertInRange(result, 0, maxValue);
return result;
}
/// <summary>Returns a random integer that is within a specified range.</summary>
......@@ -86,12 +93,19 @@ public virtual int Next(int minValue, int maxValue)
ThrowMinMaxValueSwapped();
}
return _impl.Next(minValue, maxValue);
int result = _impl.Next(minValue, maxValue);
AssertInRange(result, minValue, maxValue);
return result;
}
/// <summary>Returns a non-negative random integer.</summary>
/// <returns>A 64-bit signed integer that is greater than or equal to 0 and less than <see cref="long.MaxValue"/>.</returns>
public virtual long NextInt64() => _impl.NextInt64();
public virtual long NextInt64()
{
long result = _impl.NextInt64();
AssertInRange(result, 0, long.MaxValue);
return result;
}
/// <summary>Returns a non-negative random integer that is less than the specified maximum.</summary>
/// <param name="maxValue">The exclusive upper bound of the random number to be generated. <paramref name="maxValue"/> must be greater than or equal to 0.</param>
......@@ -107,7 +121,9 @@ public virtual long NextInt64(long maxValue)
ThrowMaxValueMustBeNonNegative();
}
return _impl.NextInt64(maxValue);
long result = _impl.NextInt64(maxValue);
AssertInRange(result, 0, maxValue);
return result;
}
/// <summary>Returns a random integer that is within a specified range.</summary>
......@@ -125,16 +141,28 @@ public virtual long NextInt64(long minValue, long maxValue)
ThrowMinMaxValueSwapped();
}
return _impl.NextInt64(minValue, maxValue);
long result = _impl.NextInt64(minValue, maxValue);
AssertInRange(result, minValue, maxValue);
return result;
}
/// <summary>Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0.</summary>
/// <returns>A single-precision floating point number that is greater than or equal to 0.0, and less than 1.0.</returns>
public virtual float NextSingle() => _impl.NextSingle();
public virtual float NextSingle()
{
float result = _impl.NextSingle();
AssertInRange(result);
return result;
}
/// <summary>Returns a random floating-point number that is greater than or equal to 0.0, and less than 1.0.</summary>
/// <returns>A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.</returns>
public virtual double NextDouble() => _impl.NextDouble();
public virtual double NextDouble()
{
double result = _impl.NextDouble();
AssertInRange(result);
return result;
}
/// <summary>Fills the elements of a specified array of bytes with random numbers.</summary>
/// <param name="buffer">The array to be filled with random numbers.</param>
......@@ -155,7 +183,12 @@ public virtual void NextBytes(byte[] buffer)
/// <summary>Returns a random floating-point number between 0.0 and 1.0.</summary>
/// <returns>A double-precision floating point number that is greater than or equal to 0.0, and less than 1.0.</returns>
protected virtual double Sample() => _impl.Sample();
protected virtual double Sample()
{
double result = _impl.Sample();
AssertInRange(result);
return result;
}
private static void ThrowMaxValueMustBeNonNegative() =>
throw new ArgumentOutOfRangeException("maxValue", SR.Format(SR.ArgumentOutOfRange_NeedNonNegNum, "maxValue"));
......@@ -163,6 +196,23 @@ public virtual void NextBytes(byte[] buffer)
private static void ThrowMinMaxValueSwapped() =>
throw new ArgumentOutOfRangeException("minValue", SR.Format(SR.Argument_MinMaxValue, "minValue", "maxValue"));
[Conditional("DEBUG")]
private static void AssertInRange(long result, long minInclusive, long maxExclusive)
{
if (maxExclusive > minInclusive)
{
Debug.Assert(result >= minInclusive && result < maxExclusive, $"Expected {minInclusive} <= {result} < {maxExclusive}");
}
else
{
Debug.Assert(result == minInclusive, $"Expected {minInclusive} == {result}");
}
}
[Conditional("DEBUG")]
private static void AssertInRange(double result) =>
Debug.Assert(result >= 0.0 && result < 1.0f, $"Expected 0.0 <= {result} < 1.0");
/// <summary>Random implementation that delegates all calls to a ThreadStatic Impl instance.</summary>
private sealed class ThreadSafeRandom : Random
{
......@@ -189,7 +239,12 @@ private sealed class ThreadSafeRandom : Random
[MethodImpl(MethodImplOptions.NoInlining)]
private static XoshiroImpl Create() => t_random = new();
public override int Next() => LocalRandom.Next();
public override int Next()
{
int result = LocalRandom.Next();
AssertInRange(result, 0, int.MaxValue);
return result;
}
public override int Next(int maxValue)
{
......@@ -198,7 +253,9 @@ public override int Next(int maxValue)
ThrowMaxValueMustBeNonNegative();
}
return LocalRandom.Next(maxValue);
int result = LocalRandom.Next(maxValue);
AssertInRange(result, 0, maxValue);
return result;
}
public override int Next(int minValue, int maxValue)
......@@ -208,10 +265,17 @@ public override int Next(int minValue, int maxValue)
ThrowMinMaxValueSwapped();
}
return LocalRandom.Next(minValue, maxValue);
int result = LocalRandom.Next(minValue, maxValue);
AssertInRange(result, minValue, maxValue);
return result;
}
public override long NextInt64() => LocalRandom.NextInt64();
public override long NextInt64()
{
long result = LocalRandom.NextInt64();
AssertInRange(result, 0, long.MaxValue);
return result;
}
public override long NextInt64(long maxValue)
{
......@@ -220,7 +284,9 @@ public override long NextInt64(long maxValue)
ThrowMaxValueMustBeNonNegative();
}
return LocalRandom.NextInt64(maxValue);
long result = LocalRandom.NextInt64(maxValue);
AssertInRange(result, 0, maxValue);
return result;
}
public override long NextInt64(long minValue, long maxValue)
......@@ -230,12 +296,24 @@ public override long NextInt64(long minValue, long maxValue)
ThrowMinMaxValueSwapped();
}
return LocalRandom.NextInt64(minValue, maxValue);
long result = LocalRandom.NextInt64(minValue, maxValue);
AssertInRange(result, minValue, maxValue);
return result;
}
public override float NextSingle() => LocalRandom.NextSingle();
public override float NextSingle()
{
float result = LocalRandom.NextSingle();
AssertInRange(result);
return result;
}
public override double NextDouble() => LocalRandom.NextDouble();
public override double NextDouble()
{
double result = LocalRandom.NextDouble();
AssertInRange(result);
return result;
}
public override void NextBytes(byte[] buffer)
{
......
......@@ -130,6 +130,31 @@ public void Next_IntInt_AllValuesWithinSmallRangeHit(bool derived, bool seeded)
Assert.DoesNotContain(44, hs);
}
public static IEnumerable<object[]> Next_IntInt_Next_IntInt_AllValuesAreWithinRange_MemberData() =>
from derived in new[] { false, true }
from seeded in new[] { false, true }
from (int min, int max) pair in new[]
{
(1, 2),
(-10, -3),
(0, int.MaxValue),
(-1, int.MaxValue),
(int.MinValue, 0),
(int.MinValue, int.MaxValue),
}
select new object[] { derived, seeded, pair.min, pair.max };
[Theory]
[MemberData(nameof(Next_IntInt_Next_IntInt_AllValuesAreWithinRange_MemberData))]
public void Next_IntInt_Next_IntInt_AllValuesAreWithinRange(bool derived, bool seeded, int min, int max)
{
Random r = Create(derived, seeded);
for (int i = 0; i < 100; i++)
{
Assert.InRange(r.Next(min, max), min, max - 1);
}
}
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
......@@ -178,6 +203,31 @@ public void Next_LongLong_AllValuesWithinSmallRangeHit(bool derived, bool seeded
Assert.DoesNotContain(44L, hs);
}
public static IEnumerable<object[]> Next_LongLong_Next_IntInt_AllValuesAreWithinRange_MemberData() =>
from derived in new[] { false, true }
from seeded in new[] { false, true }
from (long min, long max) pair in new[]
{
(1L, 2L),
(0L, long.MaxValue),
(2147483648, 2147483658),
(-1L, long.MaxValue),
(long.MinValue, 0L),
(long.MinValue, long.MaxValue),
}
select new object[] { derived, seeded, pair.min, pair.max };
[Theory]
[MemberData(nameof(Next_LongLong_Next_IntInt_AllValuesAreWithinRange_MemberData))]
public void Next_LongLong_Next_IntInt_AllValuesAreWithinRange(bool derived, bool seeded, long min, long max)
{
Random r = Create(derived, seeded);
for (int i = 0; i < 100; i++)
{
Assert.InRange(r.NextInt64(min, max), min, max - 1);
}
}
[Theory]
[InlineData(false)]
[InlineData(true)]
......@@ -502,7 +552,7 @@ public void Xoshiro_AlgorithmBehavesAsExpected()
Assert.Equal(12, randOuter.Next(0, 42));
Assert.Equal(7234, randOuter.Next(42, 12345));
Assert.Equal(2147483642, randOuter.Next(int.MaxValue - 5, int.MaxValue));
Assert.Equal(1981894504, randOuter.Next(int.MinValue, int.MaxValue));
Assert.Equal(-1236260882, randOuter.Next(int.MinValue, int.MaxValue));
Assert.Equal(3644728249650840822, randOuter.NextInt64());
Assert.Equal(2809750975933744783, randOuter.NextInt64());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册