pub(crate) mod arith;
pub(crate) mod big;
mod casts;
mod cmp;
mod ord;
#[cfg(feature = "serde")]
mod serde;
pub(crate) mod small;
mod traits;
pub use crate::float::{
big::ParseFloatError,
ord::OrdFloat,
small::{SmallFloat, ToSmall},
};
use az::SaturatingCast;
use core::{i32, u32};
use gmp_mpfr_sys::mpfr::{self, prec_t};
#[inline]
pub fn exp_min() -> i32 {
unsafe { mpfr::get_emin() }.saturating_cast()
}
#[inline]
pub fn exp_max() -> i32 {
unsafe { mpfr::get_emax() }.saturating_cast()
}
#[inline]
pub fn allowed_exp_range() -> (i32, i32) {
unsafe {
(
mpfr::get_emin_min().saturating_cast(),
mpfr::get_emax_max().saturating_cast(),
)
}
}
#[inline]
pub const fn prec_min() -> u32 {
mpfr::PREC_MIN as u32
}
#[inline]
pub const fn prec_max() -> u32 {
const MAX_FITS: bool = mpfr::PREC_MAX < u32::MAX as prec_t;
const VALUES: [u32; 2] = [u32::MAX, mpfr::PREC_MAX as u32];
const PREC_MAX: u32 = VALUES[MAX_FITS as usize];
PREC_MAX
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[allow(clippy::manual_non_exhaustive)]
pub enum Round {
Nearest,
Zero,
Up,
Down,
#[doc(hidden)]
__Nonexhaustive,
}
impl Default for Round {
#[inline]
fn default() -> Round {
Round::Nearest
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[allow(clippy::manual_non_exhaustive)]
pub enum Constant {
Log2,
Pi,
Euler,
Catalan,
#[doc(hidden)]
__Nonexhaustive,
}
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
#[allow(clippy::manual_non_exhaustive)]
pub enum Special {
Zero,
NegZero,
Infinity,
NegInfinity,
Nan,
#[doc(hidden)]
__Nonexhaustive,
}
#[allow(clippy::needless_doctest_main)]
#[allow(clippy::manual_non_exhaustive)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum FreeCache {
Local,
Global,
All,
#[doc(hidden)]
__Nonexhaustive,
}
#[allow(clippy::needless_doctest_main)]
#[inline]
pub fn free_cache(which: FreeCache) {
let way = match which {
FreeCache::Local => mpfr::FREE_LOCAL_CACHE,
FreeCache::Global => mpfr::FREE_GLOBAL_CACHE,
FreeCache::All => mpfr::FREE_LOCAL_CACHE | mpfr::FREE_GLOBAL_CACHE,
_ => unreachable!(),
};
unsafe {
mpfr::free_cache2(way);
}
}
#[cfg(test)]
#[allow(clippy::cognitive_complexity, clippy::float_cmp)]
pub(crate) mod tests {
#[cfg(feature = "rand")]
use crate::rand::{RandGen, RandState};
use crate::{
float::{self, FreeCache, Round, Special},
ops::NegAssign,
Assign, Float,
};
use az::Az;
use core::{
cmp::Ordering,
f64,
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, "\"{}\" (radix {})", s, radix),
}
}
let good_strings = [
("INF", 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);
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!("{:e}", f), "0");
assert_eq!(format!("{:?}", f), "0");
assert_eq!(format!("{:+?}", f), "+0");
assert_eq!(format!("{:<10}", f), "0 ");
assert_eq!(format!("{:>10}", f), " 0");
assert_eq!(format!("{:10}", f), " 0");
assert_eq!(format!("{:^10}", f), " 0 ");
assert_eq!(format!("{:^11}", f), " 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!("{:x}", f), "@inf@");
f.assign(Special::NegInfinity);
assert_eq!(format!("{}", f), "-inf");
assert_eq!(format!("{:x}", f), "-@inf@");
f.assign(Special::Nan);
assert_eq!(format!("{}", f), "NaN");
assert_eq!(format!("{:+}", f), "+NaN");
assert_eq!(format!("{:x}", f), "@NaN@");
f = -f;
assert_eq!(format!("{}", f), "-NaN");
assert_eq!(format!("{:x}", f), "-@NaN@");
f.assign(-27);
assert_eq!(format!("{:.2}", f), "-27");
assert_eq!(format!("{:.4?}", f), "-27.00");
assert_eq!(format!("{:.4e}", f), "-2.700e1");
assert_eq!(format!("{:.4E}", f), "-2.700E1");
assert_eq!(format!("{:.8b}", f), "-11011.000");
assert_eq!(format!("{:.3b}", f), "-1.11e4");
assert_eq!(format!("{:#.8b}", f), "-0b11011.000");
assert_eq!(format!("{:.2o}", f), "-33");
assert_eq!(format!("{:#.2o}", f), "-0o33");
assert_eq!(format!("{:.2x}", f), "-1b");
assert_eq!(format!("{:.2X}", f), "-1B");
assert_eq!(format!("{:12.1x}", f), " -1b");
assert_eq!(format!("{:012.3X}", f), "-00000001B.0");
assert_eq!(format!("{:#012.2x}", f), "-0x00000001b");
assert_eq!(format!("{:#12.2X}", f), " -0x1B");
f <<= 144;
assert_eq!(format!("{:.8b}", f), "-1.1011000e148");
assert_eq!(format!("{:.3b}", f), "-1.11e148");
assert_eq!(format!("{:#.8b}", f), "-0b1.1011000e148");
assert_eq!(format!("{:.2o}", f), "-3.3e49");
assert_eq!(format!("{:#.2o}", f), "-0o3.3e49");
assert_eq!(format!("{:.2x}", f), "-1.b@37");
assert_eq!(format!("{:.2X}", f), "-1.B@37");
assert_eq!(format!("{:12.1x}", f), " -1.b@37");
assert_eq!(format!("{:012.3X}", f), "-00001.B0@37");
assert_eq!(format!("{:#012.2x}", f), "-0x0001.b@37");
assert_eq!(format!("{:#12.2X}", f), " -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);
}
}