about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--library/compiler-builtins/libm/crates/libm-test/src/gen/edge_cases.rs100
-rw-r--r--library/compiler-builtins/libm/src/math/generic/scalbn.rs4
-rw-r--r--library/compiler-builtins/libm/src/math/support/float_traits.rs21
3 files changed, 101 insertions, 24 deletions
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 8de954ae332..8da635114d2 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<Op> {
@@ -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<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
     assert!(!x.is_nan());
@@ -91,7 +91,7 @@ fn count_up<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
     }
 }
 
-/// 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<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
     assert!(!x.is_nan());
@@ -107,31 +107,87 @@ fn count_down<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
 /// Create a list of values around interesting integer points (min, zero, max).
 pub fn int_edge_cases<I: Int>(
     ctx: &CheckCtx,
-    _argnum: usize,
-) -> (impl Iterator<Item = I> + Clone, u64) {
+    argnum: usize,
+) -> (impl Iterator<Item = I> + Clone, u64)
+where
+    i32: CastInto<I>,
+{
     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;
-        }
-    }
-
-    for down_from in [I::ZERO, I::MAX] {
-        let mut x = down_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);
+
+    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<I: Int>(x: I, points: u64, values: &mut Vec<I>) {
+    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 f15cb75d631..5ba7f2ab2d5 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 328b7061061..d6ce13f6907 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);