Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of Lemire's nearly divisionless method #79790

Merged
merged 32 commits into from
Feb 18, 2023
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
c5dadb4
Lemire implementation
mla-alm Dec 15, 2022
71b3bb3
Cleanup
mla-alm Dec 17, 2022
405c976
Article reference
mla-alm Dec 17, 2022
a3c263f
Fix
mla-alm Dec 17, 2022
96b5e91
Fixes
mla-alm Dec 17, 2022
600b9d5
Comment out implementation specific tests in Xoshiro_AlgorithmBehaves…
mla-alm Dec 18, 2022
e5c4536
Fix
mla-alm Dec 18, 2022
8a7dbdf
Merge remote-tracking branch 'origin/main' into mla-alm/lemire
mla-alm Dec 18, 2022
2dd1a78
Reenable sufficient checks for Xoshiro_AlgorithmBehavesAsExpected
mla-alm Dec 21, 2022
1b3c19b
Merge remote-tracking branch 'origin/main' into mla-alm/lemire
mla-alm Dec 21, 2022
f2a11d0
Fix
mla-alm Dec 21, 2022
a68e011
Add third party notice
mla-alm Dec 22, 2022
4ae4914
Resolve comments
mla-alm Dec 23, 2022
a4a6f93
Resolve comments
mla-alm Dec 23, 2022
e380d47
Resolve comments
mla-alm Dec 23, 2022
f50eabc
Merge remote-tracking branch 'origin/main' into mla-alm/lemire
mla-alm Dec 24, 2022
c739482
Merge branch 'main' into mla-alm/lemire
jeffhandley Jan 26, 2023
1bec03e
Resolve comments
mla-alm Jan 28, 2023
6d7b1f0
Merge branch 'dotnet:main' into mla-alm/lemire
mla-alm Jan 28, 2023
0cc3ecd
Refactor implementation to separate class
mla-alm Jan 28, 2023
20364ec
Merge remote-tracking branch 'origin/mla-alm/lemire' into mla-alm/lemire
mla-alm Jan 28, 2023
a22e745
Merge branch 'dotnet:main' into mla-alm/lemire
mla-alm Jan 29, 2023
9802461
Typo fix
mla-alm Jan 29, 2023
cc32925
Merge branch 'dotnet:main' into mla-alm/lemire
mla-alm Jan 30, 2023
89cb3bd
stephentoub's refactor
mla-alm Jan 30, 2023
4370688
Merge remote-tracking branch 'origin/mla-alm/lemire' into mla-alm/lemire
mla-alm Jan 30, 2023
ef38e9d
Reverting NextInt64 on Xoshiro128
mla-alm Feb 16, 2023
33654d2
Merge branch 'dotnet:main' into mla-alm/lemire
mla-alm Feb 16, 2023
95b4b42
Adjust test
mla-alm Feb 16, 2023
b28062c
Merge remote-tracking branch 'origin/mla-alm/lemire' into mla-alm/lemire
mla-alm Feb 16, 2023
6304f68
Merge branch 'dotnet:main' into mla-alm/lemire
mla-alm Feb 17, 2023
608a801
Update src/libraries/System.Private.CoreLib/src/System/Random.Xoshiro…
stephentoub Feb 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion THIRD-PARTY-NOTICES.TXT
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ worldwide. This software is distributed without any warranty.

See <http://creativecommons.org/publicdomain/zero/1.0/>.

License for fastmod (https://github.com/lemire/fastmod) and ibm-fpgen (https://github.com/nigeltao/parse-number-fxx-test-data)
License for fastmod (https://github.com/lemire/fastmod), ibm-fpgen (https://github.com/nigeltao/parse-number-fxx-test-data) and fastrange (https://github.com/lemire/fastrange)
--------------------------------------

Copyright 2018 Daniel Lemire
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ internal sealed class XoshiroImpl : ImplBase
//
// See <http://creativecommons.org/publicdomain/zero/1.0/>.

// Next(int maxValue), Next(int minValue, int maxValue), NextInt64(long maxValue) and NextInt64(int minValue, int maxValue)
// are based on the algorithm from https://arxiv.org/pdf/1805.10941.pdf and https://github.com/lemire/fastrange:
//
// Written in 2018 by Daniel Lemire
mla-alm marked this conversation as resolved.
Show resolved Hide resolved

private uint _s0, _s1, _s2, _s3;

public unsafe XoshiroImpl()
Expand Down Expand Up @@ -90,46 +95,46 @@ public override int Next()

public override int Next(int maxValue)
{
if (maxValue > 1)
Debug.Assert(maxValue >= 0);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

ulong randomProduct = (ulong)(uint)maxValue * NextUInt32();
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
uint lowPart = (uint)randomProduct;

if (lowPart < (uint)maxValue)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling((uint)maxValue);
while (true)
uint remainder = ((uint)0 - (uint)maxValue) % (uint)maxValue;

while (lowPart < remainder)
{
uint result = NextUInt32() >> (sizeof(uint) * 8 - bits);
if (result < (uint)maxValue)
{
return (int)result;
}
randomProduct = (ulong)(uint)maxValue * NextUInt32();
lowPart = (uint)randomProduct;
}
}

Debug.Assert(maxValue == 0 || maxValue == 1);
return 0;
return (int)(randomProduct >> (sizeof(uint) * 8));
}

public override int Next(int minValue, int maxValue)
{
Debug.Assert(minValue <= maxValue);
danmoseley marked this conversation as resolved.
Show resolved Hide resolved

uint exclusiveRange = (uint)(maxValue - minValue);

if (exclusiveRange > 1)
ulong randomProduct = (ulong)exclusiveRange * NextUInt32();
uint lowPart = (uint)randomProduct;

if (lowPart < exclusiveRange)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling(exclusiveRange);
while (true)
uint remainder = ((uint)0 - exclusiveRange) % exclusiveRange;

while (lowPart < remainder)
{
uint result = NextUInt32() >> (sizeof(uint) * 8 - bits);
if (result < exclusiveRange)
{
return (int)result + minValue;
}
randomProduct = (ulong)exclusiveRange * NextUInt32();
lowPart = (uint)randomProduct;
}
}

Debug.Assert(minValue == maxValue || minValue + 1 == maxValue);
return minValue;
return (int)(randomProduct >> (sizeof(uint) * 8)) + minValue;
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}

public override long NextInt64()
Expand All @@ -149,56 +154,46 @@ public override long NextInt64()

public override long NextInt64(long maxValue)
{
if (maxValue <= int.MaxValue)
{
return Next((int)maxValue);
}
Debug.Assert(maxValue >= 0);

if (maxValue > 1)
UInt128 randomProduct = (UInt128)(ulong)maxValue * NextUInt64();
ulong lowPart = (ulong)randomProduct;

if (lowPart < (ulong)maxValue)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling((ulong)maxValue);
while (true)
ulong remainder = ((ulong)0 - (ulong)maxValue) % (ulong)maxValue;

while (lowPart < remainder)
{
ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits);
if (result < (ulong)maxValue)
{
return (long)result;
}
randomProduct = (UInt128)(ulong)maxValue * NextUInt64();
lowPart = (ulong)randomProduct;
}
}

Debug.Assert(maxValue == 0 || maxValue == 1);
return 0;
return (long)(randomProduct >> (sizeof(ulong) * 8));
}

public override long NextInt64(long minValue, long maxValue)
{
Debug.Assert(minValue <= maxValue);

ulong exclusiveRange = (ulong)(maxValue - minValue);

if (exclusiveRange <= int.MaxValue)
{
return Next((int)exclusiveRange) + minValue;
}
UInt128 randomProduct = (UInt128)exclusiveRange * NextUInt64();
ulong lowPart = (ulong)randomProduct;

if (exclusiveRange > 1)
if (lowPart < exclusiveRange)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling(exclusiveRange);
while (true)
ulong remainder = ((ulong)0 - exclusiveRange) % exclusiveRange;

while (lowPart < remainder)
{
ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits);
if (result < exclusiveRange)
{
return (long)result + minValue;
}
randomProduct = (UInt128)exclusiveRange * NextUInt64();
lowPart = (ulong)randomProduct;
}
}

Debug.Assert(minValue == maxValue || minValue + 1 == maxValue);
return minValue;
return (long)(randomProduct >> (sizeof(ulong) * 8)) + minValue;
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
}

public override void NextBytes(byte[] buffer) => NextBytes((Span<byte>)buffer);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ internal sealed class XoshiroImpl : ImplBase
//
// See <http://creativecommons.org/publicdomain/zero/1.0/>.

// Next(int maxValue), Next(int minValue, int maxValue), NextInt64(long maxValue) and NextInt64(int minValue, int maxValue)
// are based on the algorithm from https://arxiv.org/pdf/1805.10941.pdf and https://github.com/lemire/fastrange:
//
// Written in 2018 by Daniel Lemire

private ulong _s0, _s1, _s2, _s3;

public unsafe XoshiroImpl()
Expand Down Expand Up @@ -90,46 +95,46 @@ public override int Next()

public override int Next(int maxValue)
{
if (maxValue > 1)
danmoseley marked this conversation as resolved.
Show resolved Hide resolved
Debug.Assert(maxValue >= 0);

ulong randomProduct = (ulong)(uint)maxValue * NextUInt32();
uint lowPart = (uint)randomProduct;

if (lowPart < (uint)maxValue)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling((uint)maxValue);
while (true)
uint remainder = ((uint)0 - (uint)maxValue) % (uint)maxValue;
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

while (lowPart < remainder)
{
ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits);
if (result < (uint)maxValue)
{
return (int)result;
}
randomProduct = (ulong)(uint)maxValue * NextUInt32();
lowPart = (uint)randomProduct;
}
}

Debug.Assert(maxValue == 0 || maxValue == 1);
return 0;
return (int)(randomProduct >> (sizeof(uint) * 8));
}

public override int Next(int minValue, int maxValue)
{
ulong exclusiveRange = (ulong)((long)maxValue - minValue);
Debug.Assert(minValue <= maxValue);

uint exclusiveRange = (uint)(maxValue - minValue);

ulong randomProduct = (ulong)exclusiveRange * NextUInt32();
uint lowPart = (uint)randomProduct;

if (exclusiveRange > 1)
if (lowPart < exclusiveRange)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling(exclusiveRange);
while (true)
uint remainder = ((uint)0 - exclusiveRange) % exclusiveRange;

while (lowPart < remainder)
{
ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits);
if (result < exclusiveRange)
{
return (int)result + minValue;
}
randomProduct = (ulong)exclusiveRange * NextUInt32();
lowPart = (uint)randomProduct;
}
}

Debug.Assert(minValue == maxValue || minValue + 1 == maxValue);
return minValue;
return (int)(randomProduct >> (sizeof(uint) * 8)) + minValue;
}

public override long NextInt64()
Expand All @@ -149,46 +154,46 @@ public override long NextInt64()

public override long NextInt64(long maxValue)
{
if (maxValue > 1)
Debug.Assert(maxValue >= 0);

UInt128 randomProduct = (UInt128)(ulong)maxValue * NextUInt64();
ulong lowPart = (ulong)randomProduct;

if (lowPart < (ulong)maxValue)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling((ulong)maxValue);
while (true)
ulong remainder = ((ulong)0 - (ulong)maxValue) % (ulong)maxValue;

while (lowPart < remainder)
{
ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits);
if (result < (ulong)maxValue)
{
return (long)result;
}
randomProduct = (UInt128)(ulong)maxValue * NextUInt64();
lowPart = (ulong)randomProduct;
}
}

Debug.Assert(maxValue == 0 || maxValue == 1);
return 0;
return (long)(randomProduct >> (sizeof(ulong) * 8));
}

public override long NextInt64(long minValue, long maxValue)
{
Debug.Assert(minValue <= maxValue);

ulong exclusiveRange = (ulong)(maxValue - minValue);

if (exclusiveRange > 1)
UInt128 randomProduct = (UInt128)exclusiveRange * NextUInt64();
ulong lowPart = (ulong)randomProduct;

if (lowPart < exclusiveRange)
{
// Narrow down to the smallest range [0, 2^bits] that contains maxValue.
// Then repeatedly generate a value in that outer range until we get one within the inner range.
int bits = BitOperations.Log2Ceiling(exclusiveRange);
while (true)
ulong remainder = ((ulong)0 - exclusiveRange) % exclusiveRange;

while (lowPart < remainder)
{
ulong result = NextUInt64() >> (sizeof(ulong) * 8 - bits);
if (result < exclusiveRange)
{
return (long)result + minValue;
}
randomProduct = (UInt128)exclusiveRange * NextUInt64();
lowPart = (ulong)randomProduct;
}
}

Debug.Assert(minValue == maxValue || minValue + 1 == maxValue);
return minValue;
return (long)(randomProduct >> (sizeof(ulong) * 8)) + minValue;
}

public override void NextBytes(byte[] buffer) => NextBytes((Span<byte>)buffer);
Expand Down
Loading