#![allow(clippy::float_cmp)]
use crate::float;
use crate::float::{FreeCache, Round, Special};
use crate::ops::{AddAssignRound, AssignRound, NegAssign, SubAssignRound, SubFrom, SubFromRound};
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
use crate::rand::{RandGen, RandState};
use crate::{Assign, Float};
use az::Az;
use core::cmp::Ordering;
use core::fmt::{Debug, Error as FmtError, Formatter};
use gmp_mpfr_sys::{gmp, mpfr};
pub fn nanflag() -> bool {
unsafe { mpfr::nanflag_p() != 0 }
}
pub fn clear_nanflag() {
unsafe {
mpfr::clear_nanflag();
}
}
#[derive(Clone, Copy)]
pub enum Cmp {
F64(f64),
Nan(bool),
}
impl Cmp {
pub fn inf(neg: bool) -> Cmp {
Cmp::F64(if neg {
f64::NEG_INFINITY
} else {
f64::INFINITY
})
}
}
impl Debug for Cmp {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match *self {
Cmp::F64(ref val) => val.fmt(f),
Cmp::Nan(negative) => {
let s = if negative { "-NaN" } else { "NaN" };
s.fmt(f)
}
}
}
}
impl PartialEq<Cmp> for Float {
fn eq(&self, other: &Cmp) -> bool {
match *other {
Cmp::F64(ref f) => self.eq(f),
Cmp::Nan(negative) => self.is_nan() && self.is_sign_negative() == negative,
}
}
}
#[test]
fn check_from_str() {
assert!(Float::with_val(53, Float::parse("-0").unwrap()).is_sign_negative());
assert!(Float::with_val(53, Float::parse("+0").unwrap()).is_sign_positive());
assert!(Float::with_val(53, Float::parse("1e1000").unwrap()).is_finite());
let huge_hex = "1@99999999999999999999999999999999";
assert!(Float::with_val(53, Float::parse_radix(huge_hex, 16).unwrap()).is_infinite());
let bad_strings = [
("", 10, "string has no digits"),
("-", 10, "string has no digits"),
("+", 10, "string has no digits"),
(".", 10, "string has no digits"),
("inf", 11, "invalid digit found in string"),
("@ nan @", 10, "string has no digits for significand"),
("inf", 16, "invalid digit found in string"),
("1.1.", 10, "more than one point found in string"),
("1e", 10, "string has no digits for exponent"),
("e10", 10, "string has no digits for significand"),
(".e10", 10, "string has no digits for significand"),
("1e1.", 10, "string has point in exponent"),
("1e1e1", 10, "more than one exponent found in string"),
("1e+-1", 10, "invalid digit found in string"),
("1e-+1", 10, "invalid digit found in string"),
("+-1", 10, "invalid digit found in string"),
("-+1", 10, "invalid digit found in string"),
("infinit", 10, "invalid digit found in string"),
("1@1a", 16, "invalid digit found in string"),
("9", 9, "invalid digit found in string"),
("nan(20) x", 10, "invalid digit found in string"),
];
for &(s, radix, msg) in &bad_strings {
match Float::parse_radix(s, radix) {
Ok(o) => panic!(
"\"{}\" (radix {}) parsed correctly as {}, expected: {}",
s,
radix,
Float::with_val(53, o),
msg
),
Err(e) => assert_eq!(e.to_string(), msg, "\"{s}\" (radix {radix})"),
}
}
let good_strings = [
("INF", 10, Cmp::inf(false)),
("iNfIniTY", 10, Cmp::inf(false)),
("- @iNf@", 16, Cmp::inf(true)),
("+0e99", 2, Cmp::F64(0.0)),
("-9.9e1", 10, Cmp::F64(-99.0)),
("-.99e+2", 10, Cmp::F64(-99.0)),
("+99.e+0", 10, Cmp::F64(99.0)),
("-99@-1", 10, Cmp::F64(-9.9f64)),
("-a_b__.C_d_E_@3", 16, Cmp::F64(f64::from(-0xabcde))),
("1e1023", 2, Cmp::F64(2.0f64.powi(1023))),
(" NaN() ", 10, Cmp::Nan(false)),
(" + NaN (20 Number_Is) ", 10, Cmp::Nan(false)),
(" - @nan@", 2, Cmp::Nan(true)),
];
for &(s, radix, f) in &good_strings {
match Float::parse_radix(s, radix) {
Ok(ok) => assert_eq!(Float::with_val(53, ok), f),
Err(err) => panic!("could not parse {s}: {err}"),
}
}
float::free_cache(FreeCache::All);
}
#[test]
fn check_clamping() {
let mut f = Float::new(4);
f.assign(-1);
let dir = f.clamp_round(&1.00002, &1.00001, Round::Down);
assert_eq!(f, 1.0);
assert_eq!(dir, Ordering::Less);
f.assign(-1);
let dir = f.clamp_round(&1.00002, &1.00001, Round::Up);
assert_eq!(f, 1.125);
assert_eq!(dir, Ordering::Greater);
f.assign(2);
let dir = f.clamp_round(&1.00002, &1.00001, Round::Down);
assert_eq!(f, 1.0);
assert_eq!(dir, Ordering::Less);
f.assign(2);
let dir = f.clamp_round(&1.00002, &1.00001, Round::Up);
assert_eq!(f, 1.125);
assert_eq!(dir, Ordering::Greater);
float::free_cache(FreeCache::All);
}
#[test]
#[should_panic(expected = "minimum larger than maximum")]
fn check_clamping_panic() {
let mut f = Float::new(4);
f.assign(-1);
let _panic = f.clamp(&1.00001, &0.99999);
}
#[test]
fn check_formatting() {
let mut f = Float::with_val(53, Special::Zero);
assert_eq!(format!("{f}"), "0");
assert_eq!(format!("{f:e}"), "0");
assert_eq!(format!("{f:?}"), "0");
assert_eq!(format!("{f:+?}"), "+0");
assert_eq!(format!("{f:<10}"), "0 ");
assert_eq!(format!("{f:>10}"), " 0");
assert_eq!(format!("{f:10}"), " 0");
assert_eq!(format!("{f:^10}"), " 0 ");
assert_eq!(format!("{f:^11}"), " 0 ");
f.assign(Special::NegZero);
assert_eq!(format!("{f}"), "-0");
assert_eq!(format!("{f:?}"), "-0");
assert_eq!(format!("{f:+?}"), "-0");
f.assign(Special::Infinity);
assert_eq!(format!("{f}"), "inf");
assert_eq!(format!("{f:+}"), "+inf");
assert_eq!(format!("{f:x}"), "@inf@");
f.assign(Special::NegInfinity);
assert_eq!(format!("{f}"), "-inf");
assert_eq!(format!("{f:x}"), "-@inf@");
f.assign(Special::Nan);
assert_eq!(format!("{f}"), "NaN");
assert_eq!(format!("{f:+}"), "+NaN");
assert_eq!(format!("{f:x}"), "@NaN@");
f = -f;
assert_eq!(format!("{f}"), "-NaN");
assert_eq!(format!("{f:x}"), "-@NaN@");
f.assign(-2.75);
assert_eq!(format!("{f:.1}"), "-3");
assert_eq!(format!("{f:.2}"), "-2.8");
assert_eq!(format!("{f:.4?}"), "-2.750");
assert_eq!(format!("{f:.1e}"), "-3e0");
assert_eq!(format!("{f:.2e}"), "-2.8e0");
assert_eq!(format!("{f:.4e}"), "-2.750e0");
assert_eq!(format!("{f:.4E}"), "-2.750E0");
assert_eq!(format!("{f:.8b}"), "-10.110000");
assert_eq!(format!("{f:.3b}"), "-11.0");
assert_eq!(format!("{f:#.8b}"), "-0b10.110000");
assert_eq!(format!("{f:.2o}"), "-2.6");
assert_eq!(format!("{f:#.2o}"), "-0o2.6");
assert_eq!(format!("{f:.2x}"), "-2.c");
assert_eq!(format!("{f:.2X}"), "-2.C");
assert_eq!(format!("{f:12.1x}"), " -3");
assert_eq!(format!("{f:12.2x}"), " -2.c");
assert_eq!(format!("{f:012.3X}"), "-00000002.C0");
assert_eq!(format!("{f:#012.2x}"), "-0x0000002.c");
assert_eq!(format!("{f:#12.2X}"), " -0x2.C");
f.assign(-27);
assert_eq!(format!("{f:.1}"), "-3e1");
assert_eq!(format!("{f:.2}"), "-27");
assert_eq!(format!("{f:.4?}"), "-27.00");
assert_eq!(format!("{f:.1e}"), "-3e1");
assert_eq!(format!("{f:.2e}"), "-2.7e1");
assert_eq!(format!("{f:.4e}"), "-2.700e1");
assert_eq!(format!("{f:.4E}"), "-2.700E1");
assert_eq!(format!("{f:.8b}"), "-11011.000");
assert_eq!(format!("{f:.3b}"), "-1.11e4");
assert_eq!(format!("{f:#.8b}"), "-0b11011.000");
assert_eq!(format!("{f:.2o}"), "-33");
assert_eq!(format!("{f:#.2o}"), "-0o33");
assert_eq!(format!("{f:.2x}"), "-1b");
assert_eq!(format!("{f:.2X}"), "-1B");
assert_eq!(format!("{f:12.1x}"), " -2@1");
assert_eq!(format!("{f:12.2x}"), " -1b");
assert_eq!(format!("{f:012.3X}"), "-00000001B.0");
assert_eq!(format!("{f:#012.2x}"), "-0x00000001b");
assert_eq!(format!("{f:#12.2X}"), " -0x1B");
f <<= 144;
assert_eq!(format!("{f:.8b}"), "-1.1011000e148");
assert_eq!(format!("{f:.3b}"), "-1.11e148");
assert_eq!(format!("{f:#.8b}"), "-0b1.1011000e148");
assert_eq!(format!("{f:.2o}"), "-3.3e49");
assert_eq!(format!("{f:#.2o}"), "-0o3.3e49");
assert_eq!(format!("{f:.1x}"), "-2@37");
assert_eq!(format!("{f:.2x}"), "-1.b@37");
assert_eq!(format!("{f:.2X}"), "-1.B@37");
assert_eq!(format!("{f:12.1x}"), " -2@37");
assert_eq!(format!("{f:12.2x}"), " -1.b@37");
assert_eq!(format!("{f:012.3X}"), "-00001.B0@37");
assert_eq!(format!("{f:#012.2x}"), "-0x0001.b@37");
assert_eq!(format!("{f:#12.2X}"), " -0x1.B@37");
float::free_cache(FreeCache::All);
}
#[test]
fn check_assumptions() {
assert_eq!(unsafe { mpfr::custom_get_size(64) }, 8);
assert!(unsafe { mpfr::custom_get_size(32) } <= gmp::NUMB_BITS.az::<usize>());
float::free_cache(FreeCache::All);
}
#[test]
fn check_i_pow_u() {
for &(i, u) in &[(13, 4), (13, 5), (-13, 4), (-13, 5)] {
let p = Float::i_pow_u(i, u);
let f = Float::with_val(53, p);
assert_eq!(f, i.pow(u));
}
}
#[test]
fn check_nanflag() {
clear_nanflag();
let nan = Float::with_val(53, Special::Nan);
assert!(!nanflag());
clear_nanflag();
let c = nan.clone();
assert!(c.is_nan());
assert!(!nanflag());
clear_nanflag();
let mut m = Float::new(53);
assert!(!m.is_nan());
assert!(!nanflag());
m.clone_from(&nan);
assert!(m.is_nan());
assert!(!nanflag());
m.assign(&nan);
assert!(m.is_nan());
assert!(nanflag());
clear_nanflag();
m.assign(nan.clone());
assert!(m.is_nan());
assert!(nanflag());
clear_nanflag();
let c = Float::with_val(53, -&nan);
assert!(c.is_nan());
assert!(nanflag());
clear_nanflag();
let mut m = nan.clone();
m.neg_assign();
assert!(m.is_nan());
assert!(nanflag());
clear_nanflag();
let c = Float::with_val(53, nan.clamp_ref(&0, &0));
assert!(c.is_nan());
assert!(nanflag());
clear_nanflag();
let mut m = nan.clone();
m.clamp_mut(&0, &0);
assert!(m.is_nan());
assert!(nanflag());
clear_nanflag();
let a = nan.as_neg();
assert!(a.is_nan());
assert!(!nanflag());
let a = nan.as_abs();
assert!(a.is_nan());
assert!(!nanflag());
}
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
struct OnesZerosRand {
one_words: u32,
}
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
impl RandGen for OnesZerosRand {
fn r#gen(&mut self) -> u32 {
if self.one_words > 0 {
self.one_words -= 1;
!0
} else {
0
}
}
}
#[cfg(feature = "std")]
#[cfg(feature = "rand")]
#[test]
fn check_nan_random_bits() {
use crate::ext::xmpfr;
for i in 0..2 {
let mut zeros_ones = OnesZerosRand { one_words: 2 };
let mut rand = RandState::new_custom(&mut zeros_ones);
let save_emin = xmpfr::get_emin();
unsafe {
mpfr::set_emin(-192 + i);
}
let f = Float::with_val(256, Float::random_bits(&mut rand));
if i == 0 {
assert_eq!(f, Float::with_val(64, !0u64) >> 256);
} else {
assert!(f.is_nan());
}
unsafe {
mpfr::set_emin(save_emin);
}
}
float::free_cache(FreeCache::All);
}
#[test]
fn check_sum_dot() {
let numbers = &[Float::with_val(53, 2.5), Float::with_val(53, -10)];
let sum = || Float::sum(numbers.iter());
let dot = || Float::dot(numbers.iter().zip(numbers.iter().rev()));
let mut n = Float::new(53);
let mut n3 = Float::new(3);
n.assign(sum());
assert_eq!(n, -7.5);
n3.assign(sum());
assert_eq!(n3, -8);
assert_eq!(n3.assign_round(sum(), Round::Down), Ordering::Less);
assert_eq!(n3, -8);
assert_eq!(n3.assign_round(sum(), Round::Up), Ordering::Greater);
assert_eq!(n3, -7);
n += sum();
assert_eq!(n, -15);
assert_eq!(n3.add_assign_round(sum(), Round::Down), Ordering::Less);
assert_eq!(n3, -16);
assert_eq!(n3.add_assign_round(sum(), Round::Up), Ordering::Greater);
assert_eq!(n3, -20);
assert_eq!(n.clone() + sum(), -22.5);
assert_eq!(sum() + n.clone(), -22.5);
n -= sum();
assert_eq!(n, -7.5);
n.assign(10);
n.sub_from(sum());
assert_eq!(n, -17.5);
assert_eq!(n3.sub_assign_round(sum(), Round::Down), Ordering::Less);
assert_eq!(n3, -14);
assert_eq!(n3.sub_assign_round(sum(), Round::Up), Ordering::Greater);
assert_eq!(n3, -6);
n3.assign(10);
assert_eq!(n3.sub_from_round(sum(), Round::Down), Ordering::Less);
assert_eq!(n3, -20);
assert_eq!(n3.sub_from_round(sum(), Round::Up), Ordering::Greater);
assert_eq!(n3, 14);
assert_eq!(n.clone() - sum(), -10);
assert_eq!(sum() - n.clone(), 10);
n.assign(dot());
assert_eq!(n, -50);
n3.assign(dot());
assert_eq!(n3, -48);
assert_eq!(n3.assign_round(dot(), Round::Down), Ordering::Less);
assert_eq!(n3, -56);
assert_eq!(n3.assign_round(dot(), Round::Up), Ordering::Greater);
assert_eq!(n3, -48);
n += dot();
assert_eq!(n, -100);
assert_eq!(n3.add_assign_round(dot(), Round::Down), Ordering::Less);
assert_eq!(n3, -112);
assert_eq!(n3.add_assign_round(dot(), Round::Up), Ordering::Greater);
assert_eq!(n3, -160);
assert_eq!(n.clone() + dot(), -150);
assert_eq!(dot() + n.clone(), -150);
n -= dot();
assert_eq!(n, -50);
n.assign(10);
n.sub_from(dot());
assert_eq!(n, -60);
assert_eq!(n3.sub_assign_round(dot(), Round::Down), Ordering::Less);
assert_eq!(n3, -112);
assert_eq!(n3.sub_assign_round(dot(), Round::Up), Ordering::Greater);
assert_eq!(n3, -56);
n3.assign(20);
assert_eq!(n3.sub_from_round(dot(), Round::Down), Ordering::Less);
assert_eq!(n3, -80);
assert_eq!(n3.sub_from_round(dot(), Round::Up), Ordering::Greater);
assert_eq!(n3, 32);
assert_eq!(n.clone() - dot(), -10);
assert_eq!(dot() - n, 10);
}
#[test]
fn check_issue_56() {
let x = Float::with_val(21, 1_111_111) >> 154u32;
let rounded = x.to_f32_round(Round::Up);
assert!(rounded >= x);
}
#[test]
fn check_remainder_quo31() {
let num = Float::with_val(100, 1u32 << 31);
let den = Float::with_val(100, -1);
let (remainder, quo31) = num.remainder_quo31(&den);
assert_eq!(remainder, 0);
assert_eq!(quo31, 0);
let num = Float::with_val(100, 1);
let den = Float::with_val(100, -1);
let (remainder, quo31) = num.remainder_quo31(&den);
assert_eq!(remainder, 0);
assert_eq!(quo31, -1);
}
#[track_caller]
fn check_sub(factor: f64, prev_ord: Ordering, round: Round, expected: f64, ord: Ordering) {
let mut f = Float::with_val(53, f64::from_bits(1)) * factor;
assert_eq!(f.subnormalize_ieee_round(prev_ord, round), ord);
assert_eq!(f, expected);
assert_eq!(f.is_sign_negative(), expected.is_sign_negative());
}
#[track_caller]
fn check_sub_exact(factor: f64, expected: f64) {
use Ordering::*;
use Round::*;
for prev_ord in [Less, Equal, Greater] {
for round in [Nearest, Down, Up, Zero, AwayZero] {
check_sub(factor, prev_ord, round, expected, prev_ord);
}
}
}
#[track_caller]
fn check_sub_up_down(factor: f64, expected_up: f64, expected_down: f64) {
use Ordering::*;
use Round::*;
let (expected_zero, dir_zero, expected_away, dir_away) = if factor.is_sign_negative() {
(expected_up, Greater, expected_down, Less)
} else {
(expected_down, Less, expected_up, Greater)
};
for prev_ord in [Less, Equal, Greater] {
check_sub(factor, prev_ord, Down, expected_down, Less);
check_sub(factor, prev_ord, Up, expected_up, Greater);
check_sub(factor, prev_ord, Zero, expected_zero, dir_zero);
check_sub(factor, prev_ord, AwayZero, expected_away, dir_away);
}
}
#[test]
fn check_subnormalize_tiny() {
use Ordering::*;
use Round::*;
let sub = f64::from_bits(1);
check_sub_exact(1.0, sub);
check_sub_up_down(0.75, sub, 0.0);
check_sub(0.75, Less, Nearest, sub, Greater);
check_sub(0.75, Equal, Nearest, sub, Greater);
check_sub(0.75, Greater, Nearest, sub, Greater);
check_sub_up_down(0.5, sub, 0.0);
check_sub(0.5, Less, Nearest, sub, Greater);
check_sub(0.5, Equal, Nearest, 0.0, Less);
check_sub(0.5, Greater, Nearest, 0.0, Less);
check_sub_up_down(0.25, sub, 0.0);
check_sub(0.25, Less, Nearest, 0.0, Less);
check_sub(0.25, Equal, Nearest, 0.0, Less);
check_sub(0.25, Greater, Nearest, 0.0, Less);
check_sub_exact(0.0, 0.0);
check_sub_exact(-0.0, -0.0);
check_sub_up_down(-0.25, -0.0, -sub);
check_sub(-0.25, Less, Nearest, -0.0, Greater);
check_sub(-0.25, Equal, Nearest, -0.0, Greater);
check_sub(-0.25, Greater, Nearest, -0.0, Greater);
check_sub_up_down(-0.5, -0.0, -sub);
check_sub(-0.5, Less, Nearest, -0.0, Greater);
check_sub(-0.5, Equal, Nearest, -0.0, Greater);
check_sub(-0.5, Greater, Nearest, -sub, Less);
check_sub_up_down(-0.75, -0.0, -sub);
check_sub(-0.75, Less, Nearest, -sub, Less);
check_sub(-0.75, Equal, Nearest, -sub, Less);
check_sub(-0.75, Greater, Nearest, -sub, Less);
check_sub_exact(-1.0, -sub);
}