Skip to content

Commit e2d1f84

Browse files
committed
random: make GetRand() support entire range (incl. max)
The existing code uses GetRand(nMax), with a default value for nMax, where nMax is the range of values (not the maximum!) that the output is allowed to take. This will always miss the last possible value (e.g. GetRand<uint32_t>() will never return 0xffffffff). Fix this, by moving the functionality largely in RandomMixin, and also adding a separate RandomMixin::rand function, which returns a value in the entire (non-negative) range of an integer.
1 parent 810cdf6 commit e2d1f84

File tree

3 files changed

+46
-39
lines changed

3 files changed

+46
-39
lines changed

src/random.cpp

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -679,11 +679,6 @@ void RandAddPeriodic() noexcept
679679

680680
void RandAddEvent(const uint32_t event_info) noexcept { GetRNGState().AddEvent(event_info); }
681681

682-
uint64_t GetRandInternal(uint64_t nMax) noexcept
683-
{
684-
return FastRandomContext().randrange(nMax);
685-
}
686-
687682
uint256 GetRandHash() noexcept
688683
{
689684
uint256 hash;

src/random.h

Lines changed: 45 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -80,31 +80,6 @@
8080
* Thread-safe.
8181
*/
8282
void GetRandBytes(Span<unsigned char> bytes) noexcept;
83-
/** Generate a uniform random integer in the range [0..range). Precondition: range > 0 */
84-
uint64_t GetRandInternal(uint64_t nMax) noexcept;
85-
/** Generate a uniform random integer of type T in the range [0..nMax)
86-
* nMax defaults to std::numeric_limits<T>::max()
87-
* Precondition: nMax > 0, T is an integral type, no larger than uint64_t
88-
*/
89-
template<typename T>
90-
T GetRand(T nMax=std::numeric_limits<T>::max()) noexcept {
91-
static_assert(std::is_integral<T>(), "T must be integral");
92-
static_assert(std::numeric_limits<T>::max() <= std::numeric_limits<uint64_t>::max(), "GetRand only supports up to uint64_t");
93-
return T(GetRandInternal(nMax));
94-
}
95-
/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */
96-
template <typename D>
97-
D GetRandomDuration(typename std::common_type<D>::type max) noexcept
98-
// Having the compiler infer the template argument from the function argument
99-
// is dangerous, because the desired return value generally has a different
100-
// type than the function argument. So std::common_type is used to force the
101-
// call site to specify the type of the return value.
102-
{
103-
assert(max.count() > 0);
104-
return D{GetRand(max.count())};
105-
};
106-
constexpr auto GetRandMicros = GetRandomDuration<std::chrono::microseconds>;
107-
constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>;
10883

10984
/**
11085
* Return a timestamp in the future sampled from an exponential distribution
@@ -251,17 +226,17 @@ class RandomMixin
251226
}
252227
}
253228

254-
/** Generate a random integer in the range [0..range).
255-
* Precondition: range > 0.
256-
*/
257-
uint64_t randrange(uint64_t range) noexcept
229+
/** Generate a random integer in the range [0..range), with range > 0. */
230+
template<std::integral I>
231+
I randrange(I range) noexcept
258232
{
259-
assert(range);
260-
--range;
261-
int bits = std::bit_width(range);
233+
static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
234+
Assume(range > 0);
235+
uint64_t maxval = range - 1U;
236+
int bits = std::bit_width(maxval);
262237
while (true) {
263238
uint64_t ret = Impl().randbits(bits);
264-
if (ret <= range) return ret;
239+
if (ret <= maxval) return ret;
265240
}
266241
}
267242

@@ -284,6 +259,16 @@ class RandomMixin
284259
}
285260
}
286261

262+
/** Generate a random integer in its entire (non-negative) range. */
263+
template<std::integral I>
264+
I rand() noexcept
265+
{
266+
static_assert(std::numeric_limits<I>::max() <= std::numeric_limits<uint64_t>::max());
267+
static constexpr auto BITS = std::bit_width(uint64_t(std::numeric_limits<I>::max()));
268+
static_assert(std::numeric_limits<I>::max() == std::numeric_limits<uint64_t>::max() >> (64 - BITS));
269+
return I(Impl().template randbits<BITS>());
270+
}
271+
287272
/** Generate random bytes. */
288273
template <BasicByte B = unsigned char>
289274
std::vector<B> randbytes(size_t len) noexcept
@@ -441,6 +426,33 @@ void Shuffle(I first, I last, R&& rng)
441426
}
442427
}
443428

429+
/** Generate a uniform random integer of type T in the range [0..nMax)
430+
* Precondition: nMax > 0, T is an integral type, no larger than uint64_t
431+
*/
432+
template<typename T>
433+
T GetRand(T nMax) noexcept {
434+
return T(FastRandomContext().randrange(nMax));
435+
}
436+
437+
/** Generate a uniform random integer of type T in its entire non-negative range. */
438+
template<typename T>
439+
T GetRand() noexcept {
440+
return T(FastRandomContext().rand<T>());
441+
}
442+
443+
/** Generate a uniform random duration in the range [0..max). Precondition: max.count() > 0 */
444+
template <typename D>
445+
D GetRandomDuration(typename std::common_type<D>::type max) noexcept
446+
// Having the compiler infer the template argument from the function argument
447+
// is dangerous, because the desired return value generally has a different
448+
// type than the function argument. So std::common_type is used to force the
449+
// call site to specify the type of the return value.
450+
{
451+
return D{GetRand(max.count())};
452+
};
453+
constexpr auto GetRandMicros = GetRandomDuration<std::chrono::microseconds>;
454+
constexpr auto GetRandMillis = GetRandomDuration<std::chrono::milliseconds>;
455+
444456
/* Number of random bytes returned by GetOSRand.
445457
* When changing this constant make sure to change all call sites, and make
446458
* sure that the underlying OS APIs for all platforms support the number.

src/test/util/net.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ std::vector<NodeEvictionCandidate> GetRandomNodeEvictionCandidates(int n_candida
126126
/*fRelevantServices=*/random_context.randbool(),
127127
/*m_relay_txs=*/random_context.randbool(),
128128
/*fBloomFilter=*/random_context.randbool(),
129-
/*nKeyedNetGroup=*/random_context.randrange(100),
129+
/*nKeyedNetGroup=*/random_context.randrange(100u),
130130
/*prefer_evict=*/random_context.randbool(),
131131
/*m_is_local=*/random_context.randbool(),
132132
/*m_network=*/ALL_NETWORKS[random_context.randrange(ALL_NETWORKS.size())],

0 commit comments

Comments
 (0)