rust-random / rand

A Rust library for random number generation.
https://crates.io/crates/rand
Other
1.67k stars 431 forks source link

StepRng almost always produces bool false #1303

Closed jayvdb closed 1 year ago

jayvdb commented 1 year ago

I was trying to use StepRng to produce bool true, and found that I much harder than expected. i.e. the following assertion is almost always true.

for i in 0..usize::MAX as u64 {
    for j in [0, 1, i] {
        let mut rng = rand::rngs::mock::StepRng::new(i, j);
        for k in 0..1000 {
            let foo: bool = rng.gen();
            if foo {
                eprintln!("{i} {j} {k}");
            }

            assert!(!foo);
        }
    }
}

I was able to get a true when "i=2147484" "j=2147484" & "k=999".

(edit: it becomes true when the start is 4013646938112, and then is consistently true, for a while at least)

I guess based on response for https://github.com/rust-random/rand/issues/1248 that there isn't much love for StepRng, and maybe this is just an unfortunate effect of StepRng

My real motivation is looking for a solution to https://github.com/cksac/fake-rs/issues/128. I am trying to build a generator that is reasonably random most of the time, except when asked to produce a bool, it should always produce true. Is this possible?

My crude guess was if I could eliminate 0 from the rng responses, this would eliminate generating false, and StepRng seemed like a useful way to experiment with this idea.

WarrenWeckesser commented 1 year ago

The result will be true when bit 31 of the internal u64 counter is 1. For example, this will print all true values:

    let initial = 1u64 << 31;
    let increment = 0;
    let mut rng = rand::rngs::mock::StepRng::new(initial, increment);
    for _ in 0..8 {
        let foo: bool = rng.gen();
        println!("{}", foo);
    }

If you set initial to 0 and increment to 1u64 << 31, then each time the internal value is incremented, bit 31 changes, so this will print alternating false and true, starting with false:

    let initial = 0u64;
    let increment = 1u64 << 31;
    let mut rng = rand::rngs::mock::StepRng::new(initial, increment);
    for _ in 0..8 {
        let foo: bool = rng.gen();
        println!("{}", foo);
    }
jayvdb commented 1 year ago

Thank you! Perhaps it could be documented better? Not specifically explaining this scenario involving bools, but how using this struct relates to the internal logic of rand to produce values other than f64 (update: I should have wrote u64). Note I haven't read all of the Rust Rand Book , so maybe it is adequately explained elsewhere. Maybe all that is needed is a link on https://docs.rs/rand/latest/rand/rngs/mock/struct.StepRng.html with a note that types other than f64 behave in a non-intuitive fashion and "go read X to understand caveats for non-f64 (update: I should have wrote u64) usage".

Are there any other tools in the rand toolbox which can be used to produce random values, except always produce true for bools?

dhardy commented 1 year ago

but how using this struct relates to the internal logic of rand to produce values other than f64

This RNG only directly implements next_u64 (and next_u32 as a cast). Everything else is implemented elsewhere, and value-stability rules mean the result won't change in a patch release but could in any other release.

In other words, by documenting this we might give users the false expectation that behaviour is stable. It's not (though in practice it won't change often).

jayvdb commented 1 year ago

What about a note something like

While this RNG does step by increment for signed & unsigned int data types, it may not step by that increment for other types, such as bool, float.