use gmp_mpfr_sys::mpfr::{self, rnd_t};
use malachite_base::num::arithmetic::traits::PowerOf2;
use malachite_base::num::basic::integers::PrimitiveInt;
use malachite_base::num::basic::traits::NaN;
use malachite_base::num::conversion::traits::ExactFrom;
use malachite_base::num::logic::traits::LowMask;
use malachite_base::rounding_modes::RoundingMode::{self, *};
use malachite_float::test_util::common::parse_hex_string;
use malachite_float::{ComparableFloat, Float};
use malachite_nz::natural::Natural;
use malachite_nz::platform::Limb;
use malachite_q::Rational;
const fn mpfr_rnd(rm: RoundingMode) -> rnd_t {
match rm {
Floor => rnd_t::RNDD,
Ceiling => rnd_t::RNDU,
Down => rnd_t::RNDZ,
Up => rnd_t::RNDA,
Nearest => rnd_t::RNDN,
Exact => panic!(),
}
}
fn mpfr_can_round(x: &Float, err: i64, rnd1: RoundingMode, rnd2: RoundingMode, prec: u64) -> bool {
let b = rug::Float::exact_from(x);
unsafe {
mpfr::can_round(
b.as_raw(),
err,
mpfr_rnd(rnd1),
mpfr_rnd(rnd2),
i64::exact_from(prec),
) != 0
}
}
fn can_round_ground_truth(
x: &Float,
err: i64,
rnd1: RoundingMode,
rnd2: RoundingMode,
prec: u64,
) -> bool {
let exp = i64::from(x.get_exponent().unwrap());
let eps = Rational::power_of_2(exp - err);
let b = Rational::exact_from(x);
let neg = *x < 0;
let towards_zero = match rnd1 {
Down => true,
Up | Nearest => false,
Floor => !neg,
Ceiling => neg,
Exact => unreachable!(),
};
let (lo, hi) = if rnd1 == Nearest {
(&b - &eps, &b + &eps)
} else if towards_zero != neg {
(b.clone(), &b + &eps)
} else {
(&b - &eps, b.clone())
};
let lo = Float::from_rational_prec_round(lo, prec, rnd2).0;
let hi = Float::from_rational_prec_round(hi, prec, rnd2).0;
ComparableFloat(lo) == ComparableFloat(hi)
}
#[test]
fn test_can_round_vs_mpfr() {
let rms = [Floor, Ceiling, Down, Up, Nearest];
let mut mismatches = 0u64;
for prec_x in [10u64, 64, 65, 100] {
let mut sigs = vec![
Natural::power_of_2(prec_x - 1),
Natural::low_mask(prec_x),
Natural::power_of_2(prec_x - 1) + Natural::power_of_2(0u64),
];
for t in [1, 2, prec_x / 2, prec_x - 2] {
sigs.push(Natural::power_of_2(prec_x - 1) + Natural::power_of_2(t));
sigs.push(Natural::low_mask(prec_x) - Natural::power_of_2(t));
}
for sig in sigs {
for sign in [false, true] {
let x = Float::from_natural_prec(sig.clone(), prec_x).0;
let x = if sign { -x } else { x };
for prec in [1u64, 2, prec_x / 2, prec_x - 1, prec_x, prec_x + 1, prec_x + 10] {
for err in [
i64::exact_from(prec) - 1,
i64::exact_from(prec),
i64::exact_from(prec) + 1,
i64::exact_from(prec) + 2,
i64::exact_from(prec_x) - 1,
i64::exact_from(prec_x),
i64::exact_from(prec_x) + 1,
i64::exact_from(prec_x) + 10,
] {
for rnd1 in rms {
for rnd2 in rms {
let ours = x.can_round(err, rnd1, rnd2, prec);
let theirs = mpfr_can_round(&x, err, rnd1, rnd2, prec);
if ours != theirs {
assert!(
can_round_ground_truth(&x, err, rnd1, rnd2, prec),
"unsound true: {x} {err} {rnd1} {rnd2} {prec} (prec_x \
{prec_x}, ours {ours}, mpfr {theirs})"
);
mismatches += 1;
}
}
}
}
}
}
}
}
assert_eq!(mismatches, if Limb::WIDTH == u32::WIDTH { 24 } else { 16 });
}
#[test]
fn can_round_soundness() {
let rms = [Floor, Ceiling, Down, Up, Nearest];
for prec_x in [10u64, 64] {
let mut sigs = vec![Natural::power_of_2(prec_x - 1), Natural::low_mask(prec_x)];
for t in [1, prec_x / 2, prec_x - 2] {
sigs.push(Natural::power_of_2(prec_x - 1) + Natural::power_of_2(t));
}
for sig in sigs {
for sign in [false, true] {
let x = Float::from_natural_prec(sig.clone(), prec_x).0;
let x = if sign { -x } else { x };
for prec in [2u64, prec_x / 2, prec_x - 1] {
for err in [
i64::exact_from(prec) + 1,
i64::exact_from(prec) + 3,
i64::exact_from(prec_x),
] {
let exp = i64::from(x.get_exponent().unwrap());
let eps = Float::power_of_2(exp - err);
for rnd1 in rms {
for rnd2 in rms {
if !x.can_round(err, rnd1, rnd2, prec) {
continue;
}
let towards_zero = match rnd1 {
Down => true,
Up | Nearest => false,
Floor => !sign,
Ceiling => sign,
Exact => unreachable!(),
};
let mut candidates = vec![x.clone()];
let away = if sign { &x - &eps } else { &x + &eps };
let toward = if sign { &x + &eps } else { &x - &eps };
if rnd1 == Nearest {
candidates.push(away);
candidates.push(toward);
} else if towards_zero {
candidates.push(away);
} else {
candidates.push(toward);
}
let mut results = candidates
.into_iter()
.map(|c| Float::from_float_prec_round(c, prec, rnd2).0);
let first = results.next().unwrap();
for r in results {
assert_eq!(
malachite_float::ComparableFloat(r),
malachite_float::ComparableFloat(first.clone()),
"{x} {err} {rnd1} {rnd2} {prec}"
);
}
}
}
}
}
}
}
}
}
#[test]
fn can_round_special() {
assert!(!Float::NAN.can_round(100, Nearest, Nearest, 10));
assert!(!Float::from(f64::INFINITY).can_round(100, Nearest, Nearest, 10));
assert!(!Float::from(0u32).can_round(100, Nearest, Nearest, 10));
let x = Float::from(3u32);
assert!(!x.can_round(0, Nearest, Nearest, 10));
assert!(!x.can_round(-5, Nearest, Nearest, 10));
}
#[test]
#[should_panic]
fn can_round_fail_1() {
Float::from(3u32).can_round(100, Exact, Nearest, 10);
}
#[test]
#[should_panic]
fn can_round_fail_2() {
Float::from(3u32).can_round(100, Nearest, Nearest, 0);
}
#[test]
fn test_can_round() {
let test = |s, s_hex, err, rnd1: RoundingMode, rnd2: RoundingMode, prec, out: bool| {
let x = parse_hex_string(s_hex);
assert_eq!(x.to_string(), s);
assert_eq!(x.can_round(err, rnd1, rnd2, prec), out);
};
test("65535.0", "0xffff.0#16", 20, Down, Nearest, 8, true);
test("65535.0", "0xffff.0#16", 20, Down, Down, 8, true);
test("65535.0", "0xffff.0#16", 20, Up, Up, 8, true);
test("65535.0", "0xffff.0#16", 20, Nearest, Nearest, 8, true);
test("65535.0", "0xffff.0#16", 16, Down, Nearest, 8, true);
test("32896.0", "0x8080.0#16", 20, Down, Nearest, 8, false);
test("32896.0", "0x8080.0#16", 20, Nearest, Nearest, 8, false);
test("32896.0", "0x8080.0#16", 12, Down, Nearest, 8, false);
test("32768.0", "0x8000.0#16", 20, Down, Nearest, 8, true);
test("32768.0", "0x8000.0#16", 20, Up, Nearest, 8, true);
test("-65535.0", "-0xffff.0#16", 20, Down, Nearest, 8, true);
}