Skip to content

Commit 2ef9209

Browse files
committed
New methods
1 parent 615e00f commit 2ef9209

File tree

1 file changed

+68
-26
lines changed

1 file changed

+68
-26
lines changed
Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
2+
using System.Diagnostics.Contracts;
23
using System.Runtime.CompilerServices;
3-
using System.Runtime.InteropServices;
44
using System.Security.Cryptography;
55

66
namespace Soenneker.Extensions.RandomNumberGenerators;
@@ -11,44 +11,86 @@ namespace Soenneker.Extensions.RandomNumberGenerators;
1111
public static class RandomNumberGeneratorExtension
1212
{
1313
/// <summary>
14-
/// Generates a non-negative random integer that is less than the specified maximum value using the provided random
15-
/// number generator.
14+
/// Returns a non-negative random 64-bit integer that is less than the specified maximum value.
1615
/// </summary>
17-
/// <remarks>This method avoids modulo bias by using rejection sampling, ensuring a uniform distribution
18-
/// of values in the specified range.</remarks>
19-
/// <param name="rng">The random number generator to use for producing the random integer. Cannot be null.</param>
16+
/// <remarks>This method avoids modulo bias to ensure a uniform distribution of values in the specified
17+
/// range.</remarks>
18+
/// <param name="rng">The random number generator to use for producing the random value. Cannot be null.</param>
2019
/// <param name="exclusiveMax">The exclusive upper bound of the random number to be generated. Must be greater than 0.</param>
21-
/// <returns>A non-negative integer that is greater than or equal to 0 and less than <paramref name="exclusiveMax"/>.</returns>
22-
/// <exception cref="ArgumentNullException">Thrown if <paramref name="rng"/> is null.</exception>
20+
/// <returns>A 64-bit integer greater than or equal to 0 and less than <paramref name="exclusiveMax"/>.</returns>
2321
/// <exception cref="ArgumentOutOfRangeException">Thrown if <paramref name="exclusiveMax"/> is less than or equal to 0.</exception>
24-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
25-
public static int GetInt32(this RandomNumberGenerator rng, int exclusiveMax)
22+
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
23+
public static long GetInt64(this RandomNumberGenerator rng, long exclusiveMax)
2624
{
27-
if (rng is null)
28-
throw new ArgumentNullException(nameof(rng));
25+
ArgumentNullException.ThrowIfNull(rng);
2926

3027
if (exclusiveMax <= 0)
3128
throw new ArgumentOutOfRangeException(nameof(exclusiveMax));
3229

33-
var max = (uint)exclusiveMax;
30+
ulong max = (ulong)exclusiveMax;
3431

35-
// Rejection sampling to avoid modulo bias
36-
uint limit = uint.MaxValue / max * max;
32+
// Rejection threshold to avoid modulo bias
33+
// Equivalent to: (2^64 % max)
34+
ulong rejectThreshold = unchecked((ulong)(0UL - max)) % max;
3735

38-
uint value = 0;
39-
40-
do
36+
while (true)
4137
{
42-
// Create a span pointing to 'value'
43-
Span<uint> valueSpan = MemoryMarshal.CreateSpan(ref value, 1);
38+
ulong value = rng.GetUInt64();
39+
if (value >= rejectThreshold)
40+
return (long)(value % max);
41+
}
42+
}
4443

45-
// Interpret that span as bytes and fill with random data
46-
Span<byte> bytesSpan = MemoryMarshal.AsBytes(valueSpan);
44+
/// <summary>
45+
/// Generates a random 64-bit signed integer using the specified random number generator.
46+
/// </summary>
47+
/// <param name="rng">The random number generator to use for producing the random value. Cannot be null.</param>
48+
/// <returns>A randomly generated 64-bit signed integer.</returns>
49+
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
50+
public static long GetInt64(this RandomNumberGenerator rng)
51+
=> unchecked((long)rng.GetUInt64());
4752

48-
rng.GetBytes(bytesSpan);
49-
}
50-
while (value >= limit);
53+
/// <summary>
54+
/// Returns a random 64-bit signed integer that is greater than or equal to the specified minimum value and less
55+
/// than the specified maximum value.
56+
/// </summary>
57+
/// <param name="rng">The random number generator to use for producing the random value. Cannot be null.</param>
58+
/// <param name="min">The inclusive lower bound of the random number to be generated.</param>
59+
/// <param name="max">The exclusive upper bound of the random number to be generated. Must be greater than <paramref name="min"/>.</param>
60+
/// <returns>A 64-bit signed integer greater than or equal to <paramref name="min"/> and less than <paramref name="max"/>.</returns>
61+
/// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="min"/> is greater than or equal to <paramref name="max"/>.</exception>
62+
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
63+
public static long GetInt64(this RandomNumberGenerator rng, long min, long max)
64+
{
65+
if (min >= max)
66+
throw new ArgumentOutOfRangeException();
67+
68+
return min + rng.GetInt64(max - min);
69+
}
70+
71+
/// <summary>
72+
/// Generates a random 64-bit unsigned integer using the specified random number generator.
73+
/// </summary>
74+
/// <remarks>This method fills an 8-byte buffer with random data from the provided random number generator
75+
/// and interprets it as a 64-bit unsigned integer. The distribution of returned values is uniform across the entire
76+
/// range of UInt64. This method does not modify the state of the random number generator beyond advancing its
77+
/// internal state as required to produce random bytes.</remarks>
78+
/// <param name="rng">The random number generator to use for producing the random value. Cannot be null.</param>
79+
/// <returns>A random 64-bit unsigned integer sampled from the full range of possible values.</returns>
80+
[Pure, MethodImpl(MethodImplOptions.AggressiveInlining)]
81+
public static ulong GetUInt64(this RandomNumberGenerator rng)
82+
{
83+
Span<byte> bytes = stackalloc byte[8];
84+
rng.GetBytes(bytes);
5185

52-
return (int)(value % max);
86+
return
87+
(ulong)bytes[0]
88+
| ((ulong)bytes[1] << 8)
89+
| ((ulong)bytes[2] << 16)
90+
| ((ulong)bytes[3] << 24)
91+
| ((ulong)bytes[4] << 32)
92+
| ((ulong)bytes[5] << 40)
93+
| ((ulong)bytes[6] << 48)
94+
| ((ulong)bytes[7] << 56);
5395
}
5496
}

0 commit comments

Comments
 (0)