fn ulp_of_u64(magnitude: u64) -> u64 {
let magnitude_f64 = magnitude.max(1) as f64;
let spacing = magnitude_f64.next_up() - magnitude_f64;
spacing.max(1.0) as u64
}
pub fn max_ulp_tolerance(candidate: u64, oracle: u64) -> u64 {
let mag = candidate.max(oracle);
let ulp = ulp_of_u64(mag);
ulp.saturating_mul(4)
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn ulp_standard_calc() {
assert_eq!(ulp_of_u64(0), 1);
assert_eq!(ulp_of_u64(1), 1);
assert_eq!(ulp_of_u64((1u64 << 53) - 1), 1);
assert_eq!(ulp_of_u64(1u64 << 53), 2);
assert_eq!(ulp_of_u64(u64::MAX), 4096);
}
#[test]
fn tolerance_small_magnitudes_use_single_ulp() {
assert_eq!(max_ulp_tolerance(0, 0), 4);
assert_eq!(max_ulp_tolerance(0, 1), 4);
assert_eq!(max_ulp_tolerance((1u64 << 53) - 1, 1), 4);
}
#[test]
fn tolerance_scales_with_magnitude_powers_of_two() {
let below_2_53 = max_ulp_tolerance((1u64 << 53) - 1, 0); let at_2_53 = max_ulp_tolerance(1u64 << 53, 0); let at_2_54 = max_ulp_tolerance(1u64 << 54, 0); let at_2_55 = max_ulp_tolerance(1u64 << 55, 0);
assert_eq!(below_2_53, 4); assert_eq!(at_2_53, 8); assert_eq!(at_2_54, 16); assert_eq!(at_2_55, 32); }
#[test]
fn tolerance_uses_larger_of_two_results_and_is_symmetric() {
let small = 1u64;
let large = 1u64 << 53;
let ab = max_ulp_tolerance(small, large);
let ba = max_ulp_tolerance(large, small);
assert_eq!(ab, ba);
let big_only = max_ulp_tolerance(large, large);
assert_eq!(ab, big_only);
}
#[test]
fn tolerance_at_u64_max_matches_expected_ulp() {
assert_eq!(max_ulp_tolerance(u64::MAX, 0), 4096 * 4);
assert_eq!(max_ulp_tolerance(0, u64::MAX), 4096 * 4);
}
}