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()); } } } } }