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:
parent
9223d60dfa
commit
aac4901953
3 changed files with 100 additions and 23 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue