-
Notifications
You must be signed in to change notification settings - Fork 445
/
Copy pathrandom.hpp
161 lines (129 loc) · 3.39 KB
/
random.hpp
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
#pragma once
#include <common.hpp>
#include <random>
#include <vector>
namespace rack {
/** Random number generation */
namespace random {
/** xoroshiro128+. Very fast, not-cryptographic random number generator.
From https://prng.di.unimi.it/
Example:
std::random_device rd;
random::Xoroshiro128Plus rng(rd(), rd());
uint64_t r = rng();
std::uniform_real_distribution<float> uniform(0.f, 1.f);
float r = uniform(rng);
std::normal_distribution<> normal(0.0, 1.0);
double r = normal(rng);
*/
struct Xoroshiro128Plus {
uint64_t state[2] = {};
void seed(uint64_t s0, uint64_t s1) {
state[0] = s0;
state[1] = s1;
// A bad seed will give a bad first result, so shift the state
operator()();
}
bool isSeeded() {
return state[0] || state[1];
}
static uint64_t rotl(uint64_t x, int k) {
return (x << k) | (x >> (64 - k));
}
uint64_t operator()() {
uint64_t s0 = state[0];
uint64_t s1 = state[1];
uint64_t result = s0 + s1;
s1 ^= s0;
state[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14);
state[1] = rotl(s1, 36);
return result;
}
constexpr uint64_t min() const {
return 0;
}
constexpr uint64_t max() const {
return UINT64_MAX;
}
};
// Simple thread-local API
/** Initializes the thread-local RNG state.
Must call when creating a thread, otherwise random state is undefined (might always return 0).
*/
void init();
/** Returns the thread-local generator.
*/
Xoroshiro128Plus& local();
template <typename T>
T get() {
// Call operator()() and cast by default
return local()();
}
template <>
inline uint32_t get() {
// Take top 32 bits which has better randomness properties.
return get<uint64_t>() >> 32;
}
template <>
inline uint16_t get() {
return get<uint64_t>() >> 48;
}
template <>
inline uint8_t get() {
return get<uint64_t>() >> 56;
}
template <>
inline bool get() {
return get<uint64_t>() >> 63;
}
template <>
inline float get() {
// The multiplier is 2f7fffff in hex. This gives maximum precision of uint32_t -> float conversion and its image is [0, 1).
return get<uint32_t>() * 2.32830629e-10f;
}
template <>
inline double get() {
return get<uint64_t>() * 5.421010862427522e-20;
}
/** Returns a uniform random uint64_t from 0 to UINT64_MAX */
inline uint64_t u64() {return get<uint64_t>();}
/** Returns a uniform random uint32_t from 0 to UINT32_MAX */
inline uint32_t u32() {return get<uint32_t>();}
/** Returns a uniform random float in the interval [0.0, 1.0) */
inline float uniform() {return get<float>();}
/** Returns a normal random number with mean 0 and standard deviation 1 */
inline float normal() {
// Box-Muller transform
float radius = std::sqrt(-2.f * std::log(1.f - get<float>()));
float theta = 2.f * M_PI * get<float>();
return radius * std::sin(theta);
// // Central Limit Theorem
// const int n = 8;
// float sum = 0.0;
// for (int i = 0; i < n; i++) {
// sum += get<float>();
// }
// return (sum - n / 2.f) / std::sqrt(n / 12.f);
}
/** Fills an array with random bytes. */
inline void buffer(uint8_t* out, size_t len) {
Xoroshiro128Plus& rng = local();
for (size_t i = 0; i < len; i += 4) {
uint64_t r = rng();
out[i] = r;
if (i + 1 < len)
out[i + 1] = r >> 8;
if (i + 2 < len)
out[i + 2] = r >> 16;
if (i + 3 < len)
out[i + 3] = r >> 24;
}
}
/** Creates a vector of random bytes. */
inline std::vector<uint8_t> vector(size_t len) {
std::vector<uint8_t> v(len);
buffer(v.data(), len);
return v;
}
} // namespace random
} // namespace rack