diff --git a/test/random_test.cpp b/test/random_test.cpp index 413c70449..f95fb39ee 100644 --- a/test/random_test.cpp +++ b/test/random_test.cpp @@ -123,9 +123,98 @@ TEST(RandomTest, ShiftModDistributionSignCarry) ASSERT_EQ(GenerateRnd(65535 - 1), -32768) << "Distribution must map negative numbers using sign carry shifts"; SetRndSeed(1457187811); // Test mod when a division occurs ASSERT_EQ(GenerateRnd(32768 - 1), -1) << "Distribution must map negative numbers using sign carry shifts"; +} + +// The Diablo LCG implementation attempts to improve the quality of generated numbers that would only use the low +// bits of the LCG output but due to applying this after taking the absolute value this introduces bias. This may +// be an inconsistency with the implementation in devilutionx, see the comment for RandomTest_ShiftModDistributionHighBits +TEST(RandomTest, ShiftModDistributionLowBits) { + // All the following seeds generate values less than 2^16, so after shifting they give a 0 value + constexpr auto maxBound = 65534; SetRndSeed(3604671459U); // yields 0 - ASSERT_EQ(GenerateRnd(65534), 0) << "Invalid distribution"; + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(0); // yields 1 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(2914375622U); // yields -1 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(538964771); // yields 64 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(2375410851U); // yields -64 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1229260608); // yields 65 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1685115014); // yields -65 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1768225379); // yields 128 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1146150243); // yields -128 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1480523688); // yields 7625 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1433851934); // yields -7625 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(2382565573U); // yields 32458 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(531810049); // yields -32458 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1625910243); // yields 32768 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(1288465379); // yields -32768 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + + // -1 from the max bound for the next two to ensure it's not due to a mod with no remainder + SetRndSeed(2561524649U); // yields 65534 + ASSERT_EQ(GenerateRnd(maxBound - 1), 0) << "Invalid distribution"; + SetRndSeed(352850973U); // yields -65534 + ASSERT_EQ(GenerateRnd(maxBound - 1), 0) << "Invalid distribution"; + + SetRndSeed(3251820486U); // yields 65535 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; + SetRndSeed(3957522432U); // yields -65535 + ASSERT_EQ(GenerateRnd(maxBound), 0) << "Invalid distribution"; +} + +// The highest value GenerateRnd can return is 32767 (0x7FFF). I suspect this is the Borland rand() implementation +// This may mean that devilutionx is using an incorrect version as that function was implemented as the following: +// uint seed = mult * seed + inc +// return (seed >> 16) & 0x7FFF +// i.e., no cast from unsigned to signed, no modulo when building the return value. +TEST(RandomTest, ShiftModDistributionHighBits) +{ + constexpr auto maxBound = 65534; + SetRndSeed(3267226595U); // yields 65536 + ASSERT_EQ(GenerateRnd(maxBound), 1) << "Invalid distribution"; + SetRndSeed(3942116323U); // yields -65536 + ASSERT_EQ(GenerateRnd(maxBound), 1) << "Invalid distribution"; + SetRndSeed(4279561187U); // yields 131072 + ASSERT_EQ(GenerateRnd(maxBound), 2) << "Invalid distribution"; + SetRndSeed(2929781731U); // yields -131072 + ASSERT_EQ(GenerateRnd(maxBound), 2) << "Invalid distribution"; + SetRndSeed(659483619); // yields 262144 + ASSERT_EQ(GenerateRnd(maxBound), 4) << "Invalid distribution"; + SetRndSeed(2254892003U); // yields -262144 + ASSERT_EQ(GenerateRnd(maxBound), 4) << "Invalid distribution"; + SetRndSeed(3604671458U); // yields 22695477 + ASSERT_EQ(GenerateRnd(maxBound), 346) << "Invalid distribution"; + SetRndSeed(3604671460U); // yields -22695477 + ASSERT_EQ(GenerateRnd(maxBound), 346) << "Invalid distribution"; + SetRndSeed(1012371854); // yields 429496729 + ASSERT_EQ(GenerateRnd(maxBound), 6553) << "Invalid distribution"; + SetRndSeed(1902003768); // yields -429496729 + ASSERT_EQ(GenerateRnd(maxBound), 6553) << "Invalid distribution"; + SetRndSeed(189776845); // yields 1212022642 + ASSERT_EQ(GenerateRnd(maxBound), 18493) << "Invalid distribution"; + SetRndSeed(2724598777); // yields -1212022642 + ASSERT_EQ(GenerateRnd(maxBound), 18493) << "Invalid distribution"; + SetRndSeed(76596137); // yields 2147483646 + ASSERT_EQ(GenerateRnd(maxBound), 32767) << "Invalid distribution"; + SetRndSeed(2837779485); // yields -2147483646 + ASSERT_EQ(GenerateRnd(maxBound), 32767) << "Invalid distribution"; + SetRndSeed(766891974); // yields 2147483647 + ASSERT_EQ(GenerateRnd(maxBound), 32767) << "Invalid distribution"; + SetRndSeed(2147483648); // yields -2147483647 + ASSERT_EQ(GenerateRnd(maxBound), 32767) << "Invalid distribution"; } TEST(RandomTest, ModDistributionSignPreserving)