diff --git a/src/math.rs b/src/math.rs index ed3d2d5567..8422e7ccb6 100644 --- a/src/math.rs +++ b/src/math.rs @@ -3,7 +3,11 @@ use rand::distributions::Distribution as _; use rustc_apfloat::Float as _; use rustc_apfloat::ieee::IeeeFloat; -/// Disturbes a floating-point result by a relative error on the order of (-2^scale, 2^scale). +/// Disturbes a floating-point result by a relative error in the range (-2^scale, 2^scale). +/// +/// For a 2^N ULP error, you can use an `err_scale` of `-(F::PRECISION - 1 - N)`. +/// In other words, a 1 ULP (absolute) error is the same as a `2^-(F::PRECISION-1)` relative error. +/// (Subtracting 1 compensates for the integer bit.) pub(crate) fn apply_random_float_error( ecx: &mut crate::MiriInterpCx<'_>, val: F, @@ -11,10 +15,14 @@ pub(crate) fn apply_random_float_error( ) -> F { let rng = ecx.machine.rng.get_mut(); // Generate a random integer in the range [0, 2^PREC). + // (When read as binary, the position of the first `1` determines the exponent, + // and the remaining bits fill the mantissa. `PREC` is one plus the size of the mantissa, + // so this all works out.) let dist = rand::distributions::Uniform::new(0, 1 << F::PRECISION); - let err = F::from_u128(dist.sample(rng)) - .value - .scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap())); + let r = F::from_u128(dist.sample(rng)).value; + // Multiply this with 2^(scale - PREC). The result is between 0 and + // 2^PREC * 2^(scale - PREC) = 2^scale. + let err = r.scalbn(err_scale.strict_sub(F::PRECISION.try_into().unwrap())); // give it a random sign let err = if rng.gen::() { -err } else { err }; // multiple the value with (1+err)