You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
StepRng always returns lower bound on random_range #1633
When I tried to use StepRng::new(1, 0) to test a special case of my crate, I find out the result of rng.random_range(0..=N) would always be 0.
After some experiments, I figure out the returned value has nothing to do with the initial value and the step of StepRng. The returned value is a constant, and it is the lower bound of the given range in random_range's parameter.
What behaviour is expected, and why?
The result of rng.random_range(0..=N) would return k for let mut rng = StepRng::new(k, 0) when k is in the range of 0..=N. Otherwise it is an UB.
This generates an arithmetic sequence (i.e. adds a constant each step) over a u64 number,
using wrapping arithmetic. If the increment is 0 the generator yields a constant.
So yes this is the correct behavior and StepRng is absolutely not supposed as a random source for any purpose, that why it is under mock
Moreover, when STEP has been changed to non-zero, the generator would yield increasing number on rng.random(), but the number from rng.random_range(LOWER_BOUND..=UPPER_BOUND) would always be the same.
The random generator yields a value in the range 0..u64::MAX. If you want to sample something else from it you need to map every of these number to an output.
If you want to sample a random u64 this is very easy, you just use the identity which is also the choice we made here (but you could also do something different). There are not guaranties made about this.
If you want to sample a random number in the range 0..100, you also have to map every possible u64 value to a number in 0..100, so this means a lot of numbers will map to the same output. If the mapping is good, each output will have the same amount of u64 values mapping to it.
In this case a whole range of consecutive numbers are mapped to the same output. This is absolutely ok, because the source random u64 is assumed to be random. This breaks down for StepRng, which as I said is not a suitable source of randomness.
If you put a big enough number into StepRng you see, that it does not always sample the lowest value.
There is no specification on this behaviour. In particular, there are no expectations on random algorithms when their input is not random.
However, the constant yielded from rng.random() and rng.random_range(LOWER_BOUND..=UPPER_BOUND) are not the same.
Again, there's no specification on this (beyond the output being within the specified range).
You're welcome to consider probing our random algorithms using StepRng, but for the most part don't expect to find anything useful (I mean, the code for those algorithms is already open source). Usually we use a small pseudo-random generator (e.g. SmallRng, Pcg32, Xoshiro...) with seed_from_u64 for testing.
The result of rng.random_range(0..=N) would return k for let mut rng = StepRng::new(k, 0) when k is in the range of 0..=N. Otherwise it is an UB.
This demonstrates a complete mis-understanding of the way generators and algorithms work. Generators may yield any number within their output range (e.g. next_u64() may yield anything from 0 to u64::MAX). Algorithms like random_rangemust transform any such number into a valid output for their distribution (in this case, the range 0..=N with uniform sampling), though the algorithm may draw multiple RNG samples to do this. So there must not be UB.
Also, it's completely undefined howrandom_range maps from the RNG's output to its own output. We don't use n % range.
Activity
benjamin-lieser commentedon Apr 21, 2025
If you read the documentation it says
So yes this is the correct behavior and
StepRngis absolutely not supposed as a random source for any purpose, that why it is undermockTypas commentedon Apr 21, 2025
I know it yields a constant. However, the constant yielded from
rng.random()andrng.random_range(LOWER_BOUND..=UPPER_BOUND)are not the same.Let's have an example
Moreover, when
STEPhas been changed to non-zero, the generator would yield increasing number onrng.random(), but the number fromrng.random_range(LOWER_BOUND..=UPPER_BOUND)would always be the same.Here's the example
https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=2c17c21898aeb374e0a480537b25fdfe
benjamin-lieser commentedon Apr 22, 2025
The random generator yields a value in the range
0..u64::MAX. If you want to sample something else from it you need to map every of these number to an output.If you want to sample a random
u64this is very easy, you just use the identity which is also the choice we made here (but you could also do something different). There are not guaranties made about this.If you want to sample a random number in the range
0..100, you also have to map every possibleu64value to a number in0..100, so this means a lot of numbers will map to the same output. If the mapping is good, each output will have the same amount ofu64values mapping to it.In this case a whole range of consecutive numbers are mapped to the same output. This is absolutely ok, because the source random
u64is assumed to be random. This breaks down forStepRng, which as I said is not a suitable source of randomness.If you put a big enough number into
StepRngyou see, that it does not always sample the lowest value.dhardy commentedon Apr 22, 2025
There is no specification on this behaviour. In particular, there are no expectations on random algorithms when their input is not random.
Again, there's no specification on this (beyond the output being within the specified range).
You're welcome to consider probing our random algorithms using
StepRng, but for the most part don't expect to find anything useful (I mean, the code for those algorithms is already open source). Usually we use a small pseudo-random generator (e.g.SmallRng,Pcg32,Xoshiro...) withseed_from_u64for testing.dhardy commentedon Apr 22, 2025
This demonstrates a complete mis-understanding of the way generators and algorithms work. Generators may yield any number within their output range (e.g.
next_u64()may yield anything from0tou64::MAX). Algorithms likerandom_rangemust transform any such number into a valid output for their distribution (in this case, the range0..=Nwith uniform sampling), though the algorithm may draw multiple RNG samples to do this. So there must not be UB.Also, it's completely undefined how
random_rangemaps from the RNG's output to its own output. We don't usen % range.newpavlov commentedon Apr 22, 2025
Maybe we should deprecate
StepRng? It's not the first time users complain about its output despite the docs warnings.dhardy commentedon Apr 22, 2025
I think we should: I can't think of any case where it's actually useful.