diff --git a/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs b/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs index 8de954ae332c..8da635114d2f 100644 --- a/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs +++ b/library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs @@ -1,11 +1,11 @@ //! A generator that checks a handful of cases near infinities, zeros, asymptotes, and NaNs. -use libm::support::{Float, Int}; +use libm::support::{CastInto, Float, Int}; use crate::domain::get_domain; use crate::gen::KnownSize; use crate::run_cfg::{check_near_count, check_point_count}; -use crate::{CheckCtx, FloatExt, MathOp, test_log}; +use crate::{BaseName, CheckCtx, FloatExt, FloatTy, MathOp, test_log}; /// Generate a sequence of edge cases, e.g. numbers near zeroes and infiniteis. pub trait EdgeCaseInput { @@ -78,7 +78,7 @@ where (ret.into_iter(), count) } -/// Add `AROUND` values starting at and including `x` and counting up. Uses the smallest possible +/// Add `points` values starting at and including `x` and counting up. Uses the smallest possible /// increments (1 ULP). fn count_up(mut x: F, points: u64, values: &mut Vec) { assert!(!x.is_nan()); @@ -91,7 +91,7 @@ fn count_up(mut x: F, points: u64, values: &mut Vec) { } } -/// Add `AROUND` values starting at and including `x` and counting down. Uses the smallest possible +/// Add `points` values starting at and including `x` and counting down. Uses the smallest possible /// increments (1 ULP). fn count_down(mut x: F, points: u64, values: &mut Vec) { assert!(!x.is_nan()); @@ -107,31 +107,87 @@ fn count_down(mut x: F, points: u64, values: &mut Vec) { /// Create a list of values around interesting integer points (min, zero, max). pub fn int_edge_cases( ctx: &CheckCtx, - _argnum: usize, -) -> (impl Iterator + Clone, u64) { + argnum: usize, +) -> (impl Iterator + Clone, u64) +where + i32: CastInto, +{ let mut values = Vec::new(); let near_points = check_near_count(ctx); - for up_from in [I::MIN, I::ZERO] { - let mut x = up_from; - for _ in 0..near_points { - values.push(x); - x += I::ONE; - } - } + // Check around max/min and zero + int_count_around(I::MIN, near_points, &mut values); + int_count_around(I::MAX, near_points, &mut values); + int_count_around(I::ZERO, near_points, &mut values); + int_count_around(I::ZERO, near_points, &mut values); - for down_from in [I::ZERO, I::MAX] { - let mut x = down_from; - for _ in 0..near_points { - values.push(x); - x -= I::ONE; - } + if matches!(ctx.base_name, BaseName::Scalbn | BaseName::Ldexp) { + assert_eq!(argnum, 1, "scalbn integer argument should be arg1"); + let (emax, emin, emin_sn) = match ctx.fn_ident.math_op().float_ty { + FloatTy::F16 => { + #[cfg(not(f16_enabled))] + unreachable!(); + #[cfg(f16_enabled)] + (f16::EXP_MAX, f16::EXP_MIN, f16::EXP_MIN_SUBNORM) + } + FloatTy::F32 => (f32::EXP_MAX, f32::EXP_MIN, f32::EXP_MIN_SUBNORM), + FloatTy::F64 => (f64::EXP_MAX, f64::EXP_MIN, f64::EXP_MIN_SUBNORM), + FloatTy::F128 => { + #[cfg(not(f128_enabled))] + unreachable!(); + #[cfg(f128_enabled)] + (f128::EXP_MAX, f128::EXP_MIN, f128::EXP_MIN_SUBNORM) + } + }; + + // `scalbn`/`ldexp` have their trickiest behavior around exponent limits + int_count_around(emax.cast(), near_points, &mut values); + int_count_around(emin.cast(), near_points, &mut values); + int_count_around(emin_sn.cast(), near_points, &mut values); + int_count_around((-emin_sn).cast(), near_points, &mut values); + + // Also check values that cause the maximum possible difference in exponents + int_count_around((emax - emin).cast(), near_points, &mut values); + int_count_around((emin - emax).cast(), near_points, &mut values); + int_count_around((emax - emin_sn).cast(), near_points, &mut values); + int_count_around((emin_sn - emax).cast(), near_points, &mut values); } values.sort(); values.dedup(); - let len = values.len().try_into().unwrap(); - (values.into_iter(), len) + let count = values.len().try_into().unwrap(); + + test_log(&format!( + "{gen_kind:?} {basis:?} {fn_ident} arg {arg}/{args}: {count} edge cases", + gen_kind = ctx.gen_kind, + basis = ctx.basis, + fn_ident = ctx.fn_ident, + arg = argnum + 1, + args = ctx.input_count(), + )); + + (values.into_iter(), count) +} + +/// Add `points` values both up and down, starting at and including `x`. +fn int_count_around(x: I, points: u64, values: &mut Vec) { + let mut current = x; + for _ in 0..points { + values.push(current); + current = match current.checked_add(I::ONE) { + Some(v) => v, + None => break, + }; + } + + current = x; + for _ in 0..points { + values.push(current); + current = match current.checked_sub(I::ONE) { + Some(v) => v, + None => break, + }; + } } macro_rules! impl_edge_case_input { diff --git a/library/compiler-builtins/libm/src/math/generic/scalbn.rs b/library/compiler-builtins/libm/src/math/generic/scalbn.rs index f15cb75d6312..5ba7f2ab2d5f 100644 --- a/library/compiler-builtins/libm/src/math/generic/scalbn.rs +++ b/library/compiler-builtins/libm/src/math/generic/scalbn.rs @@ -28,8 +28,8 @@ where let sig_total_bits = F::SIG_BITS + 1; // Maximum and minimum values when biased - let exp_max: i32 = F::EXP_BIAS as i32; - let exp_min = -(exp_max - 1); + let exp_max = F::EXP_MAX; + let exp_min = F::EXP_MIN; // 2 ^ Emax, maximum positive with null significand (0x1p1023 for f64) let f_exp_max = F::from_parts(false, F::EXP_BIAS << 1, zero); diff --git a/library/compiler-builtins/libm/src/math/support/float_traits.rs b/library/compiler-builtins/libm/src/math/support/float_traits.rs index 328b70610613..d6ce13f69073 100644 --- a/library/compiler-builtins/libm/src/math/support/float_traits.rs +++ b/library/compiler-builtins/libm/src/math/support/float_traits.rs @@ -59,6 +59,15 @@ pub trait Float: /// The exponent bias value const EXP_BIAS: u32 = Self::EXP_SAT >> 1; + /// Maximum unbiased exponent value. + const EXP_MAX: i32 = Self::EXP_BIAS as i32; + + /// Minimum *NORMAL* unbiased exponent value. + const EXP_MIN: i32 = -(Self::EXP_MAX - 1); + + /// Minimum subnormal exponent value. + const EXP_MIN_SUBNORM: i32 = Self::EXP_MIN - Self::SIG_BITS as i32; + /// A mask for the sign bit const SIGN_MASK: Self::Int; @@ -274,6 +283,9 @@ mod tests { // Constants assert_eq!(f16::EXP_SAT, 0b11111); assert_eq!(f16::EXP_BIAS, 15); + assert_eq!(f16::EXP_MAX, 15); + assert_eq!(f16::EXP_MIN, -14); + assert_eq!(f16::EXP_MIN_SUBNORM, -24); // `exp_unbiased` assert_eq!(f16::FRAC_PI_2.exp_unbiased(), 0); @@ -296,6 +308,9 @@ mod tests { // Constants assert_eq!(f32::EXP_SAT, 0b11111111); assert_eq!(f32::EXP_BIAS, 127); + assert_eq!(f32::EXP_MAX, 127); + assert_eq!(f32::EXP_MIN, -126); + assert_eq!(f32::EXP_MIN_SUBNORM, -149); // `exp_unbiased` assert_eq!(f32::FRAC_PI_2.exp_unbiased(), 0); @@ -319,6 +334,9 @@ mod tests { // Constants assert_eq!(f64::EXP_SAT, 0b11111111111); assert_eq!(f64::EXP_BIAS, 1023); + assert_eq!(f64::EXP_MAX, 1023); + assert_eq!(f64::EXP_MIN, -1022); + assert_eq!(f64::EXP_MIN_SUBNORM, -1074); // `exp_unbiased` assert_eq!(f64::FRAC_PI_2.exp_unbiased(), 0); @@ -343,6 +361,9 @@ mod tests { // Constants assert_eq!(f128::EXP_SAT, 0b111111111111111); assert_eq!(f128::EXP_BIAS, 16383); + assert_eq!(f128::EXP_MAX, 16383); + assert_eq!(f128::EXP_MIN, -16382); + assert_eq!(f128::EXP_MIN_SUBNORM, -16494); // `exp_unbiased` assert_eq!(f128::FRAC_PI_2.exp_unbiased(), 0);