use std::ops::{
Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
};
#[repr(transparent)]
#[derive(Copy, Clone, Debug, Default)]
pub struct N5<T>(pub T);
impl<T> N5<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self(value)
}
#[inline]
pub fn into_inner(self) -> T {
self.0
}
#[inline]
pub fn map<U>(self, f: impl FnOnce(T) -> U) -> N5<U> {
N5(f(self.0))
}
}
macro_rules! impl_float_ext {
($T:ty) => {
impl PartialEq for $crate::float::N5<$T> {
#[inline]
fn eq(&self, other: &Self) -> bool {
let a = self.0;
let b = other.0;
if a.is_nan() && b.is_nan() {
true
} else {
a == b
}
}
}
impl PartialOrd for $crate::float::N5<$T> {
#[inline]
fn partial_cmp(&self, other: &Self) -> Option<::core::cmp::Ordering> {
let a = self.0;
let b = other.0;
if a.is_nan() && b.is_nan() {
Some(::core::cmp::Ordering::Equal)
} else if a.is_nan() {
if b == <$T>::INFINITY {
Some(::core::cmp::Ordering::Less)
} else if b == <$T>::NEG_INFINITY {
Some(::core::cmp::Ordering::Greater)
} else {
None
}
} else if b.is_nan() {
if a == <$T>::NEG_INFINITY {
Some(::core::cmp::Ordering::Less)
} else if a == <$T>::INFINITY {
Some(::core::cmp::Ordering::Greater)
} else {
None
}
} else {
a.partial_cmp(&b)
}
}
}
impl Eq for $crate::float::N5<$T> {}
};
}
pub(crate) use impl_float_ext;
impl_float_ext!(f32);
impl_float_ext!(f64);
#[cfg(feature = "try_trait")]
#[derive(Copy, Clone, Debug)]
pub enum N5Residual {}
#[cfg(feature = "try_trait")]
impl<T> core::ops::Try for N5<T> {
type Output = T;
type Residual = N5Residual;
#[inline]
fn from_output(output: Self::Output) -> Self {
N5(output)
}
#[inline]
fn branch(self) -> core::ops::ControlFlow<Self::Residual, Self::Output> {
core::ops::ControlFlow::Continue(self.0)
}
}
#[cfg(feature = "try_trait")]
impl<T> core::ops::FromResidual<N5Residual> for N5<T> {
#[inline]
fn from_residual(residual: N5Residual) -> Self {
match residual {}
}
}
#[cfg(feature = "try_trait")]
impl<O> core::ops::Residual<O> for N5Residual {
type TryType = N5<O>;
}
macro_rules! impl_float_arith {
($T:ident) => {
impl Add for N5<$T> {
type Output = Self;
#[inline]
fn add(self, other: Self) -> Self {
N5(self.0 + other.0)
}
}
impl Sub for N5<$T> {
type Output = Self;
#[inline]
fn sub(self, other: Self) -> Self {
N5(self.0 - other.0)
}
}
impl Mul for N5<$T> {
type Output = Self;
#[inline]
fn mul(self, other: Self) -> Self {
N5(self.0 * other.0)
}
}
impl Div for N5<$T> {
type Output = Self;
#[inline]
fn div(self, other: Self) -> Self {
N5(self.0 / other.0)
}
}
impl Rem for N5<$T> {
type Output = Self;
#[inline]
fn rem(self, other: Self) -> Self {
N5(self.0 % other.0)
}
}
impl Neg for N5<$T> {
type Output = Self;
#[inline]
fn neg(self) -> Self {
N5(-self.0)
}
}
impl AddAssign for N5<$T> {
#[inline]
fn add_assign(&mut self, other: Self) {
*self = *self + other;
}
}
impl SubAssign for N5<$T> {
#[inline]
fn sub_assign(&mut self, other: Self) {
*self = *self - other;
}
}
impl MulAssign for N5<$T> {
#[inline]
fn mul_assign(&mut self, other: Self) {
*self = *self * other;
}
}
impl DivAssign for N5<$T> {
#[inline]
fn div_assign(&mut self, other: Self) {
*self = *self / other;
}
}
impl RemAssign for N5<$T> {
#[inline]
fn rem_assign(&mut self, other: Self) {
*self = *self % other;
}
}
impl From<u8> for N5<$T> {
#[inline]
fn from(n: u8) -> Self {
N5(<$T>::from(n))
}
}
impl From<i8> for N5<$T> {
#[inline]
fn from(n: i8) -> Self {
N5(<$T>::from(n))
}
}
impl From<i16> for N5<$T> {
#[inline]
fn from(n: i16) -> Self {
N5(<$T>::from(n))
}
}
impl From<u16> for N5<$T> {
#[inline]
fn from(n: u16) -> Self {
N5(<$T>::from(n))
}
}
impl From<$T> for N5<$T> {
#[inline]
fn from(v: $T) -> Self {
N5(v)
}
}
impl N5<$T> {
pub const PI: Self = N5(::core::$T::consts::PI);
pub const TAU: Self = N5(::core::$T::consts::TAU);
pub const E: Self = N5(::core::$T::consts::E);
pub const FRAC_PI_2: Self = N5(::core::$T::consts::FRAC_PI_2);
pub const NAN: Self = N5(<$T>::NAN);
pub const INFINITY: Self = N5(<$T>::INFINITY);
pub const NEG_INFINITY: Self = N5(<$T>::NEG_INFINITY);
pub const ZERO: Self = N5(0.0);
pub const ONE: Self = N5(1.0);
#[inline]
pub fn is_nan(self) -> bool {
self.0.is_nan()
}
#[inline]
pub fn is_finite(self) -> bool {
self.0.is_finite()
}
#[inline]
pub fn is_infinite(self) -> bool {
self.0.is_infinite()
}
#[inline]
pub fn abs(self) -> Self {
N5(self.0.abs())
}
#[inline]
pub fn signum(self) -> Self {
N5(self.0.signum())
}
#[inline]
pub fn recip(self) -> Self {
N5(self.0.recip())
}
#[inline]
pub fn sqrt(self) -> Self {
N5(self.0.sqrt())
}
#[inline]
pub fn cbrt(self) -> Self {
N5(self.0.cbrt())
}
#[inline]
pub fn sin(self) -> Self {
N5(self.0.sin())
}
#[inline]
pub fn cos(self) -> Self {
N5(self.0.cos())
}
#[inline]
pub fn tan(self) -> Self {
N5(self.0.tan())
}
#[inline]
pub fn asin(self) -> Self {
N5(self.0.asin())
}
#[inline]
pub fn acos(self) -> Self {
N5(self.0.acos())
}
#[inline]
pub fn atan(self) -> Self {
N5(self.0.atan())
}
#[inline]
pub fn atan2(self, other: Self) -> Self {
N5(self.0.atan2(other.0))
}
#[inline]
pub fn exp(self) -> Self {
N5(self.0.exp())
}
#[inline]
pub fn exp2(self) -> Self {
N5(self.0.exp2())
}
#[inline]
pub fn ln(self) -> Self {
N5(self.0.ln())
}
#[inline]
pub fn log2(self) -> Self {
N5(self.0.log2())
}
#[inline]
pub fn log10(self) -> Self {
N5(self.0.log10())
}
#[inline]
pub fn powi(self, n: i32) -> Self {
N5(self.0.powi(n))
}
#[inline]
pub fn powf(self, p: Self) -> Self {
N5(self.0.powf(p.0))
}
#[inline]
pub fn mul_add(self, a: Self, b: Self) -> Self {
N5(self.0.mul_add(a.0, b.0))
}
}
};
}
impl_float_arith!(f32);
impl_float_arith!(f64);
impl From<i32> for N5<f64> {
#[inline]
fn from(n: i32) -> Self {
N5(f64::from(n))
}
}
impl From<u32> for N5<f64> {
#[inline]
fn from(n: u32) -> Self {
N5(f64::from(n))
}
}
#[cfg(feature = "f16")]
#[cfg_attr(docsrs, doc(cfg(feature = "f16")))]
pub type F016 = N5<f16>;
pub type F032 = N5<f32>;
pub type F064 = N5<f64>;
pub(crate) fn shift32(n: i32, x: f32) -> f32 {
if x.is_nan() {
return match n.signum() {
-1 => f32::NEG_INFINITY,
1 => f32::INFINITY,
_ => f32::NAN,
};
}
let i = float_to_int32(x);
int32_to_float(clamp32(i.saturating_add(n)))
}
fn float_to_int32(x: f32) -> i32 {
signed32(x.to_bits())
}
fn int32_to_float(i: i32) -> f32 {
f32::from_bits(unsigned32(i))
}
fn signed32(x: u32) -> i32 {
if x < 0x80000000 {
x as i32
} else {
(u32::MAX - (x - 0x80000000)) as i32
}
}
fn unsigned32(x: i32) -> u32 {
if x >= 0 {
x as u32
} else {
0x80000000u32.wrapping_add(u32::MAX - (x as u32))
}
}
fn clamp32(x: i32) -> i32 {
x.clamp(-2139095041, 2139095040)
}
pub(crate) fn widen_f32_f64(x: f32) -> f64 {
x as f64
}
fn signed64(x: u64) -> i64 {
if x < 0x8000000000000000 {
x as i64
} else {
(u64::MAX - (x - 0x8000000000000000)) as i64
}
}
fn unsigned64(x: i64) -> u64 {
if x >= 0 {
x as u64
} else {
0x8000000000000000u64.wrapping_add(u64::MAX - (x as u64))
}
}
fn clamp64(x: i64) -> i64 {
x.clamp(-9218868437227405313, 9218868437227405312)
}
fn float_to_int64(x: f64) -> i64 {
signed64(x.to_bits())
}
fn int64_to_float(i: i64) -> f64 {
f64::from_bits(unsigned64(i))
}
pub(crate) fn shift64(n: i64, x: f64) -> f64 {
if x.is_nan() {
return match n.signum() {
-1 => f64::NEG_INFINITY,
1 => f64::INFINITY,
_ => f64::NAN,
};
}
let i = float_to_int64(x);
int64_to_float(clamp64(i.saturating_add(n)))
}
pub(crate) fn round_down_to_f32(b: i128) -> f32 {
let approx = b as f32;
if approx.is_nan() {
return approx;
}
if approx == f32::INFINITY {
return f32::MAX;
}
if approx == f32::NEG_INFINITY {
return f32::NEG_INFINITY;
}
if (approx as i128) > b {
shift32(-1, approx)
} else {
approx
}
}
pub(crate) fn round_down_to_f64(b: i128) -> f64 {
let approx = b as f64;
if approx.is_nan() {
return approx;
}
if approx == f64::INFINITY {
return f64::MAX;
}
if approx == f64::NEG_INFINITY {
return f64::NEG_INFINITY;
}
if (approx as i128) > b {
shift64(-1, approx)
} else {
approx
}
}
#[doc(hidden)]
pub trait RoundDownFromI128: private::Sealed + Sized {
fn from_i128_rd(b: i128) -> Self;
}
mod private {
pub trait Sealed {}
impl Sealed for f32 {}
impl Sealed for f64 {}
#[cfg(feature = "f16")]
impl Sealed for f16 {}
}
impl RoundDownFromI128 for f32 {
#[inline]
fn from_i128_rd(b: i128) -> Self {
round_down_to_f32(b)
}
}
impl RoundDownFromI128 for f64 {
#[inline]
fn from_i128_rd(b: i128) -> Self {
round_down_to_f64(b)
}
}
#[cfg(feature = "f16")]
impl RoundDownFromI128 for f16 {
#[inline]
fn from_i128_rd(b: i128) -> Self {
round_down_to_f16(b)
}
}
#[cfg(feature = "f16")]
pub(crate) fn round_down_to_f16(b: i128) -> f16 {
let approx: f16 = b as f16;
if approx.is_nan() {
return approx;
}
if approx == f16::INFINITY {
return f16::MAX;
}
if approx == f16::NEG_INFINITY {
return f16::NEG_INFINITY;
}
if (approx as i128) > b {
crate::core::f016::shift16_f16(-1, approx)
} else {
approx
}
}
#[inline]
#[cfg(feature = "fixed")]
pub(crate) const fn scale_pow2_f64(frac: u32) -> f64 {
let biased = (1023u64 + frac as u64) << 52;
f64::from_bits(biased)
}
#[inline]
#[cfg(feature = "fixed")]
pub(crate) const fn scale_pow2_f64_neg(frac: u32) -> f64 {
if frac <= 1022 {
let biased = (1023u64 - frac as u64) << 52;
f64::from_bits(biased)
} else if frac <= 1074 {
let mantissa = 1u64 << (1074 - frac as u64);
f64::from_bits(mantissa)
} else {
0.0
}
}
pub(crate) fn round_down_to_f64_u128(b: u128) -> f64 {
let approx = b as f64;
if approx.is_nan() {
return approx;
}
if approx == f64::INFINITY {
return f64::MAX;
}
if approx == (u128::MAX as f64) {
return shift64(-1, approx);
}
if (approx as u128) > b {
shift64(-1, approx)
} else {
approx
}
}
pub(crate) fn round_down_f64_to_f32(v: f64) -> f32 {
let approx = v as f32;
if approx.is_nan() {
return approx;
}
if approx == f32::INFINITY {
return if v > f32::MAX as f64 {
f32::MAX
} else {
f32::INFINITY
};
}
if approx == f32::NEG_INFINITY {
return f32::NEG_INFINITY;
}
if (approx as f64) > v {
shift32(-1, approx)
} else {
approx
}
}
#[cfg(feature = "f16")]
pub(crate) fn round_down_f64_to_f16(v: f64) -> f16 {
let approx: f16 = v as f16;
if approx.is_nan() {
return approx;
}
if approx == f16::INFINITY {
return if v > f16::MAX as f64 {
f16::MAX
} else {
f16::INFINITY
};
}
if approx == f16::NEG_INFINITY {
return f16::NEG_INFINITY;
}
if (approx as f64) > v {
crate::core::f016::shift16_f16(-1, approx)
} else {
approx
}
}
#[doc(hidden)]
pub trait RoundDownFromF64: private::Sealed + Sized {
fn from_f64_rd(v: f64) -> Self;
}
impl RoundDownFromF64 for f64 {
#[inline]
fn from_f64_rd(v: f64) -> Self {
v
}
}
impl RoundDownFromF64 for f32 {
#[inline]
fn from_f64_rd(v: f64) -> Self {
round_down_f64_to_f32(v)
}
}
#[cfg(feature = "f16")]
impl RoundDownFromF64 for f16 {
#[inline]
fn from_f64_rd(v: f64) -> Self {
round_down_f64_to_f16(v)
}
}
#[doc(hidden)]
pub trait BitsToF64Rd: bits_private::Sealed + Sized {
fn to_f64_rd(self) -> f64;
}
mod bits_private {
pub trait Sealed {}
impl Sealed for i8 {}
impl Sealed for u8 {}
impl Sealed for i16 {}
impl Sealed for u16 {}
impl Sealed for i32 {}
impl Sealed for u32 {}
impl Sealed for i64 {}
impl Sealed for u64 {}
impl Sealed for i128 {}
impl Sealed for u128 {}
}
impl BitsToF64Rd for i8 {
#[inline]
fn to_f64_rd(self) -> f64 {
self as f64
}
}
impl BitsToF64Rd for u8 {
#[inline]
fn to_f64_rd(self) -> f64 {
self as f64
}
}
impl BitsToF64Rd for i16 {
#[inline]
fn to_f64_rd(self) -> f64 {
self as f64
}
}
impl BitsToF64Rd for u16 {
#[inline]
fn to_f64_rd(self) -> f64 {
self as f64
}
}
impl BitsToF64Rd for i32 {
#[inline]
fn to_f64_rd(self) -> f64 {
self as f64
}
}
impl BitsToF64Rd for u32 {
#[inline]
fn to_f64_rd(self) -> f64 {
self as f64
}
}
impl BitsToF64Rd for i64 {
#[inline]
fn to_f64_rd(self) -> f64 {
round_down_to_f64(self as i128)
}
}
impl BitsToF64Rd for u64 {
#[inline]
fn to_f64_rd(self) -> f64 {
round_down_to_f64(self as i128)
}
}
impl BitsToF64Rd for i128 {
#[inline]
fn to_f64_rd(self) -> f64 {
round_down_to_f64(self)
}
}
impl BitsToF64Rd for u128 {
#[inline]
fn to_f64_rd(self) -> f64 {
round_down_to_f64_u128(self)
}
}
macro_rules! def_walk_helpers {
($mod_name:ident, $src:ty, $dst:ty, $shift:path, $widen:path) => {
$crate::float::def_walk_helpers!(
@inner $mod_name, $src, $dst, $shift, $widen,
);
};
($mod_name:ident, $src:ty, $dst:ty, $shift:path, $widen:path, $to_ns:path, $from_ns:path) => {
$crate::float::def_walk_helpers!(
@inner $mod_name, $src, $dst, $shift, $widen,
@solve $to_ns, $from_ns
);
};
(@inner $mod_name:ident, $src:ty, $dst:ty, $shift:path, $widen:path,
$(@solve $to_ns:path, $from_ns:path)?) => {
mod $mod_name {
#[allow(unused_imports)]
use super::*;
#[allow(dead_code)]
pub(super) fn ascend_to_ceil(start: $dst, x: $src) -> ($dst, u32) {
let mut z = start;
let mut steps = 0;
loop {
let next = $shift(1, z);
if next <= z {
return (z, steps);
}
steps += 1;
z = next;
if x <= $widen(z) {
return (z, steps);
}
}
}
#[allow(dead_code)]
pub(super) fn descend_to_ceil(start: $dst, x: $src) -> ($dst, u32) {
let mut z = start;
let mut steps = 0;
loop {
let next = $shift(-1, z);
if z <= next {
return (z, steps);
}
let next_up = $widen(next);
if x > next_up {
return (z, steps);
}
steps += 1;
z = next;
}
}
#[allow(dead_code)]
pub(super) fn ascend_to_floor(start: $dst, x: $src) -> ($dst, u32) {
let mut z = start;
let mut steps = 0;
loop {
let next = $shift(1, z);
if next <= z {
return (z, steps);
}
let next_up = $widen(next);
if next_up > x {
return (z, steps);
}
steps += 1;
z = next;
}
}
#[allow(dead_code)]
pub(super) fn descend_to_floor(start: $dst, x: $src) -> ($dst, u32) {
let mut z = start;
let mut steps = 0;
loop {
let next = $shift(-1, z);
if z <= next {
return (z, steps);
}
steps += 1;
z = next;
if $widen(z) <= x {
return (z, steps);
}
}
}
$(
#[allow(dead_code)]
pub(super) fn solve_to_ceil(start: $dst, x: $src) -> ($dst, u32) {
let n_start: i128 = $to_ns(start);
let mut lo: i128 = n_start.saturating_sub(1i128 << 50);
let mut hi: i128 = n_start.saturating_add(1i128 << 50);
let mut steps: u32 = 0;
while lo < hi {
let mid = lo + (hi - lo) / 2;
if $widen($from_ns(mid)) >= x {
hi = mid;
} else {
lo = mid + 1;
}
steps += 1;
}
let z = $from_ns(lo);
debug_assert!(
$widen(z) >= x,
"solve_to_ceil bracket too narrow: widen(lo) < x"
);
(z, steps)
}
)?
}
};
}
pub(crate) use def_walk_helpers;
#[doc(hidden)]
#[macro_export]
macro_rules! __float_ext_int_ceil_body {
($float:ty, $int:ty, $v:ident) => {{
if $v.is_nan() {
$crate::extended::Extended::PosInf
} else if $v == <$float>::NEG_INFINITY {
$crate::extended::Extended::NegInf
} else if $v == <$float>::INFINITY {
$crate::extended::Extended::PosInf
} else {
let lo = <$int>::MIN as $float;
let hi = <$int>::MAX as $float;
if $v > hi {
$crate::extended::Extended::PosInf
} else if $v < lo {
$crate::extended::Extended::Finite(<$int>::MIN)
} else {
let scaled_ceil_float: $float = $v.ceil();
let bits: $int = scaled_ceil_float as $int;
if bits == <$int>::MAX
&& <$int as $crate::float::BitsToF64Rd>::to_f64_rd(<$int>::MAX)
< scaled_ceil_float as f64
{
$crate::extended::Extended::PosInf
} else {
$crate::extended::Extended::Finite(bits)
}
}
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __float_ext_int_floor_body {
($float:ty, $int:ty, $v:ident) => {{
if $v.is_nan() {
$crate::extended::Extended::NegInf
} else if $v == <$float>::NEG_INFINITY {
$crate::extended::Extended::NegInf
} else if $v == <$float>::INFINITY {
$crate::extended::Extended::PosInf
} else {
let lo = <$int>::MIN as $float;
let hi = <$int>::MAX as $float;
if $v > hi {
$crate::extended::Extended::Finite(<$int>::MAX)
} else if $v < lo {
$crate::extended::Extended::NegInf
} else {
$crate::extended::Extended::Finite($v.floor() as $int)
}
}
}};
}
#[doc(hidden)]
#[macro_export]
macro_rules! __float_ext_int_inner_body {
($float:ty, $int:ty, $b:ident) => {
match $b {
$crate::extended::Extended::NegInf => $crate::float::N5::new(<$float>::NEG_INFINITY),
$crate::extended::Extended::PosInf => $crate::float::N5::new(<$float>::INFINITY),
$crate::extended::Extended::Finite(i) => $crate::float::N5::new(
<$float as $crate::float::RoundDownFromI128>::from_i128_rd(i as i128),
),
}
};
}
#[cfg_attr(feature = "macros", macro_export)]
macro_rules! float_ext_int {
($(#[$meta:meta])* $vis:vis $name:ident, $float:ty, $int:ty) => {
$crate::conn_k! {
$(#[$meta])*
$vis $name : $crate::float::N5<$float> => $crate::extended::Extended<$int> {
ceil: {
fn ceil(x: $crate::float::N5<$float>)
-> $crate::extended::Extended<$int>
{
let v = x.into_inner();
$crate::__float_ext_int_ceil_body!($float, $int, v)
}
ceil
},
inner: {
fn inner(b: $crate::extended::Extended<$int>)
-> $crate::float::N5<$float>
{
$crate::__float_ext_int_inner_body!($float, $int, b)
}
inner
},
floor: {
fn floor(x: $crate::float::N5<$float>)
-> $crate::extended::Extended<$int>
{
let v = x.into_inner();
$crate::__float_ext_int_floor_body!($float, $int, v)
}
floor
},
}
}
};
}
#[cfg_attr(feature = "macros", macro_export)]
macro_rules! float_ext_int_l {
($(#[$meta:meta])* $vis:vis $name:ident, $float:ty, $int:ty) => {
$(#[$meta])*
$vis const $name: $crate::conn::Conn<
$crate::float::N5<$float>,
$crate::extended::Extended<$int>,
> = {
fn ceil(x: $crate::float::N5<$float>)
-> $crate::extended::Extended<$int>
{
let v = x.into_inner();
$crate::__float_ext_int_ceil_body!($float, $int, v)
}
fn inner(b: $crate::extended::Extended<$int>)
-> $crate::float::N5<$float>
{
$crate::__float_ext_int_inner_body!($float, $int, b)
}
$crate::conn::Conn::new_l(ceil, inner)
};
};
}
pub(crate) use float_ext_int;
pub(crate) use float_ext_int_l;
#[cfg(test)]
mod tests {
use super::*;
use crate::extended::Extended;
use crate::prop::arb::extended_float_f64;
use proptest::prelude::*;
#[test]
fn shift32_from_zero() {
let z = shift32(1, 0.0);
assert!(z > 0.0);
assert_eq!(z, f32::from_bits(1));
}
#[test]
fn shift32_nan_to_inf() {
assert_eq!(shift32(1, f32::NAN), f32::INFINITY);
assert_eq!(shift32(-1, f32::NAN), f32::NEG_INFINITY);
assert!(shift32(0, f32::NAN).is_nan());
}
#[test]
fn shift32_inf_clamped() {
assert_eq!(shift32(1, f32::INFINITY), f32::INFINITY);
assert_eq!(shift32(-1, f32::NEG_INFINITY), f32::NEG_INFINITY);
}
#[test]
fn shift64_from_zero() {
let z = shift64(1, 0.0);
assert!(z > 0.0);
assert_eq!(z, f64::from_bits(1));
}
#[test]
fn shift64_nan_to_inf() {
assert_eq!(shift64(1, f64::NAN), f64::INFINITY);
assert_eq!(shift64(-1, f64::NAN), f64::NEG_INFINITY);
assert!(shift64(0, f64::NAN).is_nan());
}
#[test]
fn shift64_inf_clamped() {
assert_eq!(shift64(1, f64::INFINITY), f64::INFINITY);
assert_eq!(shift64(-1, f64::NEG_INFINITY), f64::NEG_INFINITY);
}
#[test]
fn nan_reflexive_f64() {
let n: N5<f64> = N5::new(f64::NAN);
assert_eq!(n, N5::new(f64::NAN));
assert_eq!(
n.partial_cmp(&N5::new(f64::NAN)),
Some(core::cmp::Ordering::Equal)
);
}
#[test]
fn float_ext_int_helper_boundary_policy() {
let v = f32::NAN;
assert_eq!(
crate::__float_ext_int_ceil_body!(f32, u8, v),
Extended::PosInf
);
assert_eq!(
crate::__float_ext_int_floor_body!(f32, u8, v),
Extended::NegInf
);
let v = f32::NEG_INFINITY;
assert_eq!(
crate::__float_ext_int_ceil_body!(f32, u8, v),
Extended::NegInf
);
assert_eq!(
crate::__float_ext_int_floor_body!(f32, u8, v),
Extended::NegInf
);
let v = f32::INFINITY;
assert_eq!(
crate::__float_ext_int_ceil_body!(f32, u8, v),
Extended::PosInf
);
assert_eq!(
crate::__float_ext_int_floor_body!(f32, u8, v),
Extended::PosInf
);
}
#[cfg(feature = "f16")]
#[test]
fn float_ext_int_helper_f16_wide_boundary_policy() {
let v = f16::NAN;
assert_eq!(
crate::__float_ext_int_ceil_body!(f16, i16, v),
Extended::PosInf
);
assert_eq!(
crate::__float_ext_int_floor_body!(f16, i16, v),
Extended::NegInf
);
let v = f16::NEG_INFINITY;
assert_eq!(
crate::__float_ext_int_ceil_body!(f16, i16, v),
Extended::NegInf
);
assert_eq!(
crate::__float_ext_int_floor_body!(f16, i16, v),
Extended::NegInf
);
let v = f16::INFINITY;
assert_eq!(
crate::__float_ext_int_ceil_body!(f16, i16, v),
Extended::PosInf
);
assert_eq!(
crate::__float_ext_int_floor_body!(f16, i16, v),
Extended::PosInf
);
}
#[test]
fn nan_incomparable_with_finite_f64() {
let n: N5<f64> = N5::new(f64::NAN);
assert!(n.partial_cmp(&N5::new(1.0)).is_none());
assert!(N5::new(1.0_f64).partial_cmp(&n).is_none());
}
#[test]
fn nan_between_infinities_f64() {
let n: N5<f64> = N5::new(f64::NAN);
assert!(N5::new(f64::NEG_INFINITY) < n);
assert!(n < N5::new(f64::INFINITY));
}
#[test]
fn standard_order_preserved_f64() {
assert!(N5::new(1.0_f64) < N5::new(2.0));
assert!(N5::new(f64::NEG_INFINITY) < N5::new(0.0_f64));
assert!(N5::new(0.0_f64) < N5::new(f64::INFINITY));
}
#[test]
fn f32_same_shape() {
let n: N5<f32> = N5::new(f32::NAN);
assert_eq!(n, N5::new(f32::NAN));
assert!(N5::new(f32::NEG_INFINITY) < n);
assert!(n < N5::new(f32::INFINITY));
assert!(n.partial_cmp(&N5::new(1.0_f32)).is_none());
}
proptest! {
#[test]
fn n5_reflexive(x in extended_float_f64()) {
prop_assert_eq!(x, x);
prop_assert_eq!(x.partial_cmp(&x), Some(core::cmp::Ordering::Equal));
}
}
#[test]
fn add_extends_passes_through_f64() {
let a: N5<f64> = N5::new(1.5);
let b: N5<f64> = N5::new(2.25);
assert_eq!(a + b, N5::new(3.75));
}
#[test]
fn sub_zero_is_id_f64() {
let a: N5<f64> = N5::new(std::f64::consts::PI);
let zero: N5<f64> = N5::from(0u8);
assert_eq!(a - zero, a);
}
#[test]
fn div_by_two_halves_f64() {
let a: N5<f64> = N5::new(8.0);
let two: N5<f64> = N5::from(2u8);
assert_eq!(a / two, N5::new(4.0));
}
#[test]
fn from_u8_round_trips_f32_f64() {
for n in [0u8, 1, 2, 7, 42, u8::MAX] {
assert_eq!(N5::<f64>::from(n), N5::new(n as f64));
assert_eq!(N5::<f32>::from(n), N5::new(n as f32));
}
}
#[test]
fn sub_delegates_to_ieee_f64() {
let a: N5<f64> = N5::new(5.5);
let b: N5<f64> = N5::new(1.25);
assert_eq!(a - b, N5::new(4.25));
}
#[test]
fn arithmetic_delegates_to_ieee_at_infinities() {
let neg_inf: N5<f64> = N5::new(f64::NEG_INFINITY);
let pos_inf: N5<f64> = N5::new(f64::INFINITY);
let nan: N5<f64> = N5::new(f64::NAN);
assert!((neg_inf + pos_inf).is_nan());
assert!((pos_inf - pos_inf).is_nan());
assert!((nan + N5::new(1.0)).is_nan());
assert_eq!((-neg_inf), pos_inf);
}
#[test]
fn mul_extends_passes_through_f64() {
let a: N5<f64> = N5::new(2.5);
let b: N5<f64> = N5::new(4.0);
assert_eq!(a * b, N5::new(10.0));
}
#[test]
fn neg_double_is_id_f64() {
let a: N5<f64> = N5::new(std::f64::consts::PI);
assert_eq!(-(-a), a);
}
#[test]
fn neg_infinities_flip() {
assert_eq!(-N5::new(f64::NEG_INFINITY), N5::new(f64::INFINITY));
assert_eq!(-N5::new(f64::INFINITY), N5::new(f64::NEG_INFINITY));
let one: N5<f64> = N5::new(1.0);
assert_eq!(-one, N5::new(-1.0));
}
#[test]
fn rem_zero_inner_passes_through_f64() {
let a: N5<f64> = N5::new(7.5);
let zero: N5<f64> = N5::new(0.0);
assert!((a % zero).is_nan());
}
#[test]
fn rem_infinity_arms_f64() {
let one: N5<f64> = N5::new(1.0);
assert!((N5::new(f64::NEG_INFINITY) % one).is_nan());
assert!((N5::new(f64::INFINITY) % one).is_nan());
let a: N5<f64> = N5::new(7.5);
assert_eq!(a % N5::new(f64::INFINITY), a);
assert_eq!(a % N5::new(f64::NEG_INFINITY), a);
}
#[test]
fn div_infinity_arms_f64() {
assert!((N5::new(f64::NEG_INFINITY) / N5::new(f64::INFINITY)).is_nan());
let three: N5<f64> = N5::new(3.0);
assert_eq!((three / N5::new(f64::INFINITY)).into_inner(), 0.0);
assert_eq!((three / N5::new(f64::NEG_INFINITY)).into_inner(), -0.0);
}
#[test]
fn add_assign_matches_add_f64() {
let a: N5<f64> = N5::new(3.5);
let b: N5<f64> = N5::new(1.25);
let by_value = a + b;
let mut by_assign = a;
by_assign += b;
assert_eq!(by_assign, by_value);
}
#[test]
fn sub_assign_matches_sub_f64() {
let a: N5<f64> = N5::new(3.5);
let b: N5<f64> = N5::new(1.25);
let by_value = a - b;
let mut by_assign = a;
by_assign -= b;
assert_eq!(by_assign, by_value);
}
#[test]
fn mul_assign_matches_mul_f64() {
let a: N5<f64> = N5::new(3.0);
let b: N5<f64> = N5::new(4.0);
let by_value = a * b;
let mut by_assign = a;
by_assign *= b;
assert_eq!(by_assign, by_value);
}
#[test]
fn div_assign_matches_div_f64() {
let a: N5<f64> = N5::new(8.0);
let b: N5<f64> = N5::new(2.0);
let by_value = a / b;
let mut by_assign = a;
by_assign /= b;
assert_eq!(by_assign, by_value);
}
#[test]
fn rem_assign_matches_rem_f64() {
let a: N5<f64> = N5::new(7.5);
let b: N5<f64> = N5::new(2.0);
let by_value = a % b;
let mut by_assign = a;
by_assign %= b;
assert_eq!(by_assign, by_value);
}
#[test]
fn pi_constant_round_trips_f32_f64() {
assert_eq!(N5::<f32>::PI, N5::new(std::f32::consts::PI));
assert_eq!(N5::<f64>::PI, N5::new(std::f64::consts::PI));
}
#[test]
fn nan_infinity_constants_f64() {
assert!(N5::<f64>::NAN.is_nan());
assert_eq!(N5::<f64>::INFINITY, N5::new(f64::INFINITY));
assert_eq!(N5::<f64>::NEG_INFINITY, N5::new(f64::NEG_INFINITY));
assert_eq!(N5::<f64>::ZERO, N5::new(0.0));
assert_eq!(N5::<f64>::ONE, N5::new(1.0));
}
#[test]
fn predicates_classify_correctly_f64() {
let neg_inf = N5::<f64>::NEG_INFINITY;
let pos_inf = N5::<f64>::INFINITY;
let nan = N5::<f64>::NAN;
let pi = N5::<f64>::PI;
assert!(!neg_inf.is_nan() && !pos_inf.is_nan() && nan.is_nan() && !pi.is_nan());
assert!(!neg_inf.is_finite() && !pos_inf.is_finite() && !nan.is_finite() && pi.is_finite());
assert!(neg_inf.is_infinite() && pos_inf.is_infinite() && !pi.is_infinite());
}
#[test]
fn sin_extends_passes_through_f64() {
assert_eq!(N5::new(0.0_f64).sin(), N5::new(0.0));
assert!(N5::<f64>::NEG_INFINITY.sin().is_nan());
assert!(N5::<f64>::INFINITY.sin().is_nan());
}
#[test]
fn atan_endpoints_are_finite() {
assert_eq!(
N5::<f64>::NEG_INFINITY.atan(),
N5::new(-std::f64::consts::FRAC_PI_2)
);
assert_eq!(
N5::<f64>::INFINITY.atan(),
N5::new(std::f64::consts::FRAC_PI_2)
);
}
#[test]
fn exp_endpoints_f64() {
assert_eq!(N5::<f64>::NEG_INFINITY.exp(), N5::new(0.0));
assert_eq!(N5::<f64>::INFINITY.exp(), N5::<f64>::INFINITY);
}
#[test]
fn sqrt_endpoints_f64() {
assert_eq!(N5::<f64>::INFINITY.sqrt(), N5::<f64>::INFINITY);
assert!(N5::<f64>::NEG_INFINITY.sqrt().is_nan());
}
#[test]
fn ln_endpoints_f64() {
assert!(N5::<f64>::NEG_INFINITY.ln().is_nan());
assert_eq!(N5::<f64>::INFINITY.ln(), N5::<f64>::INFINITY);
}
#[test]
fn abs_endpoints_to_top_f64() {
assert_eq!(N5::<f64>::NEG_INFINITY.abs(), N5::<f64>::INFINITY);
assert_eq!(N5::<f64>::INFINITY.abs(), N5::<f64>::INFINITY);
}
#[test]
fn from_i8_round_trips_f64() {
let a: N5<f64> = (-3i8).into();
assert_eq!(a, N5::new(-3.0));
}
#[test]
fn from_i16_u16_round_trips_f64() {
let a: N5<f64> = (-12345i16).into();
assert_eq!(a, N5::new(-12345.0));
let b: N5<f64> = 65535u16.into();
assert_eq!(b, N5::new(65535.0));
}
#[test]
fn from_i32_u32_round_trips_f64_only() {
let a: N5<f64> = i32::MIN.into();
assert_eq!(a, N5::new(i32::MIN as f64));
let b: N5<f64> = u32::MAX.into();
assert_eq!(b, N5::new(u32::MAX as f64));
}
#[test]
fn from_inner_t_wraps_extend() {
let a: N5<f32> = std::f32::consts::PI.into();
assert_eq!(a, N5::new(std::f32::consts::PI));
let b: N5<f64> = std::f64::consts::E.into();
assert_eq!(b, N5::new(std::f64::consts::E));
}
}
#[cfg(test)]
mod solve_step_bound_tests {
use proptest::prelude::*;
#[inline]
fn shift_i64(n: i32, x: i64) -> i64 {
x.saturating_add(n as i64)
}
#[inline]
fn widen_i64_ns(x: i64) -> f64 {
(x as f64) * 1e-9
}
#[inline]
fn i64_to_ns(x: i64) -> i128 {
x as i128
}
#[inline]
fn i64_from_ns(n: i128) -> i64 {
if n > i64::MAX as i128 {
i64::MAX
} else if n < i64::MIN as i128 {
i64::MIN
} else {
n as i64
}
}
crate::float::def_walk_helpers!(
toy_ns_walks,
f64,
i64,
shift_i64,
widen_i64_ns,
i64_to_ns,
i64_from_ns
);
#[inline]
fn toy_start_from(x: f64) -> i64 {
let n = (x * 1e9) as i128;
if n > i64::MAX as i128 {
i64::MAX
} else if n < i64::MIN as i128 {
i64::MIN
} else {
n as i64
}
}
#[test]
fn solve_to_ceil_step_bound_at_zero() {
let (_z, steps) = toy_ns_walks::solve_to_ceil(0_i64, 0.0_f64);
assert!(steps <= 52, "got {steps}");
}
#[test]
fn solve_to_ceil_step_bound_at_high_magnitude() {
let x = 1.0e9_f64;
let start = toy_start_from(x);
let (_z, steps) = toy_ns_walks::solve_to_ceil(start, x);
assert!(steps <= 52, "got {steps}");
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(64))]
#[test]
fn solve_to_ceil_step_bound_holds(x in -1.0e9_f64..1.0e9_f64) {
let start = toy_start_from(x);
let (_z, steps) = toy_ns_walks::solve_to_ceil(start, x);
prop_assert!(steps <= 52, "got {steps}");
}
}
}