using System;
namespace Amphora.UnitTests
{
public class RandomDrawsTests : UnitTestSharp.TestFixture
{
///
/// Hands out the given draws in order. A 32-bit draw is the low half of the next one.
///
public struct ScriptedGenerator : IRandomGenerator
{
public ulong[] Draws;
public int Taken;
public ScriptedGenerator(params ulong[] draws)
{
Draws = draws;
Taken = 0;
}
public ulong NextUInt64()
{
ulong returnMe = Draws[Taken];
Taken++;
return returnMe;
}
public uint NextUInt32()
{
return (uint)NextUInt64();
}
}
public static ScriptedGenerator Repeating(ulong draw, int count)
{
var draws = new ulong[count];
for (int i = 0; i < count; i++)
{
draws[i] = draw;
}
return new ScriptedGenerator(draws);
}
///
/// Returns the 64-bit draw that a bounded draw over values turns into .
///
public static ulong DrawFor(ulong index, ulong count)
{
// Aims halfway into the index's slot, (index * 2^64 + 2^63) / count, so no threshold rejects it.
ulong quotient = 0;
ulong remainder = 0;
for (int bit = 127; bit >= 0; bit--)
{
ulong word = index;
if (bit < 64)
{
word = 1UL << 63;
}
remainder = (remainder << 1) | ((word >> (bit & 63)) & 1);
quotient <<= 1;
if (remainder >= count)
{
remainder -= count;
quotient |= 1;
}
}
return quotient;
}
///
/// Returns the 32-bit draw that a bounded draw over values turns into .
///
public static ulong DrawFor32(uint index, uint count)
{
return ((2UL * index + 1) << 31) / count;
}
public static double AwayFromZero(double value)
{
return BitConverter.Int64BitsToDouble(BitConverter.DoubleToInt64Bits(value) + 1);
}
public static double ExactPowerOfTwo(int exponent)
{
if (exponent >= -1022)
{
return BitConverter.Int64BitsToDouble((long)(exponent + 1023) << 52);
}
return BitConverter.Int64BitsToDouble(1L << (exponent + 1074));
}
///
/// Draws a double in the range the plain way, by dividing by the gap and flooring. It is the definition the
/// integer version has to agree with, draw for draw.
///
public static double DrawByDivision(ref GeneratorType generator, double minimum,
double maximumExclusive)
where GeneratorType : struct, IRandomGenerator
{
if (Math.Abs(minimum) <= Math.Abs(maximumExclusive))
{
double gamma = RandomDraws.GapBelow(maximumExclusive);
long top = (long)(maximumExclusive / gamma);
double scaledMinimum = minimum / gamma;
long bottom = (long)Math.Floor(scaledMinimum);
if (scaledMinimum == 0 && minimum < 0)
{
bottom = -1;
}
ulong count = (ulong)(top - bottom);
ulong step = 1 + generator.NextUInt64(count);
return step == count ? minimum : (top - (long)step) * gamma;
}
else
{
double gamma = RandomDraws.GapBelow(-minimum);
long bottom = (long)(minimum / gamma);
double scaledMaximum = maximumExclusive / gamma;
long top = (long)Math.Ceiling(scaledMaximum);
if (scaledMaximum == 0 && maximumExclusive > 0)
{
top = 1;
}
return (bottom + (long)generator.NextUInt64((ulong)(top - bottom))) * gamma;
}
}
public static float ExactFloatPowerOfTwo(int exponent)
{
return (float)ExactPowerOfTwo(exponent);
}
public static int BitsOf(float value)
{
return BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
}
///
/// Draws a float in the range the plain way, as DrawByDivision does for doubles.
///
public static float DrawFloatByDivision(ref GeneratorType generator, float minimum,
float maximumExclusive)
where GeneratorType : struct, IRandomGenerator
{
if (Math.Abs(minimum) <= Math.Abs(maximumExclusive))
{
float gamma = RandomDraws.GapBelow(maximumExclusive);
int top = (int)(maximumExclusive / gamma);
int bottom = (int)Math.Floor((double)minimum / gamma);
uint count = (uint)(top - bottom);
uint step = 1 + generator.NextUInt32(count);
return step == count ? minimum : (top - (int)step) * gamma;
}
else
{
float gamma = RandomDraws.GapBelow(-minimum);
int bottom = (int)(minimum / gamma);
int top = (int)Math.Ceiling((double)maximumExclusive / gamma);
return (bottom + (int)generator.NextUInt32((uint)(top - bottom))) * gamma;
}
}
public static float AwayFromZero(float value)
{
int bits = BitConverter.ToInt32(BitConverter.GetBytes(value), 0);
return BitConverter.ToSingle(BitConverter.GetBytes(bits + 1), 0);
}
public class MultiplyHighTests : UnitTestSharp.TestFixture
{
public void ASmallProductIsAllInTheLowHalf()
{
CheckEqual(0UL, RandomDraws.MultiplyHigh(6, 7, out ulong low));
CheckEqual(42UL, low);
}
public void ZeroTimesTheTopIsZero()
{
CheckEqual(0UL, RandomDraws.MultiplyHigh(0, ulong.MaxValue, out ulong low));
CheckEqual(0UL, low);
}
public void TheTopTimesTwoCarriesOneIntoTheHighHalf()
{
CheckEqual(1UL, RandomDraws.MultiplyHigh(ulong.MaxValue, 2, out ulong low));
CheckEqual(0xfffffffffffffffeUL, low);
}
public void TheTopSquaredFillsBothHalves()
{
CheckEqual(0xfffffffffffffffeUL,
RandomDraws.MultiplyHigh(ulong.MaxValue, ulong.MaxValue, out ulong low));
CheckEqual(1UL, low);
}
public void ACarryOutOfTheMiddleWordsReachesTheHighHalf()
{
CheckEqual(0xffffffff00000000UL,
RandomDraws.MultiplyHigh(0xffffffffffffffffUL, 0xffffffff00000001UL, out ulong low));
CheckEqual(0x00000000ffffffffUL, low);
}
public void MixedWordsMatchAWideMultiply()
{
CheckEqual(0x0121fa00ad77d742UL,
RandomDraws.MultiplyHigh(0x0123456789abcdefUL, 0xfedcba9876543210UL, out ulong low));
CheckEqual(0x2236d88fe5618cf0UL, low);
}
}
public class GapBelowTests : UnitTestSharp.TestFixture
{
public void OneHasTheUnitIntervalsStep()
{
CheckEqual(RandomDraws.DoubleStep, RandomDraws.GapBelow(1.0));
}
public void APowerOfTwoHasHalfTheGapAboveIt()
{
CheckEqual(AwayFromZero(2.0) - 2.0, 2 * RandomDraws.GapBelow(2.0));
}
public void TheSmallestSubnormalHasAGapOfItself()
{
CheckEqual(double.Epsilon, RandomDraws.GapBelow(double.Epsilon));
}
public void TheLargestDoubleHasAGapOf2To971()
{
CheckEqual(Math.Pow(2, 971), RandomDraws.GapBelow(double.MaxValue));
}
public void OneAsAFloatHasTheUnitIntervalsStep()
{
CheckEqual(RandomDraws.FloatStep, RandomDraws.GapBelow(1.0f));
}
public void TheSmallestSubnormalFloatHasAGapOfItself()
{
CheckEqual(float.Epsilon, RandomDraws.GapBelow(float.Epsilon));
}
public void TheLargestFloatHasAGapOf2To104()
{
CheckEqual((float)Math.Pow(2, 104), RandomDraws.GapBelow(float.MaxValue));
}
}
public class NextUInt32Tests : UnitTestSharp.TestFixture
{
public void IsTheUpperHalfOfTheDrawTimesTheBound()
{
var generator = new ScriptedGenerator(0x80000001UL);
CheckEqual(5U, generator.NextUInt32(10));
}
public void KeepsADrawBelowTheBoundButNotBelowTheThreshold()
{
var generator = new ScriptedGenerator(0x33333334UL);
CheckEqual(2U, generator.NextUInt32(10));
CheckEqual(1, generator.Taken);
}
public void RejectsADrawBelowTheThresholdAndDrawsAgain()
{
var generator = new ScriptedGenerator(0, 0x80000001UL);
CheckEqual(5U, generator.NextUInt32(10));
CheckEqual(2, generator.Taken);
}
public void ABoundOfOneAlwaysGivesZero()
{
var generator = new ScriptedGenerator(uint.MaxValue);
CheckEqual(0U, generator.NextUInt32(1));
}
public void TheTopBoundCanGiveItsLargestValue()
{
var generator = new ScriptedGenerator(uint.MaxValue);
CheckEqual(uint.MaxValue - 1, generator.NextUInt32(uint.MaxValue));
}
public void SucceedsOnTheLastDrawItMayTake()
{
ScriptedGenerator generator = Repeating(0, RandomDraws.MaximumRejections);
generator.Draws[RandomDraws.MaximumRejections - 1] = 0x80000001UL;
CheckEqual(5U, generator.NextUInt32(10));
}
public void ThrowsOnceEveryDrawItMayTakeIsRejected()
{
ScriptedGenerator generator = Repeating(0, RandomDraws.MaximumRejections + 1);
CheckThrow(typeof(InvalidOperationException));
generator.NextUInt32(10);
}
public void RejectsAZeroBound()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextUInt32(0);
}
}
public class NextUInt64Tests : UnitTestSharp.TestFixture
{
public void IsTheUpperHalfOfTheDrawTimesTheBound()
{
var generator = new ScriptedGenerator(0x8000000000000001UL);
CheckEqual(5UL, generator.NextUInt64(10));
}
public void KeepsADrawBelowTheBoundButNotBelowTheThreshold()
{
var generator = new ScriptedGenerator(0x3333333333333334UL);
CheckEqual(2UL, generator.NextUInt64(10));
CheckEqual(1, generator.Taken);
}
public void RejectsADrawBelowTheThresholdAndDrawsAgain()
{
var generator = new ScriptedGenerator(0, 0x8000000000000001UL);
CheckEqual(5UL, generator.NextUInt64(10));
CheckEqual(2, generator.Taken);
}
public void ABoundOfOneAlwaysGivesZero()
{
var generator = new ScriptedGenerator(ulong.MaxValue);
CheckEqual(0UL, generator.NextUInt64(1));
}
public void TheTopBoundCanGiveItsLargestValue()
{
var generator = new ScriptedGenerator(ulong.MaxValue);
CheckEqual(ulong.MaxValue - 1, generator.NextUInt64(ulong.MaxValue));
}
public void SucceedsOnTheLastDrawItMayTake()
{
ScriptedGenerator generator = Repeating(0, RandomDraws.MaximumRejections);
generator.Draws[RandomDraws.MaximumRejections - 1] = 0x8000000000000001UL;
CheckEqual(5UL, generator.NextUInt64(10));
}
public void ThrowsOnceEveryDrawItMayTakeIsRejected()
{
ScriptedGenerator generator = Repeating(0, RandomDraws.MaximumRejections + 1);
CheckThrow(typeof(InvalidOperationException));
generator.NextUInt64(10);
}
public void RejectsAZeroBound()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextUInt64(0);
}
public void ABoundJustUnder2To32GivesTheUpperHalfOfTheWideProduct()
{
var generator = new ScriptedGenerator(0x8000000000000001UL, ulong.MaxValue, 0x0123456789ABCDEFUL);
CheckEqual(0x7FFFFFFFUL, generator.NextUInt64(0xFFFFFFFFUL));
CheckEqual(0xFFFFFFFEUL, generator.NextUInt64(0xFFFFFFFFUL));
CheckEqual(0x1234567UL, generator.NextUInt64(0xFFFFFFFFUL));
}
public void ABoundOf2To32GivesTheUpperHalfOfTheWideProduct()
{
var generator = new ScriptedGenerator(0x8000000000000001UL, ulong.MaxValue);
CheckEqual(0x80000000UL, generator.NextUInt64(0x100000000UL));
CheckEqual(0xFFFFFFFFUL, generator.NextUInt64(0x100000000UL));
}
public void ABoundJustOver2To32GivesTheUpperHalfOfTheWideProduct()
{
var generator = new ScriptedGenerator(0x0123456789ABCDEFUL);
CheckEqual(0x1234567UL, generator.NextUInt64(0x100000001UL));
}
public void ABoundOf10To12GivesTheUpperHalfOfTheWideProduct()
{
var generator = new ScriptedGenerator(0xFEDCBA9876543210UL);
CheckEqual(995555555555UL, generator.NextUInt64(1000000000000UL));
}
public void ANarrowBoundRejectsOnTheLowHalfOfTheWideProduct()
{
var generator = new ScriptedGenerator(0, 0x8000000000000001UL);
CheckEqual(0x7FFFFFFFUL, generator.NextUInt64(0xFFFFFFFFUL));
CheckEqual(2, generator.Taken);
}
}
public class PowerOfTwoTests : UnitTestSharp.TestFixture
{
public void IsExactForEveryExponentItCovers()
{
for (int exponent = -1024; exponent <= 1023; exponent++)
{
CheckEqual(ExactPowerOfTwo(exponent), RandomDraws.PowerOfTwo(exponent));
}
}
}
public class NextIntTests : UnitTestSharp.TestFixture
{
public void OffsetsTheDrawByTheMinimum()
{
var generator = new ScriptedGenerator(0x80000001UL);
CheckEqual(-5, generator.NextInt(minimum: -10, maximumExclusive: 0));
}
public void SpansTheWholeIntRangeWithoutOverflowing()
{
var generator = new ScriptedGenerator(uint.MaxValue);
CheckEqual(int.MaxValue - 1, generator.NextInt(minimum: int.MinValue, maximumExclusive: int.MaxValue));
}
public void RejectsAnEmptyRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextInt(minimum: 3, maximumExclusive: 3);
}
public void RejectsAReversedRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextInt(minimum: 4, maximumExclusive: 3);
}
public void MaximumAlone_StartsTheRangeAtZero()
{
var generator = new ScriptedGenerator(0x80000001UL);
CheckEqual(5, generator.NextInt(10));
}
public void MaximumAlone_RejectsZero()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextInt(0);
}
}
public class NextLongTests : UnitTestSharp.TestFixture
{
public void OffsetsTheDrawByTheMinimum()
{
var generator = new ScriptedGenerator(0x8000000000000001UL);
CheckEqual(-5L, generator.NextLong(minimum: -10, maximumExclusive: 0));
}
public void SpansTheWholeLongRangeWithoutOverflowing()
{
var generator = new ScriptedGenerator(ulong.MaxValue);
CheckEqual(long.MaxValue - 1,
generator.NextLong(minimum: long.MinValue, maximumExclusive: long.MaxValue));
}
public void RejectsAnEmptyRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextLong(minimum: 3, maximumExclusive: 3);
}
public void RejectsAReversedRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextLong(minimum: 4, maximumExclusive: 3);
}
public void MaximumAlone_StartsTheRangeAtZero()
{
var generator = new ScriptedGenerator(0x8000000000000001UL);
CheckEqual(5L, generator.NextLong(10));
}
public void MaximumAlone_RejectsZero()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextLong(0);
}
}
public class NextDoubleTests : UnitTestSharp.TestFixture
{
public void AZeroDrawGivesZero()
{
var generator = new ScriptedGenerator(0);
CheckEqual(0.0, generator.NextDouble());
}
public void TheTopDrawGivesOneLessAStep()
{
var generator = new ScriptedGenerator(ulong.MaxValue);
CheckEqual(1.0 - RandomDraws.DoubleStep, generator.NextDouble());
}
public void ReadsOnlyTheTop53Bits()
{
var generator = new ScriptedGenerator((1UL << 11) - 1, 1UL << 11);
CheckEqual(0.0, generator.NextDouble());
CheckEqual(RandomDraws.DoubleStep, generator.NextDouble());
}
public void MatchesTheWholeValueScaledByTheStep()
{
var generator = new Xoshiro256PlusPlus(3);
var copy = generator;
for (int i = 0; i < 1000; i++)
{
CheckEqual((long)(copy.NextUInt64() >> 11) * RandomDraws.DoubleStep, generator.NextDouble());
}
}
public void OneMinusTheSmallestStepIsExact()
{
var generator = new ScriptedGenerator(ulong.MaxValue);
CheckEqual(RandomDraws.DoubleStep, 1.0 - generator.NextDouble());
}
}
public class NextDoubleRangeTests : UnitTestSharp.TestFixture
{
public static double Draw(double minimum, double maximumExclusive, ulong index, ulong count)
{
var generator = new ScriptedGenerator(DrawFor(index, count));
return generator.NextDouble(minimum, maximumExclusive);
}
public void TheUnitInterval_StepsDownFromJustBelowOne()
{
CheckEqual(1.0 - RandomDraws.DoubleStep, Draw(0, 1, index: 0, count: 1UL << 53));
CheckEqual(1.0 - 2 * RandomDraws.DoubleStep, Draw(0, 1, index: 1, count: 1UL << 53));
}
public void TheUnitInterval_EndsOnTheMinimum()
{
CheckEqual(0.0, Draw(0, 1, index: (1UL << 53) - 1, count: 1UL << 53));
}
public void ARangeCrossingZero_UsesTheGapOfTheFartherEnd()
{
ulong count = (1UL << 53) + (1UL << 52);
CheckEqual(0.0, Draw(-0.5, 1, index: (1UL << 53) - 1, count));
CheckEqual(-RandomDraws.DoubleStep, Draw(-0.5, 1, index: 1UL << 53, count));
CheckEqual(-0.5, Draw(-0.5, 1, index: count - 1, count));
}
public void AMinimumBetweenGridPoints_TakesTheLastPlace()
{
ulong count = (1UL << 53) + 1;
CheckEqual(0.0, Draw(-1e-300, 1, index: count - 2, count));
CheckEqual(-1e-300, Draw(-1e-300, 1, index: count - 1, count));
}
public void AFartherMinimum_StepsUpFromIt()
{
ulong count = 3UL << 52;
CheckEqual(-2.0, Draw(-2, 1, index: 0, count));
CheckEqual(-2.0 + 2 * RandomDraws.DoubleStep, Draw(-2, 1, index: 1, count));
CheckEqual(1.0 - 2 * RandomDraws.DoubleStep, Draw(-2, 1, index: count - 1, count));
}
public void AMaximumBetweenGridPoints_StopsAtTheGridPointBelowIt()
{
CheckEqual(0.0, Draw(-1, 1e-300, index: 1UL << 53, count: (1UL << 53) + 1));
}
public void AnOddCountPast2To53_StaysBelowTheMaximum()
{
double maximum = 1.0 - 3 * RandomDraws.DoubleStep;
ulong count = (1UL << 54) - 3;
CheckEqual(1.0 - 4 * RandomDraws.DoubleStep, Draw(-1, maximum, index: count - 1, count));
}
public void TheWholeDoubleRange_NeitherOverflowsNorReachesTheMaximum()
{
ulong count = (1UL << 54) - 2;
CheckEqual(double.MaxValue - Math.Pow(2, 971),
Draw(-double.MaxValue, double.MaxValue, index: 0, count));
CheckEqual(-double.MaxValue, Draw(-double.MaxValue, double.MaxValue, index: count - 1, count));
}
public void SubnormalEnds_StepByTheSmallestSubnormal()
{
double epsilon = double.Epsilon;
CheckEqual(3 * epsilon, Draw(epsilon, 4 * epsilon, index: 0, count: 3));
CheckEqual(2 * epsilon, Draw(epsilon, 4 * epsilon, index: 1, count: 3));
CheckEqual(epsilon, Draw(epsilon, 4 * epsilon, index: 2, count: 3));
}
public void ARangeOfOneDouble_GivesTheMinimum()
{
CheckEqual(1.0, Draw(1, AwayFromZero(1.0), index: 0, count: 1));
}
public void AMinimumTooSmallToScale_StillLeavesZeroReachable()
{
ulong count = 1UL << 53;
CheckEqual(0.0, Draw(-double.Epsilon, double.MaxValue, index: count - 2, count));
CheckEqual(-double.Epsilon, Draw(-double.Epsilon, double.MaxValue, index: count - 1, count));
}
public void AMaximumTooSmallToScale_StillLeavesZeroReachable()
{
CheckEqual(0.0, Draw(-double.MaxValue, double.Epsilon, index: (1UL << 53) - 1, count: 1UL << 53));
}
public void ANegativeRange_StepsUpFromTheMinimum()
{
CheckEqual(-2.0, Draw(-2, -1, index: 0, count: 1UL << 52));
CheckEqual(AwayFromZero(-1.0), Draw(-2, -1, index: (1UL << 52) - 1, count: 1UL << 52));
}
public void RejectsANotANumberMinimum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextDouble(double.NaN, 1);
}
public void RejectsAnInfiniteMinimum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextDouble(double.NegativeInfinity, 1);
}
public void RejectsANotANumberMaximum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextDouble(0, double.NaN);
}
public void RejectsAnInfiniteMaximum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextDouble(0, double.PositiveInfinity);
}
public void RejectsAnEmptyRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextDouble(1, 1);
}
public void RejectsAReversedRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextDouble(2, 1);
}
public void MatchesTheDivisionDefinitionDrawForDraw()
{
double belowThreshold = ExactPowerOfTwo(RandomDraws.MinimumFastExponent - 1024);
double[][] ranges =
{
new[] { 0.0, 1.0 }, new[] { -1.0, 1.0 }, new[] { -3.5, 7.25 }, new[] { -1.0, 0.0 },
new[] { 1.0, 2.0 }, new[] { 0.5, 1.0 }, new[] { -0.5, 1.0 }, new[] { -2.0, 1.0 },
new[] { -2.0, -1.0 }, new[] { -1.0, -0.5 }, new[] { 100.0, 200.0 }, new[] { -1e300, 1e308 },
new[] { -double.MaxValue, double.MaxValue }, new[] { -1e-300, 1.0 }, new[] { -1.0, 1e-300 },
new[] { 0.0, 1e-300 }, new[] { -4.0, 4.0 }, new[] { -8.0, 4.0 }, new[] { 4.0, 8.0 },
new[] { 0.0, 4.0 }, new[] { -4.0, 0.0 }, new[] { -1e-300, 1e300 }, new[] { -1e300, 1e-300 },
new[] { 3.0, 3.0000000000000004 }, new[] { double.Epsilon, 1.0 }, new[] { 1e-310, 2e-310 },
new[] { 0.0, belowThreshold }, new[] { 0.0, 2 * belowThreshold },
new[] { -belowThreshold, 2 * belowThreshold }, new[] { -2 * belowThreshold, belowThreshold },
new[] { -2 * belowThreshold, 0.0 }, new[] { -belowThreshold, 0.0 },
};
foreach (double[] range in ranges)
{
var expected = new Xoshiro256PlusPlus(7);
var actual = new Xoshiro256PlusPlus(7);
for (int draw = 0; draw < 200; draw++)
{
CheckEqual(BitConverter.DoubleToInt64Bits(DrawByDivision(ref expected, range[0], range[1])),
BitConverter.DoubleToInt64Bits(actual.NextDouble(range[0], range[1])));
}
CheckEqual(expected.NextUInt64(), actual.NextUInt64());
}
}
public void MatchesTheDivisionDefinitionForTheEdgesOfEveryWord()
{
ulong[] words = { 0, 1, ulong.MaxValue, 0x8000000000000000UL, 0x7FFFFFFFFFFFFFFFUL, 1UL << 11 };
double[][] ranges =
{
new[] { -3.5, 7.25 }, new[] { 0.0, 1.0 }, new[] { -1.0, 1.0 }, new[] { -2.0, -1.0 },
new[] { -double.MaxValue, double.MaxValue }, new[] { -1e-300, 1e300 }, new[] { -1e300, 1e-300 },
};
foreach (double[] range in ranges)
{
foreach (ulong word in words)
{
ScriptedGenerator expected = Repeating(0x9E3779B97F4A7C15UL, 70);
expected.Draws[0] = word;
ScriptedGenerator actual = Repeating(0x9E3779B97F4A7C15UL, 70);
actual.Draws[0] = word;
CheckEqual(BitConverter.DoubleToInt64Bits(DrawByDivision(ref expected, range[0], range[1])),
BitConverter.DoubleToInt64Bits(actual.NextDouble(range[0], range[1])));
}
}
}
}
public class NextFloatTests : UnitTestSharp.TestFixture
{
public void AZeroDrawGivesZero()
{
var generator = new ScriptedGenerator(0);
CheckEqual(0.0f, generator.NextFloat());
}
public void TheTopDrawGivesOneLessAStep()
{
var generator = new ScriptedGenerator(uint.MaxValue);
CheckEqual(1.0f - RandomDraws.FloatStep, generator.NextFloat());
}
public void ReadsOnlyTheTop24Bits()
{
var generator = new ScriptedGenerator((1UL << 8) - 1, 1UL << 8);
CheckEqual(0.0f, generator.NextFloat());
CheckEqual(RandomDraws.FloatStep, generator.NextFloat());
}
}
public class NextFloatRangeTests : UnitTestSharp.TestFixture
{
public static float Draw(float minimum, float maximumExclusive, uint index, uint count)
{
var generator = new ScriptedGenerator(DrawFor32(index, count));
return generator.NextFloat(minimum, maximumExclusive);
}
public void TheUnitInterval_StepsDownFromJustBelowOne()
{
CheckEqual(1.0f - RandomDraws.FloatStep, Draw(0, 1, index: 0, count: 1U << 24));
}
public void TheUnitInterval_EndsOnTheMinimum()
{
CheckEqual(0.0f, Draw(0, 1, index: (1U << 24) - 1, count: 1U << 24));
}
public void ARangeCrossingZero_UsesTheGapOfTheFartherEnd()
{
uint count = (1U << 24) + (1U << 23);
CheckEqual(0.0f, Draw(-0.5f, 1, index: (1U << 24) - 1, count));
CheckEqual(-0.5f, Draw(-0.5f, 1, index: count - 1, count));
}
public void AMinimumBetweenGridPoints_TakesTheLastPlace()
{
uint count = (1U << 24) + 1;
CheckEqual(0.0f, Draw(-1e-30f, 1, index: count - 2, count));
CheckEqual(-1e-30f, Draw(-1e-30f, 1, index: count - 1, count));
}
public void AFartherMinimum_StepsUpFromIt()
{
uint count = 3U << 23;
CheckEqual(-2.0f, Draw(-2, 1, index: 0, count));
CheckEqual(1.0f - 2 * RandomDraws.FloatStep, Draw(-2, 1, index: count - 1, count));
}
public void AMaximumBetweenGridPoints_StopsAtTheGridPointBelowIt()
{
CheckEqual(0.0f, Draw(-1, 1e-30f, index: 1U << 24, count: (1U << 24) + 1));
}
public void TheWholeFloatRange_NeitherOverflowsNorReachesTheMaximum()
{
uint count = (1U << 25) - 2;
CheckEqual(float.MaxValue - (float)Math.Pow(2, 104),
Draw(-float.MaxValue, float.MaxValue, index: 0, count));
CheckEqual(-float.MaxValue, Draw(-float.MaxValue, float.MaxValue, index: count - 1, count));
}
public void SubnormalEnds_StepByTheSmallestSubnormal()
{
float epsilon = float.Epsilon;
CheckEqual(3 * epsilon, Draw(epsilon, 4 * epsilon, index: 0, count: 3));
CheckEqual(epsilon, Draw(epsilon, 4 * epsilon, index: 2, count: 3));
}
public void ARangeOfOneFloat_GivesTheMinimum()
{
CheckEqual(1.0f, Draw(1, AwayFromZero(1.0f), index: 0, count: 1));
}
public void ASubnormalMinimumBesideTheLargestMaximum_StillLeavesZeroReachable()
{
uint count = 1U << 24;
CheckEqual(0.0f, Draw(-float.Epsilon, float.MaxValue, index: count - 2, count));
CheckEqual(-float.Epsilon, Draw(-float.Epsilon, float.MaxValue, index: count - 1, count));
}
public void ASubnormalMaximumBesideTheLargestMinimum_StillLeavesZeroReachable()
{
CheckEqual(0.0f, Draw(-float.MaxValue, float.Epsilon, index: (1U << 24) - 1, count: 1U << 24));
}
public void RejectsANotANumberMinimum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextFloat(float.NaN, 1);
}
public void RejectsAnInfiniteMinimum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextFloat(float.NegativeInfinity, 1);
}
public void RejectsANotANumberMaximum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextFloat(0, float.NaN);
}
public void RejectsAnInfiniteMaximum()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextFloat(0, float.PositiveInfinity);
}
public void RejectsAnEmptyRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextFloat(1, 1);
}
public void RejectsAReversedRange()
{
var generator = new ScriptedGenerator(1);
CheckThrow(typeof(ArgumentOutOfRangeException));
generator.NextFloat(2, 1);
}
public void MatchesTheDivisionDefinitionDrawForDraw()
{
float belowThreshold = ExactFloatPowerOfTwo(RandomDraws.MinimumFastFloatExponent - 128);
float[][] ranges =
{
new[] { 0f, 1f }, new[] { -1f, 1f }, new[] { -3.5f, 7.25f }, new[] { -1f, 0f },
new[] { 1f, 2f }, new[] { 0.5f, 1f }, new[] { -0.5f, 1f }, new[] { -2f, 1f },
new[] { -2f, -1f }, new[] { -1f, -0.5f }, new[] { 100f, 200f }, new[] { -1e30f, 1e38f },
new[] { -float.MaxValue, float.MaxValue }, new[] { -1e-30f, 1f }, new[] { -1f, 1e-30f },
new[] { 0f, 1e-30f }, new[] { -4f, 4f }, new[] { -8f, 4f }, new[] { 4f, 8f }, new[] { 0f, 4f },
new[] { -4f, 0f }, new[] { -1e-30f, 1e30f }, new[] { -1e30f, 1e-30f },
new[] { float.Epsilon, 1f }, new[] { 1e-40f, 2e-40f }, new[] { 0f, belowThreshold },
new[] { 0f, 2 * belowThreshold }, new[] { -belowThreshold, 2 * belowThreshold },
new[] { -2 * belowThreshold, belowThreshold }, new[] { -2 * belowThreshold, 0f },
new[] { -belowThreshold, 0f },
};
foreach (float[] range in ranges)
{
var expected = new Xoshiro128PlusPlus(7);
var actual = new Xoshiro128PlusPlus(7);
for (int draw = 0; draw < 200; draw++)
{
CheckEqual(BitsOf(DrawFloatByDivision(ref expected, range[0], range[1])),
BitsOf(actual.NextFloat(range[0], range[1])));
}
CheckEqual(expected.NextUInt32(), actual.NextUInt32());
}
}
}
}
}