//! Helpful numeric operations. use std::cmp::min; use std::ops::RangeInclusive; use libm::support::Float; use crate::{Int, MinInt}; /// Extension to `libm`'s `Float` trait with methods that are useful for tests but not /// needed in `libm` itself. pub trait FloatExt: Float { /// The minimum subnormal number. const TINY_BITS: Self::Int = Self::Int::ONE; /// Retrieve additional constants for this float type. fn consts() -> Consts { Consts::new() } /// Increment by one ULP, saturating at infinity. fn next_up(self) -> Self { let bits = self.to_bits(); if self.is_nan() || bits == Self::INFINITY.to_bits() { return self; } let abs = self.abs().to_bits(); let next_bits = if abs == Self::Int::ZERO { // Next up from 0 is the smallest subnormal Self::TINY_BITS } else if bits == abs { // Positive: counting up is more positive bits + Self::Int::ONE } else { // Negative: counting down is more positive bits - Self::Int::ONE }; Self::from_bits(next_bits) } /// A faster way to effectively call `next_up` `n` times. fn n_up(self, n: Self::Int) -> Self { let bits = self.to_bits(); if self.is_nan() || bits == Self::INFINITY.to_bits() || n == Self::Int::ZERO { return self; } let abs = self.abs().to_bits(); let is_positive = bits == abs; let crosses_zero = !is_positive && n > abs; let inf_bits = Self::INFINITY.to_bits(); let next_bits = if abs == Self::Int::ZERO { min(n, inf_bits) } else if crosses_zero { min(n - abs, inf_bits) } else if is_positive { // Positive, counting up is more positive but this may overflow match bits.checked_add(n) { Some(v) if v >= inf_bits => inf_bits, Some(v) => v, None => inf_bits, } } else { // Negative, counting down is more positive bits - n }; Self::from_bits(next_bits) } /// Decrement by one ULP, saturating at negative infinity. fn next_down(self) -> Self { let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() { return self; } let abs = self.abs().to_bits(); let next_bits = if abs == Self::Int::ZERO { // Next up from 0 is the smallest negative subnormal Self::TINY_BITS | Self::SIGN_MASK } else if bits == abs { // Positive: counting down is more negative bits - Self::Int::ONE } else { // Negative: counting up is more negative bits + Self::Int::ONE }; Self::from_bits(next_bits) } /// A faster way to effectively call `next_down` `n` times. fn n_down(self, n: Self::Int) -> Self { let bits = self.to_bits(); if self.is_nan() || bits == Self::NEG_INFINITY.to_bits() || n == Self::Int::ZERO { return self; } let abs = self.abs().to_bits(); let is_positive = bits == abs; let crosses_zero = is_positive && n > abs; let inf_bits = Self::INFINITY.to_bits(); let ninf_bits = Self::NEG_INFINITY.to_bits(); let next_bits = if abs == Self::Int::ZERO { min(n, inf_bits) | Self::SIGN_MASK } else if crosses_zero { min(n - abs, inf_bits) | Self::SIGN_MASK } else if is_positive { // Positive, counting down is more negative bits - n } else { // Negative, counting up is more negative but this may overflow match bits.checked_add(n) { Some(v) if v > ninf_bits => ninf_bits, Some(v) => v, None => ninf_bits, } }; Self::from_bits(next_bits) } } impl FloatExt for F where F: Float {} /// Extra constants that are useful for tests. #[derive(Debug, Clone, Copy)] pub struct Consts { /// The default quiet NaN, which is also the minimum quiet NaN. pub pos_nan: F, /// The default quiet NaN with negative sign. pub neg_nan: F, /// NaN with maximum (unsigned) significand to be a quiet NaN. The significand is saturated. pub max_qnan: F, /// NaN with minimum (unsigned) significand to be a signaling NaN. pub min_snan: F, /// NaN with maximum (unsigned) significand to be a signaling NaN. pub max_snan: F, pub neg_max_qnan: F, pub neg_min_snan: F, pub neg_max_snan: F, } impl Consts { fn new() -> Self { let top_sigbit_mask = F::Int::ONE << (F::SIG_BITS - 1); let pos_nan = F::EXP_MASK | top_sigbit_mask; let max_qnan = F::EXP_MASK | F::SIG_MASK; let min_snan = F::EXP_MASK | F::Int::ONE; let max_snan = (F::EXP_MASK | F::SIG_MASK) ^ top_sigbit_mask; let neg_nan = pos_nan | F::SIGN_MASK; let neg_max_qnan = max_qnan | F::SIGN_MASK; let neg_min_snan = min_snan | F::SIGN_MASK; let neg_max_snan = max_snan | F::SIGN_MASK; Self { pos_nan: F::from_bits(pos_nan), neg_nan: F::from_bits(neg_nan), max_qnan: F::from_bits(max_qnan), min_snan: F::from_bits(min_snan), max_snan: F::from_bits(max_snan), neg_max_qnan: F::from_bits(neg_max_qnan), neg_min_snan: F::from_bits(neg_min_snan), neg_max_snan: F::from_bits(neg_max_snan), } } pub fn iter(self) -> impl Iterator { // Destructure so we get unused warnings if we forget a list entry. let Self { pos_nan, neg_nan, max_qnan, min_snan, max_snan, neg_max_qnan, neg_min_snan, neg_max_snan, } = self; [ pos_nan, neg_nan, max_qnan, min_snan, max_snan, neg_max_qnan, neg_min_snan, neg_max_snan, ] .into_iter() } } /// Return the number of steps between two floats, returning `None` if either input is NaN. /// /// This is the number of steps needed for `n_up` or `n_down` to go between values. Infinities /// are treated the same as those functions (will return the nearest finite value), and only one /// of `-0` or `+0` is counted. It does not matter which value is greater. pub fn ulp_between(x: F, y: F) -> Option { let a = as_ulp_steps(x)?; let b = as_ulp_steps(y)?; Some(a.abs_diff(b)) } /// Return the (signed) number of steps from zero to `x`. fn as_ulp_steps(x: F) -> Option { let s = x.to_bits_signed(); let val = if s >= F::SignedInt::ZERO { // each increment from `s = 0` is one step up from `x = 0.0` s } else { // each increment from `s = F::SignedInt::MIN` is one step down from `x = -0.0` F::SignedInt::MIN - s }; // If `x` is NaN, return `None` (!x.is_nan()).then_some(val) } /// An iterator that returns floats with linearly spaced integer representations, which translates /// to logarithmic spacing of their values. /// /// Note that this tends to skip negative zero, so that needs to be checked explicitly. /// /// Returns `(iterator, iterator_length)`. pub fn logspace( start: F, end: F, steps: F::Int, ) -> (impl Iterator + Clone, F::Int) where RangeInclusive: Iterator, { assert!(!start.is_nan()); assert!(!end.is_nan()); assert!(end >= start); let steps = steps .checked_sub(F::Int::ONE) .expect("`steps` must be at least 2"); let between = ulp_between(start, end).expect("`start` or `end` is NaN"); let spacing = (between / steps).max(F::Int::ONE); let steps = steps.min(between); // At maximum, one step per ULP let mut x = start; ( (F::Int::ZERO..=steps).map(move |_| { let ret = x; x = x.n_up(spacing); ret }), steps + F::Int::ONE, ) } /// Returns an iterator of up to `steps` integers evenly distributed. pub fn linear_ints( range: RangeInclusive, steps: u64, ) -> (impl Iterator + Clone, u64) { let steps = steps.checked_sub(1).unwrap(); let between = u64::from(range.start().abs_diff(*range.end())); let spacing = i32::try_from((between / steps).max(1)).unwrap(); let steps = steps.min(between); let mut x: i32 = *range.start(); ( (0..=steps).map(move |_| { let res = x; // Wrapping add to avoid panic on last item (where `x` could overflow past i32::MAX as // there is no next item). x = x.wrapping_add(spacing); res }), steps + 1, ) } #[cfg(test)] mod tests { use std::cmp::max; use super::*; use crate::f8; #[test] fn test_next_up_down() { for (i, v) in f8::ALL.into_iter().enumerate() { let down = v.next_down().to_bits(); let up = v.next_up().to_bits(); if i == 0 { assert_eq!(down, f8::NEG_INFINITY.to_bits(), "{i} next_down({v:#010b})"); } else { let expected = if v == f8::ZERO { 1 | f8::SIGN_MASK } else { f8::ALL[i - 1].to_bits() }; assert_eq!(down, expected, "{i} next_down({v:#010b})"); } if i == f8::ALL_LEN - 1 { assert_eq!(up, f8::INFINITY.to_bits(), "{i} next_up({v:#010b})"); } else { let expected = if v == f8::NEG_ZERO { 1 } else { f8::ALL[i + 1].to_bits() }; assert_eq!(up, expected, "{i} next_up({v:#010b})"); } } } #[test] fn test_next_up_down_inf_nan() { assert_eq!(f8::NEG_INFINITY.next_up().to_bits(), f8::ALL[0].to_bits(),); assert_eq!( f8::NEG_INFINITY.next_down().to_bits(), f8::NEG_INFINITY.to_bits(), ); assert_eq!( f8::INFINITY.next_down().to_bits(), f8::ALL[f8::ALL_LEN - 1].to_bits(), ); assert_eq!(f8::INFINITY.next_up().to_bits(), f8::INFINITY.to_bits(),); assert_eq!(f8::NAN.next_up().to_bits(), f8::NAN.to_bits(),); assert_eq!(f8::NAN.next_down().to_bits(), f8::NAN.to_bits(),); } #[test] fn test_n_up_down_quick() { assert_eq!(f8::ALL[0].n_up(4).to_bits(), f8::ALL[4].to_bits(),); assert_eq!( f8::ALL[f8::ALL_LEN - 1].n_down(4).to_bits(), f8::ALL[f8::ALL_LEN - 5].to_bits(), ); // Check around zero assert_eq!(f8::from_bits(0b0).n_up(7).to_bits(), 0b0_0000_111); assert_eq!(f8::from_bits(0b0).n_down(7).to_bits(), 0b1_0000_111); // Check across zero assert_eq!(f8::from_bits(0b1_0000_111).n_up(8).to_bits(), 0b0_0000_001); assert_eq!( f8::from_bits(0b0_0000_111).n_down(8).to_bits(), 0b1_0000_001 ); } #[test] fn test_n_up_down_one() { // Verify that `n_up(1)` and `n_down(1)` are the same as `next_up()` and next_down()`.` for i in 0..u8::MAX { let v = f8::from_bits(i); assert_eq!(v.next_up().to_bits(), v.n_up(1).to_bits()); assert_eq!(v.next_down().to_bits(), v.n_down(1).to_bits()); } } #[test] fn test_n_up_down_inf_nan_zero() { assert_eq!(f8::NEG_INFINITY.n_up(1).to_bits(), f8::ALL[0].to_bits()); assert_eq!( f8::NEG_INFINITY.n_up(239).to_bits(), f8::ALL[f8::ALL_LEN - 1].to_bits() ); assert_eq!(f8::NEG_INFINITY.n_up(240).to_bits(), f8::INFINITY.to_bits()); assert_eq!( f8::NEG_INFINITY.n_down(u8::MAX).to_bits(), f8::NEG_INFINITY.to_bits() ); assert_eq!( f8::INFINITY.n_down(1).to_bits(), f8::ALL[f8::ALL_LEN - 1].to_bits() ); assert_eq!(f8::INFINITY.n_down(239).to_bits(), f8::ALL[0].to_bits()); assert_eq!( f8::INFINITY.n_down(240).to_bits(), f8::NEG_INFINITY.to_bits() ); assert_eq!(f8::INFINITY.n_up(u8::MAX).to_bits(), f8::INFINITY.to_bits()); assert_eq!(f8::NAN.n_up(u8::MAX).to_bits(), f8::NAN.to_bits()); assert_eq!(f8::NAN.n_down(u8::MAX).to_bits(), f8::NAN.to_bits()); assert_eq!(f8::ZERO.n_down(1).to_bits(), f8::TINY_BITS | f8::SIGN_MASK); assert_eq!(f8::NEG_ZERO.n_up(1).to_bits(), f8::TINY_BITS); } /// True if the specified range of `f8::ALL` includes both +0 and -0 fn crossed_zero(start: usize, end: usize) -> bool { let crossed = &f8::ALL[start..=end]; crossed.iter().any(|f| f8::eq_repr(*f, f8::ZERO)) && crossed.iter().any(|f| f8::eq_repr(*f, f8::NEG_ZERO)) } #[test] fn test_n_up_down() { for (i, v) in f8::ALL.into_iter().enumerate() { for n in 0..f8::ALL_LEN { let down = v.n_down(n as u8).to_bits(); let up = v.n_up(n as u8).to_bits(); if let Some(down_exp_idx) = i.checked_sub(n) { // No overflow let mut expected = f8::ALL[down_exp_idx].to_bits(); if n >= 1 && crossed_zero(down_exp_idx, i) { // If both -0 and +0 are included, we need to adjust our expected value match down_exp_idx.checked_sub(1) { Some(v) => expected = f8::ALL[v].to_bits(), // Saturate to -inf if we are out of values None => expected = f8::NEG_INFINITY.to_bits(), } } assert_eq!(down, expected, "{i} {n} n_down({v:#010b})"); } else { // Overflow to -inf assert_eq!( down, f8::NEG_INFINITY.to_bits(), "{i} {n} n_down({v:#010b})" ); } let mut up_exp_idx = i + n; if up_exp_idx < f8::ALL_LEN { // No overflow if n >= 1 && up_exp_idx < f8::ALL_LEN && crossed_zero(i, up_exp_idx) { // If both -0 and +0 are included, we need to adjust our expected value up_exp_idx += 1; } let expected = if up_exp_idx >= f8::ALL_LEN { f8::INFINITY.to_bits() } else { f8::ALL[up_exp_idx].to_bits() }; assert_eq!(up, expected, "{i} {n} n_up({v:#010b})"); } else { // Overflow to +inf assert_eq!(up, f8::INFINITY.to_bits(), "{i} {n} n_up({v:#010b})"); } } } } #[test] fn test_ulp_between() { for (i, x) in f8::ALL.into_iter().enumerate() { for (j, y) in f8::ALL.into_iter().enumerate() { let ulp = ulp_between(x, y).unwrap(); let make_msg = || format!("i: {i} j: {j} x: {x:b} y: {y:b} ulp {ulp}"); let i_low = min(i, j); let i_hi = max(i, j); let mut expected = u8::try_from(i_hi - i_low).unwrap(); if crossed_zero(i_low, i_hi) { expected -= 1; } assert_eq!(ulp, expected, "{}", make_msg()); // Skip if either are zero since `next_{up,down}` will count over it let either_zero = x == f8::ZERO || y == f8::ZERO; if x < y && !either_zero { assert_eq!(x.n_up(ulp).to_bits(), y.to_bits(), "{}", make_msg()); assert_eq!(y.n_down(ulp).to_bits(), x.to_bits(), "{}", make_msg()); } else if !either_zero { assert_eq!(y.n_up(ulp).to_bits(), x.to_bits(), "{}", make_msg()); assert_eq!(x.n_down(ulp).to_bits(), y.to_bits(), "{}", make_msg()); } } } } #[test] fn test_ulp_between_inf_nan_zero() { assert_eq!( ulp_between(f8::NEG_INFINITY, f8::INFINITY).unwrap(), f8::ALL_LEN as u8 ); assert_eq!( ulp_between(f8::INFINITY, f8::NEG_INFINITY).unwrap(), f8::ALL_LEN as u8 ); assert_eq!( ulp_between(f8::NEG_INFINITY, f8::ALL[f8::ALL_LEN - 1]).unwrap(), f8::ALL_LEN as u8 - 1 ); assert_eq!( ulp_between(f8::INFINITY, f8::ALL[0]).unwrap(), f8::ALL_LEN as u8 - 1 ); assert_eq!(ulp_between(f8::ZERO, f8::NEG_ZERO).unwrap(), 0); assert_eq!(ulp_between(f8::NAN, f8::ZERO), None); assert_eq!(ulp_between(f8::ZERO, f8::NAN), None); } #[test] fn test_logspace() { let (ls, count) = logspace(f8::from_bits(0x0), f8::from_bits(0x4), 2); let ls: Vec<_> = ls.collect(); let exp = [f8::from_bits(0x0), f8::from_bits(0x4)]; assert_eq!(ls, exp); assert_eq!(ls.len(), usize::from(count)); let (ls, count) = logspace(f8::from_bits(0x0), f8::from_bits(0x4), 3); let ls: Vec<_> = ls.collect(); let exp = [f8::from_bits(0x0), f8::from_bits(0x2), f8::from_bits(0x4)]; assert_eq!(ls, exp); assert_eq!(ls.len(), usize::from(count)); // Check that we include all values with no repeats if `steps` exceeds the maximum number // of steps. let (ls, count) = logspace(f8::from_bits(0x0), f8::from_bits(0x3), 10); let ls: Vec<_> = ls.collect(); let exp = [ f8::from_bits(0x0), f8::from_bits(0x1), f8::from_bits(0x2), f8::from_bits(0x3), ]; assert_eq!(ls, exp); assert_eq!(ls.len(), usize::from(count)); } #[test] fn test_linear_ints() { let (ints, count) = linear_ints(0..=4, 2); let ints: Vec<_> = ints.collect(); let exp = [0, 4]; assert_eq!(ints, exp); assert_eq!(ints.len(), usize::try_from(count).unwrap()); let (ints, count) = linear_ints(0..=4, 3); let ints: Vec<_> = ints.collect(); let exp = [0, 2, 4]; assert_eq!(ints, exp); assert_eq!(ints.len(), usize::try_from(count).unwrap()); // Check that we include all values with no repeats if `steps` exceeds the maximum number // of steps. let (ints, count) = linear_ints(0x0..=0x3, 10); let ints: Vec<_> = ints.collect(); let exp = [0, 1, 2, 3]; assert_eq!(ints, exp); assert_eq!(ints.len(), usize::try_from(count).unwrap()); // Check that there are no panics around `i32::MAX`. let (ints, count) = linear_ints(i32::MAX - 1..=i32::MAX, 5); let ints: Vec<_> = ints.collect(); let exp = [i32::MAX - 1, i32::MAX]; assert_eq!(ints, exp); assert_eq!(ints.len(), usize::try_from(count).unwrap()); } #[test] fn test_consts() { let Consts { pos_nan, neg_nan, max_qnan, min_snan, max_snan, neg_max_qnan, neg_min_snan, neg_max_snan, } = f8::consts(); assert_eq!(pos_nan.to_bits(), 0b0_1111_100); assert_eq!(neg_nan.to_bits(), 0b1_1111_100); assert_eq!(max_qnan.to_bits(), 0b0_1111_111); assert_eq!(min_snan.to_bits(), 0b0_1111_001); assert_eq!(max_snan.to_bits(), 0b0_1111_011); assert_eq!(neg_max_qnan.to_bits(), 0b1_1111_111); assert_eq!(neg_min_snan.to_bits(), 0b1_1111_001); assert_eq!(neg_max_snan.to_bits(), 0b1_1111_011); } }