use gmp_mpfr_sys::mpfr::{self, rnd_t};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use rug::Float;
use std::cell::RefCell;
use zenith_float_num::{
Consts, ExactNum, Exponent, Radix, RoundingMode, Sign, Word, WORD_BIT_SIZE,
WORD_SIGNIFICANT_BIT,
};
thread_local! {
static TEST_RNG: RefCell<StdRng> = RefCell::new(StdRng::seed_from_u64(0x5EED_CAFE_BADC_0D00));
}
pub fn test_random<T>() -> T
where
rand::distributions::Standard: rand::distributions::Distribution<T>,
{
TEST_RNG.with(|rng| rng.borrow_mut().gen())
}
pub fn reset_test_rng() {
TEST_RNG.with(|rng| {
*rng.borrow_mut() = StdRng::seed_from_u64(0x5EED_CAFE_BADC_0D00);
});
zenith_float_num::reseed_random(zenith_float_num::DEFAULT_RANDOM_SEED);
}
macro_rules! test_zf_op {
($eq:literal, $n1:ident, $n2:ident, $zf_op:ident, $f1:ident, $f2:ident, $mpfr_op:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n3 = ExactNum::$zf_op(&($n1), &($n2), $p, $rm, &mut $cc);
let mut f3 = Float::with_val($p as u32, 1);
unsafe { mpfr::$mpfr_op(f3.as_raw_mut(), ($f1).as_raw(), ($f2).as_raw(), $rnd) };
assert_float_close(n3, f3, $p, &format!("{:?}", $op_info), $eq, &mut $cc);
};
($eq:literal, $n1:ident, $zf_op:ident, $f1:ident, $mpfr_op:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n3 = ExactNum::$zf_op(&($n1), $p, $rm, &mut $cc);
let mut f3 = Float::with_val($p as u32, 1);
unsafe { mpfr::$mpfr_op(f3.as_raw_mut(), ($f1).as_raw(), $rnd) };
assert_float_close(n3, f3, $p, &format!("{:?}", $op_info), $eq, &mut $cc);
};
($eq:literal, $n1:ident, $zf_op:ident, $f1:ident, $mpfr_op:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n3 = ExactNum::$zf_op(&($n1), $p, $rm, &mut $cc);
let mut f3 = Float::with_val($p as u32, 1);
unsafe { mpfr::$mpfr_op(f3.as_raw_mut(), ($f1).as_raw(), $rnd) };
assert_float_close(n3, f3, $p, &format!("{:?}", $op_info), $eq, &mut $cc);
};
}
macro_rules! test_zf_op_no_cc {
($eq:literal, $n1:ident, $n2:ident, $zf_op:ident, $f1:ident, $f2:ident, $mpfr_op:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n3 = ExactNum::$zf_op(&($n1), &($n2), $p, $rm);
let mut f3 = Float::with_val($p as u32, 1);
unsafe { mpfr::$mpfr_op(f3.as_raw_mut(), ($f1).as_raw(), ($f2).as_raw(), $rnd) };
assert_float_close(n3, f3, $p, &format!("{:?}", $op_info), $eq, &mut $cc);
};
($eq:literal, $n1:ident, $zf_op:ident, $f1:ident, $mpfr_op:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n3 = ExactNum::$zf_op(&($n1), $p, $rm);
let mut f3 = Float::with_val($p as u32, 1);
unsafe { mpfr::$mpfr_op(f3.as_raw_mut(), ($f1).as_raw(), $rnd) };
assert_float_close(n3, f3, $p, &format!("{:?}", $op_info), $eq, &mut $cc);
};
}
macro_rules! test_zf_fma {
($eq:literal, $n1:ident, $n2:ident, $nc:ident, $f1:ident, $f2:ident, $fc:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n4 = ExactNum::fma(&($n1), &($n2), &($nc), $p, $rm);
let mut f4 = Float::with_val($p as u32, 1);
unsafe {
mpfr::fma(
f4.as_raw_mut(),
($f1).as_raw(),
($f2).as_raw(),
($fc).as_raw(),
$rnd,
)
};
assert_float_close(n4, f4, $p, &format!("{:?}", $op_info), $eq, &mut $cc);
};
}
macro_rules! test_zf_rem_pi {
($n1:ident, $f1:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let mut n3 = ExactNum::rem_pi(&($n1), $p, $rm, &mut $cc);
let _ = n3.set_precision($p, $rm);
let _ = $rnd;
if ($n1).exponent().unwrap_or(0) <= 2 {
assert_float_close(
n3,
($f1).clone(),
$p,
&format!("{:?}", $op_info),
true,
&mut $cc,
);
} else if !n3.is_nan() && !n3.is_inf() && !n3.is_zero() {
let e = n3.exponent().unwrap_or(0);
assert!(
e <= 3,
"{}",
format!("{:?} rem_pi exponent {e} (want <= 3)", $op_info)
);
}
};
}
macro_rules! test_zf_const {
($zf_const:ident, $mpfr_const:ident, $p:ident, $rm:ident, $rnd:ident, $op_info:expr, $cc:ident) => {
let n1: ExactNum = $cc.$zf_const($p, $rm);
let mut f1 = Float::with_val($p as u32, 1);
unsafe {
mpfr::$mpfr_const(f1.as_raw_mut(), $rnd);
}
assert_float_close(n1, f1, $p, &format!("{:?}", $op_info), true, &mut $cc);
};
}
pub(crate) use test_zf_const;
pub(crate) use test_zf_fma;
pub(crate) use test_zf_op;
pub(crate) use test_zf_op_no_cc;
pub(crate) use test_zf_rem_pi;
pub const fn get_prec_rng() -> usize {
157
}
pub fn get_float_pair(
p: usize,
emin: Exponent,
emax: Exponent,
cc: &mut Consts,
) -> (ExactNum, Float) {
let n = ExactNum::random_normal(p, emin, emax);
let f = conv_to_mpfr(p, &n, cc);
(n, f)
}
pub fn conv_to_mpfr(p: usize, n: &ExactNum, cc: &mut Consts) -> Float {
let s1 = conv_str_to_mpfr_compat(n.format(Radix::Bin, RoundingMode::ToEven, cc).unwrap());
let f = Float::with_val(p as u32, Float::parse_radix(s1, 2).unwrap());
let s2 = conv_str_from_mpfr_compat(f.to_string_radix(2, None));
assert_eq!(
*n,
ExactNum::parse(&s2, Radix::Bin, p, RoundingMode::None, cc)
);
f
}
pub fn assert_float_close(n: ExactNum, f: Float, p: usize, op: &str, eq: bool, cc: &mut Consts) {
if n.is_inf() {
let ovf = unsafe { mpfr::overflow_p() };
assert!(f.is_infinite() || ovf != 0, "{}", op);
if eq {
if n.is_positive() {
assert!(f.is_sign_positive(), "{}", op);
} else {
assert!(f.is_sign_negative(), "{}", op);
}
}
} else if n.is_nan() {
if eq {
assert!(f.is_nan(), "{}", op);
}
return;
} else if (n.is_subnormal() || n.is_zero()) && eq {
let unf = unsafe { mpfr::underflow_p() };
if !f.is_zero() {
assert!(unf != 0, "{}", op);
}
} else if eq {
let s1 = f.to_string_radix(2, None);
let s2 = conv_str_from_mpfr_compat(s1);
let n2 = ExactNum::parse(&s2, Radix::Bin, p, RoundingMode::None, cc);
assert_eq!(n, n2, "{}", op);
} else {
let s1 = f.to_string_radix(2, None);
let s2 = conv_str_from_mpfr_compat(s1);
let n2 = ExactNum::parse(&s2, Radix::Bin, p, RoundingMode::None, cc);
assert_eq!(
n.mantissa_max_bit_len(),
n2.mantissa_max_bit_len(),
"{}",
op
);
let d = n.sub(&n2, 1, RoundingMode::None).abs();
let e1 = n.exponent().unwrap() as isize
- (n.mantissa_max_bit_len().unwrap() - n.precision().unwrap()) as isize;
let e2 = d.exponent().unwrap() as isize
- (d.mantissa_max_bit_len().unwrap() - d.precision().unwrap()) as isize;
if !d.is_zero() {
assert!(
e1 - e2 >= n.mantissa_max_bit_len().unwrap() as isize - 1,
"{}",
op
);
}
}
}
pub fn conv_str_to_mpfr_compat(s: String) -> String {
let (sig, exp) = if let Some(pos) = s.find('e') {
s.split_at(pos + 1)
} else {
(s.as_str(), "0")
};
let expn = i64::from_str_radix(exp, 2).unwrap();
sig.to_owned() + &expn.to_string()
}
pub fn conv_str_from_mpfr_compat(s: String) -> String {
if let Some(epos) = s.find('e') {
let (sig, exp) = s.split_at(epos + 1);
let expn = exp.parse::<i64>().unwrap();
if expn < 0 {
sig.to_owned() + "-" + &format!("{:b}", -expn)
} else {
sig.to_owned() + &format!("{:b}", expn)
}
} else {
s
}
}
pub fn get_random_rnd_pair() -> (RoundingMode, rnd_t) {
match test_random::<u8>() % 5 {
0 => (RoundingMode::ToEven, rnd_t::RNDN),
1 => (RoundingMode::Up, rnd_t::RNDU),
2 => (RoundingMode::Down, rnd_t::RNDD),
3 => (RoundingMode::FromZero, rnd_t::RNDA),
4 => (RoundingMode::ToZero, rnd_t::RNDZ),
_ => unreachable!(),
}
}
pub fn get_oned_zeroed(p: usize, exp_from: Exponent, exp_to: Exponent) -> ExactNum {
let zero_bits = test_random::<usize>() % (p - 1);
let mut m1 = vec![Word::MAX; p / WORD_BIT_SIZE];
let i = zero_bits / WORD_BIT_SIZE;
m1.iter_mut().take(i).for_each(|v| *v = 0);
m1[i] <<= zero_bits % WORD_BIT_SIZE;
bf_from_mantissa_and_exp_rng(&m1, exp_from, exp_to)
}
pub fn get_oned_sides(p: usize, exp_from: Exponent, exp_to: Exponent) -> ExactNum {
let one_bits = test_random::<usize>() % (p / 2).min(WORD_BIT_SIZE * 2) + 1;
let mut m1 = vec![0; p / WORD_BIT_SIZE];
let i = one_bits / WORD_BIT_SIZE;
let m1l = m1.len();
m1.iter_mut().take(i + 1).for_each(|v| *v = Word::MAX);
m1[i] >>= one_bits % WORD_BIT_SIZE;
m1.iter_mut().rev().take(i + 1).for_each(|v| *v = Word::MAX);
m1[m1l - i - 1] <<= one_bits % WORD_BIT_SIZE;
bf_from_mantissa_and_exp_rng(&m1, exp_from, exp_to)
}
pub fn get_periodic(p: usize, exp_from: Exponent, exp_to: Exponent) -> ExactNum {
let nbits = 4;
let bits = test_random::<Word>() % ((1 << nbits) - 1) + 1;
let mut w: Word = bits;
for _ in 1..WORD_BIT_SIZE / nbits {
w <<= nbits;
w |= bits;
}
let mut m1 = vec![w; p / WORD_BIT_SIZE];
*(m1.last_mut().unwrap()) |= 1 << (WORD_BIT_SIZE - 1);
bf_from_mantissa_and_exp_rng(&m1, exp_from, exp_to)
}
pub fn get_last_zero(p: usize, exp_from: Exponent, exp_to: Exponent) -> ExactNum {
let mut m1 = vec![Word::MAX; p / WORD_BIT_SIZE];
m1[0] = Word::MAX - 1;
bf_from_mantissa_and_exp_rng(&m1, exp_from, exp_to)
}
pub fn get_near_one(p: usize) -> ExactNum {
let e = (test_random::<u8>() & 1) as Exponent;
let random_bits = test_random::<usize>() % (p - 1);
let i = random_bits / WORD_BIT_SIZE;
let mut m1;
if e == 0 {
m1 = vec![Word::MAX; p / WORD_BIT_SIZE];
} else {
m1 = vec![0; p / WORD_BIT_SIZE];
}
m1[i] ^= test_random::<Word>() >> (random_bits % WORD_BIT_SIZE);
m1.iter_mut().take(i).for_each(|v| *v = test_random());
*m1.last_mut().unwrap() |= WORD_SIGNIFICANT_BIT;
bf_from_mantissa_and_exp_rng(&m1, e, e)
}
pub fn bf_from_mantissa_and_exp_rng(m: &[Word], exp_from: Exponent, exp_to: Exponent) -> ExactNum {
let e = if exp_from < exp_to {
(test_random::<isize>().abs() % (exp_to as isize - exp_from as isize) + exp_from as isize)
as Exponent
} else {
exp_from
};
let s = if test_random::<u8>() & 1 == 0 { Sign::Pos } else { Sign::Neg };
ExactNum::from_words(m, s, e)
}