#![allow(clippy::float_cmp)]
use crate::float;
use crate::float::{FreeCache, Round, Special};
use crate::ops::{AddAssignRound, AssignRound, NegAssign, SubAssignRound, SubFrom, SubFromRound};
#[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.iter() {
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.iter() {
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());
clear_nanflag();
let a = nan.as_abs();
assert!(a.is_nan());
assert!(nanflag());
}
#[cfg(feature = "rand")]
struct OnesZerosRand {
one_words: u32,
}
#[cfg(feature = "rand")]
impl RandGen for OnesZerosRand {
fn gen(&mut self) -> u32 {
if self.one_words > 0 {
self.one_words -= 1;
!0
} else {
0
}
}
}
#[cfg(feature = "rand")]
#[test]
fn check_nan_random_bits() {
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;
unsafe {
save_emin = mpfr::get_emin();
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);
}