11using System ;
2+ using System . Diagnostics . Contracts ;
23using System . Runtime . CompilerServices ;
3- using System . Runtime . InteropServices ;
44using System . Security . Cryptography ;
55
66namespace Soenneker . Extensions . RandomNumberGenerators ;
@@ -11,44 +11,86 @@ namespace Soenneker.Extensions.RandomNumberGenerators;
1111public 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