Skip to content

Commit 724e47d

Browse files
committed
Add better edge case testing for scalbn
Include integer values around the minimum and maximum exponents which require different behavior in the scale functions.
1 parent c967c7e commit 724e47d

File tree

3 files changed

+101
-24
lines changed

3 files changed

+101
-24
lines changed

crates/libm-test/src/gen/edge_cases.rs

Lines changed: 78 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
//! A generator that checks a handful of cases near infinities, zeros, asymptotes, and NaNs.
22
3-
use libm::support::{Float, Int};
3+
use libm::support::{CastInto, Float, Int};
44

55
use crate::domain::get_domain;
66
use crate::gen::KnownSize;
77
use crate::run_cfg::{check_near_count, check_point_count};
8-
use crate::{CheckCtx, FloatExt, MathOp, test_log};
8+
use crate::{BaseName, CheckCtx, FloatExt, FloatTy, MathOp, test_log};
99

1010
/// Generate a sequence of edge cases, e.g. numbers near zeroes and infiniteis.
1111
pub trait EdgeCaseInput<Op> {
@@ -78,7 +78,7 @@ where
7878
(ret.into_iter(), count)
7979
}
8080

81-
/// Add `AROUND` values starting at and including `x` and counting up. Uses the smallest possible
81+
/// Add `points` values starting at and including `x` and counting up. Uses the smallest possible
8282
/// increments (1 ULP).
8383
fn count_up<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
8484
assert!(!x.is_nan());
@@ -91,7 +91,7 @@ fn count_up<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
9191
}
9292
}
9393

94-
/// Add `AROUND` values starting at and including `x` and counting down. Uses the smallest possible
94+
/// Add `points` values starting at and including `x` and counting down. Uses the smallest possible
9595
/// increments (1 ULP).
9696
fn count_down<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
9797
assert!(!x.is_nan());
@@ -107,31 +107,87 @@ fn count_down<F: Float>(mut x: F, points: u64, values: &mut Vec<F>) {
107107
/// Create a list of values around interesting integer points (min, zero, max).
108108
pub fn int_edge_cases<I: Int>(
109109
ctx: &CheckCtx,
110-
_argnum: usize,
111-
) -> (impl Iterator<Item = I> + Clone, u64) {
110+
argnum: usize,
111+
) -> (impl Iterator<Item = I> + Clone, u64)
112+
where
113+
i32: CastInto<I>,
114+
{
112115
let mut values = Vec::new();
113116
let near_points = check_near_count(ctx);
114117

115-
for up_from in [I::MIN, I::ZERO] {
116-
let mut x = up_from;
117-
for _ in 0..near_points {
118-
values.push(x);
119-
x += I::ONE;
120-
}
121-
}
122-
123-
for down_from in [I::ZERO, I::MAX] {
124-
let mut x = down_from;
125-
for _ in 0..near_points {
126-
values.push(x);
127-
x -= I::ONE;
128-
}
118+
// Check around max/min and zero
119+
int_count_around(I::MIN, near_points, &mut values);
120+
int_count_around(I::MAX, near_points, &mut values);
121+
int_count_around(I::ZERO, near_points, &mut values);
122+
int_count_around(I::ZERO, near_points, &mut values);
123+
124+
if matches!(ctx.base_name, BaseName::Scalbn | BaseName::Ldexp) {
125+
assert_eq!(argnum, 1, "scalbn integer argument should be arg1");
126+
let (emax, emin, emin_sn) = match ctx.fn_ident.math_op().float_ty {
127+
FloatTy::F16 => {
128+
#[cfg(not(f16_enabled))]
129+
unreachable!();
130+
#[cfg(f16_enabled)]
131+
(f16::EXP_MAX, f16::EXP_MIN, f16::EXP_MIN_SUBNORM)
132+
}
133+
FloatTy::F32 => (f32::EXP_MAX, f32::EXP_MIN, f32::EXP_MIN_SUBNORM),
134+
FloatTy::F64 => (f64::EXP_MAX, f64::EXP_MIN, f64::EXP_MIN_SUBNORM),
135+
FloatTy::F128 => {
136+
#[cfg(not(f128_enabled))]
137+
unreachable!();
138+
#[cfg(f128_enabled)]
139+
(f128::EXP_MAX, f128::EXP_MIN, f128::EXP_MIN_SUBNORM)
140+
}
141+
};
142+
143+
// `scalbn`/`ldexp` have their trickiest behavior around exponent limits
144+
int_count_around(emax.cast(), near_points, &mut values);
145+
int_count_around(emin.cast(), near_points, &mut values);
146+
int_count_around(emin_sn.cast(), near_points, &mut values);
147+
int_count_around((-emin_sn).cast(), near_points, &mut values);
148+
149+
// Also check values that cause the maximum possible difference in exponents
150+
int_count_around((emax - emin).cast(), near_points, &mut values);
151+
int_count_around((emin - emax).cast(), near_points, &mut values);
152+
int_count_around((emax - emin_sn).cast(), near_points, &mut values);
153+
int_count_around((emin_sn - emax).cast(), near_points, &mut values);
129154
}
130155

131156
values.sort();
132157
values.dedup();
133-
let len = values.len().try_into().unwrap();
134-
(values.into_iter(), len)
158+
let count = values.len().try_into().unwrap();
159+
160+
test_log(&format!(
161+
"{gen_kind:?} {basis:?} {fn_ident} arg {arg}/{args}: {count} edge cases",
162+
gen_kind = ctx.gen_kind,
163+
basis = ctx.basis,
164+
fn_ident = ctx.fn_ident,
165+
arg = argnum + 1,
166+
args = ctx.input_count(),
167+
));
168+
169+
(values.into_iter(), count)
170+
}
171+
172+
/// Add `points` values both up and down, starting at and including `x`.
173+
fn int_count_around<I: Int>(x: I, points: u64, values: &mut Vec<I>) {
174+
let mut current = x;
175+
for _ in 0..points {
176+
values.push(current);
177+
current = match current.checked_add(I::ONE) {
178+
Some(v) => v,
179+
None => break,
180+
};
181+
}
182+
183+
current = x;
184+
for _ in 0..points {
185+
values.push(current);
186+
current = match current.checked_sub(I::ONE) {
187+
Some(v) => v,
188+
None => break,
189+
};
190+
}
135191
}
136192

137193
macro_rules! impl_edge_case_input {

src/math/generic/scalbn.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ where
2828
let sig_total_bits = F::SIG_BITS + 1;
2929

3030
// Maximum and minimum values when biased
31-
let exp_max: i32 = F::EXP_BIAS as i32;
32-
let exp_min = -(exp_max - 1);
31+
let exp_max = F::EXP_MAX;
32+
let exp_min = F::EXP_MIN;
3333

3434
// 2 ^ Emax, maximum positive with null significand (0x1p1023 for f64)
3535
let f_exp_max = F::from_parts(false, F::EXP_BIAS << 1, zero);

src/math/support/float_traits.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,15 @@ pub trait Float:
5959
/// The exponent bias value
6060
const EXP_BIAS: u32 = Self::EXP_SAT >> 1;
6161

62+
/// Maximum unbiased exponent value.
63+
const EXP_MAX: i32 = Self::EXP_BIAS as i32;
64+
65+
/// Minimum *NORMAL* unbiased exponent value.
66+
const EXP_MIN: i32 = -(Self::EXP_MAX - 1);
67+
68+
/// Minimum subnormal exponent value.
69+
const EXP_MIN_SUBNORM: i32 = Self::EXP_MIN - Self::SIG_BITS as i32;
70+
6271
/// A mask for the sign bit
6372
const SIGN_MASK: Self::Int;
6473

@@ -274,6 +283,9 @@ mod tests {
274283
// Constants
275284
assert_eq!(f16::EXP_SAT, 0b11111);
276285
assert_eq!(f16::EXP_BIAS, 15);
286+
assert_eq!(f16::EXP_MAX, 15);
287+
assert_eq!(f16::EXP_MIN, -14);
288+
assert_eq!(f16::EXP_MIN_SUBNORM, -24);
277289

278290
// `exp_unbiased`
279291
assert_eq!(f16::FRAC_PI_2.exp_unbiased(), 0);
@@ -296,6 +308,9 @@ mod tests {
296308
// Constants
297309
assert_eq!(f32::EXP_SAT, 0b11111111);
298310
assert_eq!(f32::EXP_BIAS, 127);
311+
assert_eq!(f32::EXP_MAX, 127);
312+
assert_eq!(f32::EXP_MIN, -126);
313+
assert_eq!(f32::EXP_MIN_SUBNORM, -149);
299314

300315
// `exp_unbiased`
301316
assert_eq!(f32::FRAC_PI_2.exp_unbiased(), 0);
@@ -319,6 +334,9 @@ mod tests {
319334
// Constants
320335
assert_eq!(f64::EXP_SAT, 0b11111111111);
321336
assert_eq!(f64::EXP_BIAS, 1023);
337+
assert_eq!(f64::EXP_MAX, 1023);
338+
assert_eq!(f64::EXP_MIN, -1022);
339+
assert_eq!(f64::EXP_MIN_SUBNORM, -1074);
322340

323341
// `exp_unbiased`
324342
assert_eq!(f64::FRAC_PI_2.exp_unbiased(), 0);
@@ -343,6 +361,9 @@ mod tests {
343361
// Constants
344362
assert_eq!(f128::EXP_SAT, 0b111111111111111);
345363
assert_eq!(f128::EXP_BIAS, 16383);
364+
assert_eq!(f128::EXP_MAX, 16383);
365+
assert_eq!(f128::EXP_MIN, -16382);
366+
assert_eq!(f128::EXP_MIN_SUBNORM, -16494);
346367

347368
// `exp_unbiased`
348369
assert_eq!(f128::FRAC_PI_2.exp_unbiased(), 0);

0 commit comments

Comments
 (0)