-
Notifications
You must be signed in to change notification settings - Fork 432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement HighPrecision01 distribution #320
Conversation
New error handling + type
Implement Rng for Box with alloc feature
CloudABI has a special system call, random_get(), that can be used to obtain random data from the environment (the kernel, emulator, etc). We can invoke this system call by making use of the cloudabi crate.
Port rand to CloudABI.
Fix documentation warnings
Copied from dhardy/master, minus single-read functions, with extra tests
The planned switch to 256-bit byte array seeds does not allow such long seeds.
It's not being used anyway unless the std feature is enabled
@tspiteri can I interest you in reviewing? |
I had a thought: we could do extra testing with biased generators. E.g. if an RNG generated four numbers and XORed all, I think the mean output from this algorithm should be 2^-4 = 1/16. This might let us better test extremely unlikely outputs? |
Continuing our discussion about using a wrapper type...
So using unique types for high-precision stuff is probably the best approach. In which case we might have to choose between something like one of the following: // generic wrapper type:
let range = Range::new(HighPrecision(-1.0), HighPrecision(1.0));
let range = Range::new(HP(-1.0), HP(1.0));
// specific wrapper types:
let range = Range::new(f64(-1.0), f64(1.0)); I think possibly Also @burdges perhaps you have a better idea of how to parametrise Range to use a different, high-precision, method internally? |
Continuing from #326 (comment): I am slowly coming around to the idea of using a wrapper type. Would then something like this work? let value = rng.gen::<HighPrecision<f32>>().0;
let value_in_range = rng.gen_range::<HighPrecision<f32>>(-10.0, 10.0).0; Still slightly ugly, but no extra distributions. |
Ah, that is how it would look. Again seems ugly to me. That wrapper type has no meaning for the input values. |
I don't really follow what you are suggesting. |
One other thing I am thinking about (directly copied from the old
So our But I am not sure this is something we want to fix. It would require an extra branch, and taking one bit from the exponent, which seems like it would complicate the function a lot. The worst case is |
So "0.5" represents the space between 0.5 and 0.5+ε/2, whereas the next smallest number represents the space between 0.5-ε/4 and 0.5; indeed we always round down instead of rounding up 50% of the time. I guess a "fix" would be to have a 50% chance of adding 1 to the fractional part, and dealing with the case it wraps to zero properly (increase exponent). But should we?
So I'd say no. |
You are right that the wrapper type solution is ugly, but the alternatives are:
The latter actually sounds reasonable, don't you think? Obviously this means it would never be used by I am wondering though if we should offer two versions of |
In my mind having two distributions, I wonder if we could even make do with a distribution We don't need to support all methods from |
It's not going to be |
exp += bits.trailing_zeros() as i32; | ||
// If RNG were guaranteed unbiased we could skip the | ||
// check against exp; unfortunately it may be. | ||
// Worst case ("zeros" RNG) has recursion depth 16. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The recursion can be removed easily. The contents of fallback
can look like the code below, which I also find a little easier to understand.
// Worst case ("zeros" RNG) will have 16 iterations (f64 exp bias / 64).
loop {
let bits = rng.gen::<$uty>();
exp += bits.trailing_zeros() as i32;
// if exp reaches bias, return a subnormal that can be zero
if exp >= $exponent_bias {
return fraction.into_float_with_exponent(-$exponent_bias);
}
if bits != 0 {
return fraction.into_float_with_exponent(-exp);
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually I just thought the recursion was neater since @pitdicker recommended this bit be in a separate function anyway (not inlined so that the common code is small). Since it will almost never recurse I don't see a problem with that strategy. But using a loop also works (was used in an older commit).
I have been thinking about how to do a range implementation that can have precision like Take for example a range like When we only used 32/64 bits to generate the float this seemed acceptable. But if we call this 'as much precision as the floating-point format can represent', it gets messy. Is it instead possible to generate values directly inside the target range? I think it is possible, with only a very small part of the values that have to be rejected. What is needed (for
Let's assume the
I think this would give us full precision, without the rounding problems. |
Sounds very good! Can we also eliminate the sign bit if not needed? If so I suspect this can be as fast as the |
On second thoughts, it sounds complicated without having large rejection zones (and if you consider a range like I don't think your zeroing trick can work without bias. Numbers from What's your second idea — sample the sign bit and the first exponent bit, and if both are 1 (i.e. range This gives some improvement to the above range So it sounds to me like any perfect generation method would be slow. There's also closed ranges to consider: ideally we would sample from All FP arithmetic is designed to be lossy anyway once the maximum representable precision is exceeded. Perfectly producing random values seems like a lot of work to save one extra bit of precision — and if users care about precision they will already be using |
This seems to be settled, but just a small note: I agree that just one bit of precision is not worth the extra complexity for ranges that are not from 0 to 1; using |
Replying to my own comment, But handling such cases should probably be left to the user of the library. In that particular example, if the library user really wants high precision in the range -1 to 1, they can just generate a number from 0.0 to 1.0 and then generate the sign using a random |
No, this isn't settled, just recreated (#372) due to history cleanup. @pitdicker already implemented a method to maintain (some) precision close to 0; see https://github.com/dhardy/rand/blob/experimental/src/distributions/range.rs#L432. I guess we will use that or similar code. |
Separated out from #308.