-
Notifications
You must be signed in to change notification settings - Fork 0
/
RandomInteger.cs
70 lines (56 loc) · 2.23 KB
/
RandomInteger.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
using System.Numerics;
using System.Security.Cryptography;
namespace protohackers;
public class RandomInteger
{
public static BigInteger Next(BigInteger min, BigInteger max)
{
RandomNumberGenerator gen = RandomNumberGenerator.Create();
return RandomInRange(gen, min, max);
}
// Implementation was taken from
// https://stackoverflow.com/a/48855115/2015348
private static BigInteger RandomInRange(RandomNumberGenerator rng, BigInteger min, BigInteger max)
{
if (min.CompareTo(max) > 0)
{
(min, max) = (max, min);
}
// offset to set min = 0
BigInteger offset = BigInteger.Negate(min);
min = 0;
max = BigInteger.Add(max, offset);
var value = BigInteger.Subtract(RandomInRangeFromZeroToPositive(rng, max), offset);
return value;
}
private static BigInteger RandomInRangeFromZeroToPositive(RandomNumberGenerator rng, BigInteger max)
{
BigInteger value;
var bytes = max.ToByteArray();
// count how many bits of the most significant byte are 0
// NOTE: sign bit is always 0 because `max` must always be positive
byte zeroBitsMask = 0b00000000;
var mostSignificantByte = bytes[bytes.Length - 1];
// we try to set to 0 as many bits as there are in the most significant byte, starting from the left (most significant bits first)
// NOTE: `i` starts from 7 because the sign bit is always 0
for (var i = 7; i >= 0; i--)
{
// we keep iterating until we find the most significant non-0 bit
if ((mostSignificantByte & (0b1 << i)) != 0)
{
var zeroBits = 7 - i;
zeroBitsMask = (byte)(0b11111111 >> zeroBits);
break;
}
}
do
{
rng.GetBytes(bytes);
// set most significant bits to 0 (because `value > max` if any of these bits is 1)
bytes[bytes.Length - 1] &= zeroBitsMask;
value = new BigInteger(bytes);
// `value > max` 50% of the times, in which case the fastest way to keep the distribution uniform is to try again
} while (value.CompareTo(max) > 0);
return value;
}
}