Add better edge case testing for scalbn

Include integer values around the minimum and maximum exponents which
require different behavior in the scale functions.
This commit is contained in:
Trevor Gross 2025-02-07 00:47:00 +00:00 committed by Trevor Gross
parent 9223d60dfa
commit aac4901953
3 changed files with 100 additions and 23 deletions

View file

@ -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;
}
}
// 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<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 {

View file

@ -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);

View file

@ -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);