Browse Source

Random: fix RandomSource.Next(x, x+1) #427

Merge branch 'pr-428' (early part)
pull/436/head
Christoph Ruegg 10 years ago
parent
commit
ceb697f92f
  1. 12
      src/Numerics/Numerics.csproj
  2. 9
      src/Numerics/Properties/Resources.Designer.cs
  3. 3
      src/Numerics/Properties/Resources.resx
  4. 119
      src/Numerics/Random/RandomSource.cs
  5. 9
      src/Numerics/Random/SystemRandomSource.cs
  6. 23
      src/UnitTests/Random/RandomTests.cs

12
src/Numerics/Numerics.csproj

@ -167,6 +167,11 @@
<Compile Include="LinearRegression\WeightedRegression.cs" /> <Compile Include="LinearRegression\WeightedRegression.cs" />
<Compile Include="LinearRegression\SimpleRegression.cs" /> <Compile Include="LinearRegression\SimpleRegression.cs" />
<Compile Include="LinearRegression\Util.cs" /> <Compile Include="LinearRegression\Util.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Providers\LinearAlgebra\Acml\AcmlLinearAlgebraProvider.Complex.cs" /> <Compile Include="Providers\LinearAlgebra\Acml\AcmlLinearAlgebraProvider.Complex.cs" />
<Compile Include="Providers\LinearAlgebra\Acml\AcmlLinearAlgebraProvider.Complex32.cs" /> <Compile Include="Providers\LinearAlgebra\Acml\AcmlLinearAlgebraProvider.Complex32.cs" />
<Compile Include="Providers\LinearAlgebra\Acml\AcmlLinearAlgebraProvider.Double.cs" /> <Compile Include="Providers\LinearAlgebra\Acml\AcmlLinearAlgebraProvider.Double.cs" />
@ -428,11 +433,6 @@
<Compile Include="Interpolation\SplineBoundaryCondition.cs" /> <Compile Include="Interpolation\SplineBoundaryCondition.cs" />
<Compile Include="Precision.cs" /> <Compile Include="Precision.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="Random\RandomSource.cs" /> <Compile Include="Random\RandomSource.cs" />
<Compile Include="Random\Mcg31m1.cs" /> <Compile Include="Random\Mcg31m1.cs" />
<Compile Include="Random\Mcg59.cs" /> <Compile Include="Random\Mcg59.cs" />
@ -472,8 +472,8 @@
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="Properties\Resources.resx"> <EmbeddedResource Include="Properties\Resources.resx">
<Generator>PublicResXFileCodeGenerator</Generator> <Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType> <SubType>Designer</SubType>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource> </EmbeddedResource>
</ItemGroup> </ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />

9
src/Numerics/Properties/Resources.Designer.cs

@ -314,6 +314,15 @@ namespace MathNet.Numerics.Properties {
} }
} }
/// <summary>
/// Looks up a localized string similar to In the specified range, the exclusive maximum must be greater than the inclusive minimum..
/// </summary>
public static string ArgumentMaxExclusiveMustBeLargerThanMinInclusive {
get {
return ResourceManager.GetString("ArgumentMaxExclusiveMustBeLargerThanMinInclusive", resourceCulture);
}
}
/// <summary> /// <summary>
/// Looks up a localized string similar to In the specified range, the minimum is greater than maximum.. /// Looks up a localized string similar to In the specified range, the minimum is greater than maximum..
/// </summary> /// </summary>

3
src/Numerics/Properties/Resources.resx

@ -282,6 +282,9 @@
<data name="ArgumentMinValueGreaterThanMaxValue" xml:space="preserve"> <data name="ArgumentMinValueGreaterThanMaxValue" xml:space="preserve">
<value>In the specified range, the minimum is greater than maximum.</value> <value>In the specified range, the minimum is greater than maximum.</value>
</data> </data>
<data name="ArgumentMaxExclusiveMustBeLargerThanMinInclusive" xml:space="preserve">
<value>In the specified range, the exclusive maximum must be greater than the inclusive minimum.</value>
</data>
<data name="ArgumentUpperBoundMustBeLargerThanOrEqualToLowerBound" xml:space="preserve"> <data name="ArgumentUpperBoundMustBeLargerThanOrEqualToLowerBound" xml:space="preserve">
<value>The upper bound must be at least as large as the lower bound.</value> <value>The upper bound must be at least as large as the lower bound.</value>
</data> </data>

119
src/Numerics/Random/RandomSource.cs

@ -3,7 +3,7 @@
// http://numerics.mathdotnet.com // http://numerics.mathdotnet.com
// http://github.com/mathnet/mathnet-numerics // http://github.com/mathnet/mathnet-numerics
// //
// Copyright (c) 2009-2015 Math.NET // Copyright (c) 2009-2016 Math.NET
// //
// Permission is hereby granted, free of charge, to any person // Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation // obtaining a copy of this software and associated documentation
@ -133,28 +133,39 @@ namespace MathNet.Numerics.Random
return DoSampleInteger(); return DoSampleInteger();
} }
} }
else
return DoSampleInteger(); {
return DoSampleInteger();
}
} }
/// <summary> /// <summary>
/// Returns a random number less then a specified maximum. /// Returns a random number less then a specified maximum.
/// </summary> /// </summary>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 1.</param>
/// <returns>A 32-bit signed integer less than <paramref name="maxExclusive"/>.</returns> /// <returns>A 32-bit signed integer less than <paramref name="maxExclusive"/>.</returns>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="maxExclusive"/> is negative. </exception> /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="maxExclusive"/> is zero or negative.</exception>
public sealed override int Next(int maxExclusive) public sealed override int Next(int maxExclusive)
{ {
// Invalid case: Zero and less are not valid use cases.
if (maxExclusive <= 0) if (maxExclusive <= 0)
{ {
throw new ArgumentException(Resources.ArgumentMustBePositive); throw new ArgumentException(Resources.ArgumentMustBePositive);
} }
// Fast case: Only zero is allowed to be returned. No sampling is needed.
if (maxExclusive == 1)
{
return 0;
}
// Simple case: standard range
if (maxExclusive == int.MaxValue) if (maxExclusive == int.MaxValue)
{ {
return Next(); return Next();
} }
// Sample with maxExclusive ≥ 2
if (_threadSafe) if (_threadSafe)
{ {
lock (_lock) lock (_lock)
@ -162,28 +173,39 @@ namespace MathNet.Numerics.Random
return DoSampleInteger(maxExclusive); return DoSampleInteger(maxExclusive);
} }
} }
else
return DoSampleInteger(maxExclusive); {
return DoSampleInteger(maxExclusive);
}
} }
/// <summary> /// <summary>
/// Returns a random number within a specified range. /// Returns a random number within a specified range.
/// </summary> /// </summary>
/// <param name="minInclusive">The inclusive lower bound of the random number returned.</param> /// <param name="minInclusive">The inclusive lower bound of the random number returned.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. <paramref name="maxExclusive"/> must be greater than or equal to <paramref name="minInclusive"/>.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive.</param>
/// <returns> /// <returns>
/// A 32-bit signed integer greater than or equal to <paramref name="minInclusive"/> and less than <paramref name="maxExclusive"/>; that is, the range of return values includes <paramref name="minInclusive"/> but not <paramref name="maxExclusive"/>. If <paramref name="minInclusive"/> equals <paramref name="maxExclusive"/>, <paramref name="minInclusive"/> is returned. /// A 32-bit signed integer greater than or equal to <paramref name="minInclusive"/> and less than <paramref name="maxExclusive"/>; that is, the range of return values includes <paramref name="minInclusive"/> but not <paramref name="maxExclusive"/>. If <paramref name="minInclusive"/> equals <paramref name="maxExclusive"/>, <paramref name="minInclusive"/> is returned.
/// </returns> /// </returns>
/// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="minInclusive"/> is greater than <paramref name="maxExclusive"/>. </exception> /// <exception cref="T:System.ArgumentOutOfRangeException"><paramref name="minInclusive"/> is greater than <paramref name="maxExclusive"/>. </exception>
public sealed override int Next(int minInclusive, int maxExclusive) public sealed override int Next(int minInclusive, int maxExclusive)
{ {
if (minInclusive > maxExclusive) // Invalid case: empty range.
if (minInclusive >= maxExclusive)
{ {
throw new ArgumentException(Resources.ArgumentMinValueGreaterThanMaxValue); throw new ArgumentException(Resources.ArgumentMaxExclusiveMustBeLargerThanMinInclusive);
} }
// Fast case: Only minInclusive is allowed to be returned. No sampling is needed.
if (maxExclusive == minInclusive + 1)
{
return minInclusive;
}
// Simple case: simple range
if (minInclusive == 0) if (minInclusive == 0)
{ {
// Simple case: standard range
if (maxExclusive == int.MaxValue) if (maxExclusive == int.MaxValue)
{ {
return Next(); return Next();
@ -192,6 +214,7 @@ namespace MathNet.Numerics.Random
return Next(maxExclusive); return Next(maxExclusive);
} }
// Sample with maxExclusive ≥ minExclusive + 2
if (_threadSafe) if (_threadSafe)
{ {
lock (_lock) lock (_lock)
@ -199,8 +222,10 @@ namespace MathNet.Numerics.Random
return DoSampleInteger(minInclusive, maxExclusive); return DoSampleInteger(minInclusive, maxExclusive);
} }
} }
else
return DoSampleInteger(minInclusive, maxExclusive); {
return DoSampleInteger(minInclusive, maxExclusive);
}
} }
/// <summary> /// <summary>
@ -243,15 +268,30 @@ namespace MathNet.Numerics.Random
/// Fills an array with random numbers within a specified range. /// Fills an array with random numbers within a specified range.
/// </summary> /// </summary>
/// <param name="values">The array to fill with random values.</param> /// <param name="values">The array to fill with random values.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 1.</param>
public void NextInt32s(int[] values, int maxExclusive) public void NextInt32s(int[] values, int maxExclusive)
{ {
// Invalid case: Zero and less are not valid use cases.
if (maxExclusive <= 0)
{
throw new ArgumentException(Resources.ArgumentMustBePositive);
}
// Fast case: Only zero is allowed to be returned. No sampling is needed.
if (maxExclusive == 1)
{
Array.Clear(values, 0, values.Length);
return;
}
// Simple case: standard range
if (maxExclusive == int.MaxValue) if (maxExclusive == int.MaxValue)
{ {
NextInt32s(values); NextInt32s(values);
return; return;
} }
// Sample with maxExclusive ≥ 2
if (_threadSafe) if (_threadSafe)
{ {
lock (_lock) lock (_lock)
@ -271,27 +311,46 @@ namespace MathNet.Numerics.Random
} }
} }
/// <summary>
/// Returns an array with random 32-bit signed integers within the specified range.
/// </summary>
/// <param name="count">The size of the array to fill.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 1.</param>
public int[] NextInt32s(int count, int maxExclusive)
{
var values = new int[count];
NextInt32s(values, maxExclusive);
return values;
}
/// <summary> /// <summary>
/// Fills an array with random numbers within a specified range. /// Fills an array with random numbers within a specified range.
/// </summary> /// </summary>
/// <param name="values">The array to fill with random values.</param> /// <param name="values">The array to fill with random values.</param>
/// <param name="minInclusive">The inclusive lower bound of the random number returned.</param> /// <param name="minInclusive">The inclusive lower bound of the random number returned.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. <paramref name="maxExclusive"/> must be greater than or equal to <paramref name="minInclusive"/>.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive.</param>
public void NextInt32s(int[] values, int minInclusive, int maxExclusive) public void NextInt32s(int[] values, int minInclusive, int maxExclusive)
{ {
if (minInclusive > maxExclusive) // Invalid case: empty range.
if (minInclusive >= maxExclusive)
{ {
throw new ArgumentException(Resources.ArgumentMinValueGreaterThanMaxValue); throw new ArgumentException(Resources.ArgumentMaxExclusiveMustBeLargerThanMinInclusive);
} }
if (maxExclusive == int.MaxValue && minInclusive == 0) // Fast case: Only minInclusive is allowed to be returned. No sampling is needed.
if (maxExclusive == minInclusive + 1)
{ {
NextInt32s(values); for (var i = 0; i < values.Length; i++)
{
values[i] = minInclusive;
}
return; return;
} }
// Simple case: simple range
if (minInclusive == 0) if (minInclusive == 0)
{ {
// Simple case: standard range
if (maxExclusive == int.MaxValue) if (maxExclusive == int.MaxValue)
{ {
NextInt32s(values); NextInt32s(values);
@ -302,6 +361,7 @@ namespace MathNet.Numerics.Random
return; return;
} }
// Sample with maxExclusive ≥ minExclusive + 2
if (_threadSafe) if (_threadSafe)
{ {
lock (_lock) lock (_lock)
@ -326,7 +386,7 @@ namespace MathNet.Numerics.Random
/// </summary> /// </summary>
/// <param name="count">The size of the array to fill.</param> /// <param name="count">The size of the array to fill.</param>
/// <param name="minInclusive">The inclusive lower bound of the random number returned.</param> /// <param name="minInclusive">The inclusive lower bound of the random number returned.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. <paramref name="maxExclusive"/> must be greater than or equal to <paramref name="minInclusive"/>.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive.</param>
public int[] NextInt32s(int count, int minInclusive, int maxExclusive) public int[] NextInt32s(int count, int minInclusive, int maxExclusive)
{ {
var values = new int[count]; var values = new int[count];
@ -359,7 +419,7 @@ namespace MathNet.Numerics.Random
/// Returns an infinite sequence of random numbers within a specified range. /// Returns an infinite sequence of random numbers within a specified range.
/// </summary> /// </summary>
/// <param name="minInclusive">The inclusive lower bound of the random number returned.</param> /// <param name="minInclusive">The inclusive lower bound of the random number returned.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. <paramref name="maxExclusive"/> must be greater than or equal to <paramref name="minInclusive"/>.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive > minExclusive.</param>
public IEnumerable<int> NextInt32Sequence(int minInclusive, int maxExclusive) public IEnumerable<int> NextInt32Sequence(int minInclusive, int maxExclusive)
{ {
if (minInclusive > maxExclusive) if (minInclusive > maxExclusive)
@ -455,6 +515,13 @@ namespace MathNet.Numerics.Random
/// </summary> /// </summary>
protected virtual int DoSampleInt32WithNBits(int bitCount) protected virtual int DoSampleInt32WithNBits(int bitCount)
{ {
// Fast case: Only 0 is allowed to be returned
// No random call is needed
if (bitCount == 0)
{
return 0;
}
var bytes = new byte[4]; var bytes = new byte[4];
DoSampleBytes(bytes); DoSampleBytes(bytes);
@ -472,6 +539,13 @@ namespace MathNet.Numerics.Random
/// </summary> /// </summary>
protected virtual long DoSampleInt64WithNBits(int bitCount) protected virtual long DoSampleInt64WithNBits(int bitCount)
{ {
// Fast case: Only 0 is allowed to be returned
// No random call is needed
if (bitCount == 0)
{
return 0;
}
var bytes = new byte[8]; var bytes = new byte[8];
DoSampleBytes(bytes); DoSampleBytes(bytes);
@ -486,7 +560,7 @@ namespace MathNet.Numerics.Random
/// <summary> /// <summary>
/// Returns a random 32-bit signed integer within the specified range. /// Returns a random 32-bit signed integer within the specified range.
/// </summary> /// </summary>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 2 (not verified, must be ensured by caller).</param>
protected virtual int DoSampleInteger(int maxExclusive) protected virtual int DoSampleInteger(int maxExclusive)
{ {
// non-biased implementation // non-biased implementation
@ -516,9 +590,10 @@ namespace MathNet.Numerics.Random
/// Returns a random 32-bit signed integer within the specified range. /// Returns a random 32-bit signed integer within the specified range.
/// </summary> /// </summary>
/// <param name="minInclusive">The inclusive lower bound of the random number returned.</param> /// <param name="minInclusive">The inclusive lower bound of the random number returned.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. <paramref name="maxExclusive"/> must be greater than or equal to <paramref name="minInclusive"/>.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ minExclusive + 2 (not verified, must be ensured by caller).</param>
protected virtual int DoSampleInteger(int minInclusive, int maxExclusive) protected virtual int DoSampleInteger(int minInclusive, int maxExclusive)
{ {
// Sample with maxExclusive ≥ 2
return DoSampleInteger(maxExclusive - minInclusive) + minInclusive; return DoSampleInteger(maxExclusive - minInclusive) + minInclusive;
} }
} }

9
src/Numerics/Random/SystemRandomSource.cs

@ -126,6 +126,10 @@ namespace MathNet.Numerics.Random
return _random.Next(); return _random.Next();
} }
/// <summary>
/// Returns a random 32-bit signed integer within the specified range.
/// </summary>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ 2 (not verified, must be ensured by caller).</param>
protected override int DoSampleInteger(int maxExclusive) protected override int DoSampleInteger(int maxExclusive)
{ {
return _random.Next(maxExclusive); return _random.Next(maxExclusive);
@ -135,12 +139,15 @@ namespace MathNet.Numerics.Random
/// Returns a random 32-bit signed integer within the specified range. /// Returns a random 32-bit signed integer within the specified range.
/// </summary> /// </summary>
/// <param name="minInclusive">The inclusive lower bound of the random number returned.</param> /// <param name="minInclusive">The inclusive lower bound of the random number returned.</param>
/// <param name="maxExclusive">The exclusive upper bound of the random number returned. <paramref name="maxExclusive"/> must be greater than or equal to <paramref name="minInclusive"/>.</param> /// <param name="maxExclusive">The exclusive upper bound of the random number returned. Range: maxExclusive ≥ minExclusive + 2 (not verified, must be ensured by caller).</param>
protected override int DoSampleInteger(int minInclusive, int maxExclusive) protected override int DoSampleInteger(int minInclusive, int maxExclusive)
{ {
return _random.Next(minInclusive, maxExclusive); return _random.Next(minInclusive, maxExclusive);
} }
/// <summary>
/// Fills the elements of a specified array of bytes with random numbers in full range, including zero and 255 (<see cref="F:System.Byte.MaxValue"/>).
/// </summary>
protected override void DoSampleBytes(byte[] buffer) protected override void DoSampleBytes(byte[] buffer)
{ {
_random.NextBytes(buffer); _random.NextBytes(buffer);

23
src/UnitTests/Random/RandomTests.cs

@ -78,6 +78,29 @@ namespace MathNet.Numerics.UnitTests.Random
Assert.IsTrue(sum >= (N/2.0) - (.05*N)); Assert.IsTrue(sum >= (N/2.0) - (.05*N));
Assert.IsTrue(sum <= (N/2.0) + (.05*N)); Assert.IsTrue(sum <= (N/2.0) + (.05*N));
var disposable = random as IDisposable;
if (disposable != null)
{
disposable.Dispose();
}
}
/// <summary>
/// Next() result is in boundaries.
/// </summary>
[Test]
public void Boundaries()
{
var random = (System.Random)Activator.CreateInstance(_randomType, new object[] { false });
for (var i = 1; i < N; i++)
{
var j = N;
var next = random.Next(i, j);
Assert.IsTrue(next >= i, string.Format("Value {0} is smaller than lower bound {1}", next, i));
Assert.IsTrue(next < j, string.Format("Value {0} is larger or equal to upper bound {1}", next, j));
}
var disposable = random as IDisposable; var disposable = random as IDisposable;
if (disposable != null) if (disposable != null)
{ {

Loading…
Cancel
Save