Added GetInt32 to RandomNumberGenerator. #31243
Conversation
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
|
||
public static int GetInt32(int toExclusive) | ||
{ | ||
if (toExclusive <= 0) throw new ArgumentOutOfRangeException(nameof(toExclusive), SR.ArgumentOutOfRange_NeedPosNum); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw
on next line. The next function happens to violate our style (rule 1, at that). If you wanted to clean it up I wouldn't mind.
Span<uint> valueResult = stackalloc uint[1]; | ||
ref uint result = ref valueResult[0]; | ||
Span<byte> valueResultBytes = MemoryMarshal.AsBytes(valueResult); | ||
if (BitConverter.IsLittleEndian) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My personal style is to have a blank line before a control flow statement because I find it helps legibility. I'm just offering it out there, our style guide isn't prescriptive on this one way or another.
public static void GetInt32_FullRange() | ||
{ | ||
int result = RandomNumberGenerator.GetInt32(int.MinValue, int.MaxValue); | ||
Assert.NotEqual(0, result); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a 1 in 4 billion chance this fails. If you want to assert something you should just assert it wasn't int.MaxValue
observedNumbers[number]++; | ||
} | ||
} | ||
const double tollerance = 0.07; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: tolerance
foreach ((_, int occurences) in observedNumbers) | ||
{ | ||
double percentage = occurences / (double)numbers.Length; | ||
Assert.True(Math.Abs(expected - percentage) < tollerance, "Occured number of times within threshold."); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: Occurred
double percentage = occurences / (double)numbers.Length; | ||
Assert.True(Math.Abs(expected - percentage) < tollerance, "Occured number of times within threshold."); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be good to see range validity test for negative bounds, and some tests with interesting-looking bounds sizes (really I want to test both the lzcnt implementation and the algorithm. Something annoying like (x, x + 0x0101) so the high bit in the mask value is technically required, but rarely valid. If you ran that 1000 times you "should" have at least one hit on the highest value, and shouldn't have value skew.)
(int.MinValue, int.MinValue + 3)
(-257, -129)
(-100, 5)
(254, 512)
...
Also, roll 1000d6, check for skew. (The existing "coinflip" test sort of does this, but it's bit-aligned in range, so it doesn't test the high value rejection vs mod bias algorithm differential)
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
I'm not fully sure what the failure in d243963 was. It looks like infrastructure? The most information I could glean was a workItemStatus had a failure count of 1, but the test failure count was 0. Update: "mission control" says it was System.IO.Packaging.Tests. |
|
||
// We only want to generate as many bytes as required to satisfy the mask to not apply | ||
// undue pressure to the underlying random number generator. | ||
int bytesRequired = (32 + 7 - leadingZeroCount) / 8; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe comment where the 7 comes from?
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
...m.Security.Cryptography.Algorithms/src/System/Security/Cryptography/RandomNumberGenerator.cs
Outdated
Show resolved
Hide resolved
Test failures look unrelated. |
Fixes 30873.
@GrabYourPitchforks @bartonjs OK. Changed, rebased, squashed. |
@terrajobst Candidate for
api-candidate-for-standard
|
Fixes 30873. Commit migrated from dotnet/corefx@928873f
@vcsjones Sorry for misusing this platform for contacting you. For several reasons i'm stuck at Netstandard 2.0 for my project, so i wanted to add your GetInt32 method as an extension to RandomNumberGenerator. Unfortunately i have a problem with "RandomNumberGeneratorImplementation.FillSpan()." Where does it come from? What does it do? Can you shed some light on it please? |
@puenktchen It's the internal implementation for RandomNumberGenerator in .NET Core that can fill a This will mean the Disclaimer: The snippet below is untested for correctness. The only testing on the code below that I have done is that it compiles for static class Extensions
{
public static int GetInt32(this RandomNumberGenerator rng, int toExclusive)
{
return GetInt32(rng, 0, toExclusive);
}
public static int GetInt32(this RandomNumberGenerator rng, int fromInclusive, int toExclusive)
{
if (fromInclusive >= toExclusive)
throw new ArgumentException("Range is invalid");
uint range = (uint)toExclusive - (uint)fromInclusive - 1;
// If there is only one possible choice, nothing random will actually happen, so return
// the only possibility.
if (range == 0)
{
return fromInclusive;
}
// Create a mask for the bits that we care about for the range. The other bits will be
// masked away.
uint mask = range;
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;
byte[] resultBuffer = new byte[sizeof(uint)];
uint result;
do
{
rng.GetBytes(resultBuffer);
result = mask & BitConverter.ToUInt32(resultBuffer, 0);
}
while (result > range);
return (int)result + fromInclusive;
}
} |
@vcsjones Works perfect for me. Thank you so much! |
Implement RNG.GetInt32.
/cc @bartonjs @GrabYourPitchforks
Fixes #30873.