Skip to content
This repository was archived by the owner on Apr 28, 2025. It is now read-only.

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)