use dashu_float::round::mode::HalfAway as DashuHalfAway;
use dashu_float::{Context as DashuContext, FBig};
use oxinum_core::Sign;
use oxinum_float::native::{BigFloat, RoundingMode};
use proptest::prelude::*;
fn mid_at_p2() -> BigFloat {
let v = BigFloat::from_f64(1.5, 2).expect("1.5 fits in 2 bits");
assert_eq!(v.to_f64(), 1.5);
v
}
fn neg_mid_at_p2() -> BigFloat {
-mid_at_p2()
}
#[test]
fn round_mid_at_p1_half_even_to_two() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::HalfEven);
assert_eq!(v.to_f64(), 2.0);
}
#[test]
fn round_three_quarters_at_p1_half_even_to_one() {
let three_quarters = BigFloat::from_f64(0.75, 2).expect("0.75 fits");
let v = three_quarters.round_to_precision(1, RoundingMode::HalfEven);
assert_eq!(v.to_f64(), 1.0);
}
#[test]
fn round_three_at_p1_half_even_to_four() {
let three = BigFloat::from_i64(3, 2, RoundingMode::HalfEven);
let v = three.round_to_precision(1, RoundingMode::HalfEven);
assert_eq!(v.to_f64(), 4.0);
}
#[test]
fn round_five_at_p2_half_even_to_four() {
let five = BigFloat::from_i64(5, 3, RoundingMode::HalfEven);
let v = five.round_to_precision(2, RoundingMode::HalfEven);
assert_eq!(v.to_f64(), 4.0);
}
#[test]
fn round_seven_at_p2_half_even_to_eight() {
let seven = BigFloat::from_i64(7, 3, RoundingMode::HalfEven);
let v = seven.round_to_precision(2, RoundingMode::HalfEven);
assert_eq!(v.to_f64(), 8.0);
}
#[test]
fn round_mid_at_p1_half_away() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::HalfAway);
assert_eq!(v.to_f64(), 2.0);
}
#[test]
fn round_mid_at_p1_half_to_zero() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::HalfToZero);
assert_eq!(v.to_f64(), 1.0);
}
#[test]
fn round_mid_at_p1_to_zero() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::ToZero);
assert_eq!(v.to_f64(), 1.0);
}
#[test]
fn round_mid_at_p1_to_inf() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::ToInf);
assert_eq!(v.to_f64(), 2.0);
}
#[test]
fn round_mid_at_p1_to_neg_inf() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::ToNegInf);
assert_eq!(v.to_f64(), 1.0);
}
#[test]
fn round_neg_mid_at_p1_to_neg_inf() {
let v = neg_mid_at_p2().round_to_precision(1, RoundingMode::ToNegInf);
assert_eq!(v.to_f64(), -2.0);
}
#[test]
fn round_neg_mid_at_p1_to_inf() {
let v = neg_mid_at_p2().round_to_precision(1, RoundingMode::ToInf);
assert_eq!(v.to_f64(), -1.0);
}
#[test]
fn round_mid_at_p1_away_from_zero() {
let v = mid_at_p2().round_to_precision(1, RoundingMode::AwayFromZero);
assert_eq!(v.to_f64(), 2.0);
}
#[test]
fn round_neg_mid_at_p1_away_from_zero() {
let v = neg_mid_at_p2().round_to_precision(1, RoundingMode::AwayFromZero);
assert_eq!(v.to_f64(), -2.0);
}
#[test]
fn add_precision_propagation_takes_max() {
let a = BigFloat::from_i64(1, 10, RoundingMode::HalfEven);
let b = BigFloat::from_i64(2, 20, RoundingMode::HalfEven);
let s = &a + &b;
assert_eq!(s.precision(), 20);
assert_eq!(s.to_f64(), 3.0);
}
#[test]
fn add_precision_propagation_other_order() {
let a = BigFloat::from_i64(1, 50, RoundingMode::HalfEven);
let b = BigFloat::from_i64(2, 20, RoundingMode::HalfEven);
let s = &a + &b;
assert_eq!(s.precision(), 50);
}
#[test]
fn canonical_zero_after_a_plus_neg_a() {
let a = BigFloat::from_i64(42, 32, RoundingMode::HalfEven);
let neg_a = -&a;
let sum = &a + &neg_a;
assert!(sum.is_zero());
assert_eq!(sum.sign(), Sign::Positive);
}
#[test]
fn canonical_zero_at_different_precisions_compares_equal() {
let z10 = BigFloat::zero(10);
let z50 = BigFloat::zero(50);
assert_eq!(z10, z50);
}
#[test]
fn zero_neg_is_zero() {
let z = BigFloat::zero(32);
let nz = -&z;
assert!(nz.is_zero());
assert_eq!(nz.sign(), Sign::Positive);
}
#[test]
fn a_plus_zero_equals_a() {
let a = BigFloat::from_i64(123, 32, RoundingMode::HalfEven);
let z = BigFloat::zero(32);
assert_eq!(&a + &z, a);
assert_eq!(&z + &a, a);
}
#[test]
fn from_f64_half_roundtrip() {
let v = BigFloat::from_f64(0.5, 53).expect("0.5 finite");
assert_eq!(v.to_f64(), 0.5);
}
#[test]
fn from_f64_point_one_at_p53_matches_double() {
let x = 0.1_f64;
let v = BigFloat::from_f64(x, 53).expect("0.1 finite");
assert_eq!(v.to_f64(), x);
}
#[test]
fn from_f64_one_thousand_random_roundtrips() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config {
cases: 1000,
..Config::default()
});
runner
.run(&proptest::num::f64::ANY, |x| {
if x.is_finite() && x != 0.0 {
let v = BigFloat::from_f64(x, 53).expect("finite non-zero is convertible");
prop_assert_eq!(v.to_f64(), x);
}
Ok(())
})
.expect("round trip");
}
#[test]
fn from_f64_nan_errors() {
assert!(BigFloat::from_f64(f64::NAN, 53).is_err());
}
#[test]
fn from_f64_inf_errors() {
assert!(BigFloat::from_f64(f64::INFINITY, 53).is_err());
assert!(BigFloat::from_f64(f64::NEG_INFINITY, 53).is_err());
}
#[test]
fn from_f64_subnormal_roundtrip() {
let smallest = f64::from_bits(1); let v = BigFloat::from_f64(smallest, 53).expect("subnormal finite");
assert_eq!(v.to_f64(), smallest);
}
#[test]
fn to_f64_overflow_saturates() {
let huge_exp: i64 = 2000;
let v = BigFloat::from_parts(
Sign::Positive,
oxinum_int::native::BigUint::one(),
huge_exp,
53,
RoundingMode::HalfEven,
);
assert!(v.to_f64().is_infinite());
}
#[test]
fn to_f64_underflow_to_zero() {
let tiny = BigFloat::from_parts(
Sign::Positive,
oxinum_int::native::BigUint::one(),
-3000,
53,
RoundingMode::HalfEven,
);
assert_eq!(tiny.to_f64(), 0.0);
}
#[test]
fn ord_negative_less_than_positive() {
let n = BigFloat::from_i64(-1, 8, RoundingMode::HalfEven);
let p = BigFloat::from_i64(1, 8, RoundingMode::HalfEven);
assert!(n < p);
}
#[test]
fn ord_same_sign_by_magnitude() {
let a = BigFloat::from_i64(3, 8, RoundingMode::HalfEven);
let b = BigFloat::from_i64(5, 8, RoundingMode::HalfEven);
assert!(a < b);
let na = -&a;
let nb = -&b;
assert!(nb < na);
}
#[test]
fn display_zero_is_short() {
assert_eq!(format!("{}", BigFloat::zero(32)), "0xb0p0");
}
#[test]
fn display_round_trip_two() {
let two = BigFloat::from_i64(2, 4, RoundingMode::HalfEven);
let s = format!("{two}");
assert!(s.starts_with("0xb"), "got {s}");
assert!(s.contains("p"), "got {s}");
}
#[test]
fn display_negative_carries_sign() {
let neg_one = BigFloat::from_i64(-1, 8, RoundingMode::HalfEven);
let s = format!("{neg_one}");
assert!(s.starts_with("-0xb"), "got {s}");
}
type DashuBin = FBig<DashuHalfAway, 2>;
#[test]
fn cross_val_dashu_binary_smoke() {
let a: DashuBin = DashuBin::try_from(1.0_f64).expect("finite");
let b: DashuBin = DashuBin::try_from(2.0_f64).expect("finite");
let s = &a + &b;
let s_f = s.to_f64().value();
assert_eq!(s_f, 3.0);
let ax = BigFloat::from_f64(1.0, 53).expect("finite");
let bx = BigFloat::from_f64(2.0, 53).expect("finite");
let sx = &ax + &bx;
assert_eq!(sx.to_f64(), s_f);
let _ctx = DashuContext::<DashuHalfAway>::new(53);
}
#[test]
fn cross_val_add_simple() {
let a = BigFloat::from_f64(1.25, 53).expect("finite");
let b = BigFloat::from_f64(2.5, 53).expect("finite");
let sum = &a + &b;
assert_eq!(sum.to_f64(), 1.25 + 2.5);
}
#[test]
fn cross_val_sub_simple() {
let a = BigFloat::from_f64(7.0, 53).expect("finite");
let b = BigFloat::from_f64(3.5, 53).expect("finite");
let diff = &a - &b;
assert_eq!(diff.to_f64(), 7.0 - 3.5);
}
#[test]
fn cross_val_add_200_random_pairs() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config {
cases: 200,
..Config::default()
});
runner
.run(
&(proptest::num::f64::NORMAL, proptest::num::f64::NORMAL),
|(x, y)| {
if !x.is_finite() || !y.is_finite() || x == 0.0 || y == 0.0 {
return Ok(());
}
let a = BigFloat::from_f64(x, 53).expect("finite");
let b = BigFloat::from_f64(y, 53).expect("finite");
let sum = &a + &b;
let sum_f = sum.to_f64();
let expected = x + y;
prop_assert_eq!(sum_f, expected, "for {} + {}", x, y);
Ok(())
},
)
.expect("cross-val add");
}
#[test]
fn cross_val_sub_200_random_pairs() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config {
cases: 200,
..Config::default()
});
runner
.run(
&(proptest::num::f64::NORMAL, proptest::num::f64::NORMAL),
|(x, y)| {
if !x.is_finite() || !y.is_finite() || x == 0.0 || y == 0.0 {
return Ok(());
}
let a = BigFloat::from_f64(x, 53).expect("finite");
let b = BigFloat::from_f64(y, 53).expect("finite");
let diff = &a - &b;
let diff_f = diff.to_f64();
let expected = x - y;
prop_assert_eq!(diff_f, expected, "for {} - {}", x, y);
Ok(())
},
)
.expect("cross-val sub");
}
#[test]
fn cross_val_display_vs_dashu_decimal_zero() {
let v = BigFloat::zero(32);
assert_eq!(format!("{v}"), "0xb0p0");
}
proptest! {
#[test]
fn proptest_add_commutative(x in proptest::num::f64::NORMAL, y in proptest::num::f64::NORMAL) {
prop_assume!(x.is_finite() && y.is_finite() && x != 0.0 && y != 0.0);
let a = BigFloat::from_f64(x, 50).expect("finite");
let b = BigFloat::from_f64(y, 50).expect("finite");
prop_assert_eq!(&a + &b, &b + &a);
}
#[test]
fn proptest_add_assoc_modulo_lsb(
x in proptest::num::f64::NORMAL,
y in proptest::num::f64::NORMAL,
z in proptest::num::f64::NORMAL,
) {
prop_assume!(x.is_finite() && y.is_finite() && z.is_finite());
prop_assume!(x != 0.0 && y != 0.0 && z != 0.0);
prop_assume!(x.abs().log2() - y.abs().log2() < 900.0);
prop_assume!(y.abs().log2() - z.abs().log2() < 900.0);
let a = BigFloat::from_f64(x, 60).expect("finite");
let b = BigFloat::from_f64(y, 60).expect("finite");
let c = BigFloat::from_f64(z, 60).expect("finite");
let left = (&a + &b) + &c;
let right = &a + (&b + &c);
let lf = left.to_f64();
let rf = right.to_f64();
if lf.is_finite() && rf.is_finite() && lf != 0.0 && rf != 0.0 {
let rel = (lf - rf).abs() / lf.abs().max(rf.abs());
prop_assert!(rel < 1e-12, "assoc rel diff {rel}");
}
}
#[test]
fn proptest_a_plus_zero_is_a(x in proptest::num::f64::NORMAL) {
prop_assume!(x.is_finite() && x != 0.0);
let a = BigFloat::from_f64(x, 50).expect("finite");
let z = BigFloat::zero(50);
prop_assert_eq!(&a + &z, a.clone());
}
#[test]
fn proptest_a_minus_a_is_zero(x in proptest::num::f64::NORMAL) {
prop_assume!(x.is_finite() && x != 0.0);
let a = BigFloat::from_f64(x, 50).expect("finite");
let zero = &a - &a;
prop_assert!(zero.is_zero());
prop_assert_eq!(zero.sign(), Sign::Positive);
}
#[test]
fn proptest_neg_neg_is_identity(x in proptest::num::f64::NORMAL) {
prop_assume!(x.is_finite() && x != 0.0);
let a = BigFloat::from_f64(x, 50).expect("finite");
prop_assert_eq!(-(-&a), a);
}
}
use oxinum_core::OxiNumError;
#[test]
fn mul_identity_one_times_a() {
let one = BigFloat::from_i64(1, 32, RoundingMode::HalfEven);
let a = BigFloat::from_i64(42, 32, RoundingMode::HalfEven);
assert_eq!(&one * &a, a);
assert_eq!(&a * &one, a);
}
#[test]
fn mul_zero_times_a_is_zero() {
let z = BigFloat::zero(32);
let a = BigFloat::from_i64(123, 32, RoundingMode::HalfEven);
let p = &z * &a;
assert!(p.is_zero());
let p2 = &a * &z;
assert!(p2.is_zero());
}
#[test]
fn mul_two_times_three_is_six() {
let two = BigFloat::from_i64(2, 16, RoundingMode::HalfEven);
let three = BigFloat::from_i64(3, 16, RoundingMode::HalfEven);
let p = &two * &three;
assert_eq!(p.to_f64(), 6.0);
}
#[test]
fn mul_one_and_a_half_times_two_is_three() {
let a = BigFloat::from_f64(1.5, 4).expect("1.5 fits in 4 bits");
let b = BigFloat::from_i64(2, 4, RoundingMode::HalfEven);
let p = &a * &b;
assert_eq!(p.to_f64(), 3.0);
}
#[test]
fn mul_neg_two_times_three_is_neg_six() {
let a = BigFloat::from_i64(-2, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(3, 16, RoundingMode::HalfEven);
let p = &a * &b;
assert_eq!(p.to_f64(), -6.0);
}
#[test]
fn mul_two_negatives_is_positive() {
let a = BigFloat::from_i64(-2, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(-3, 16, RoundingMode::HalfEven);
let p = &a * &b;
assert_eq!(p.to_f64(), 6.0);
}
#[test]
fn mul_precision_propagation_takes_max() {
let a = BigFloat::from_i64(2, 10, RoundingMode::HalfEven);
let b = BigFloat::from_i64(3, 50, RoundingMode::HalfEven);
let p = &a * &b;
assert_eq!(p.precision(), 50);
assert_eq!(p.to_f64(), 6.0);
}
#[test]
fn mul_assign_works() {
let mut a = BigFloat::from_i64(5, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(7, 16, RoundingMode::HalfEven);
a *= &b;
assert_eq!(a.to_f64(), 35.0);
}
proptest! {
#[test]
fn proptest_mul_commutative(
x in proptest::num::f64::NORMAL,
y in proptest::num::f64::NORMAL,
) {
prop_assume!(x.is_finite() && y.is_finite() && x != 0.0 && y != 0.0);
let a = BigFloat::from_f64(x, 50).expect("finite");
let b = BigFloat::from_f64(y, 50).expect("finite");
prop_assert_eq!(&a * &b, &b * &a);
}
#[test]
fn proptest_mul_assoc_modulo_lsb(
x in proptest::num::f64::NORMAL,
y in proptest::num::f64::NORMAL,
z in proptest::num::f64::NORMAL,
) {
prop_assume!(x.is_finite() && y.is_finite() && z.is_finite());
prop_assume!(x != 0.0 && y != 0.0 && z != 0.0);
prop_assume!(x.abs().log2() + y.abs().log2() + z.abs().log2() < 900.0);
prop_assume!(x.abs().log2() + y.abs().log2() + z.abs().log2() > -900.0);
let a = BigFloat::from_f64(x, 60).expect("finite");
let b = BigFloat::from_f64(y, 60).expect("finite");
let c = BigFloat::from_f64(z, 60).expect("finite");
let left = (&a * &b) * &c;
let right = &a * (&b * &c);
let lf = left.to_f64();
let rf = right.to_f64();
if lf.is_finite() && rf.is_finite() && lf != 0.0 && rf != 0.0 {
let rel = (lf - rf).abs() / lf.abs().max(rf.abs());
prop_assert!(rel < 1e-12, "mul assoc rel diff {rel}");
}
}
}
#[test]
fn cross_val_mul_200_random_pairs() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config {
cases: 200,
..Config::default()
});
runner
.run(
&(proptest::num::f64::NORMAL, proptest::num::f64::NORMAL),
|(x, y)| {
if !x.is_finite() || !y.is_finite() || x == 0.0 || y == 0.0 {
return Ok(());
}
let expected = x * y;
if !expected.is_finite() || expected == 0.0 {
return Ok(());
}
let a = BigFloat::from_f64(x, 53).expect("finite");
let b = BigFloat::from_f64(y, 53).expect("finite");
let p = &a * &b;
let pf = p.to_f64();
prop_assert_eq!(pf, expected, "for {} * {}", x, y);
Ok(())
},
)
.expect("cross-val mul");
}
#[test]
fn div_six_by_two_is_three() {
let a = BigFloat::from_i64(6, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(2, 16, RoundingMode::HalfEven);
let q = a.div_ref(&b).expect("nonzero");
assert_eq!(q.to_f64(), 3.0);
}
#[test]
fn div_one_by_two_is_half() {
let a = BigFloat::from_i64(1, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(2, 16, RoundingMode::HalfEven);
let q = a.div_ref(&b).expect("nonzero");
assert_eq!(q.to_f64(), 0.5);
}
#[test]
fn div_neg_six_by_two_is_neg_three() {
let a = BigFloat::from_i64(-6, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(2, 16, RoundingMode::HalfEven);
let q = a.div_ref(&b).expect("nonzero");
assert_eq!(q.to_f64(), -3.0);
}
#[test]
fn div_two_negatives_is_positive() {
let a = BigFloat::from_i64(-6, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(-2, 16, RoundingMode::HalfEven);
let q = a.div_ref(&b).expect("nonzero");
assert_eq!(q.to_f64(), 3.0);
}
#[test]
fn div_zero_by_nonzero_is_zero() {
let z = BigFloat::zero(32);
let b = BigFloat::from_i64(7, 32, RoundingMode::HalfEven);
let q = z.div_ref(&b).expect("nonzero divisor");
assert!(q.is_zero());
assert_eq!(q.precision(), 32);
}
#[test]
fn div_by_zero_returns_error() {
let a = BigFloat::from_i64(5, 16, RoundingMode::HalfEven);
let z = BigFloat::zero(16);
let r = a.div_ref(&z);
assert!(matches!(r, Err(OxiNumError::DivByZero)));
}
#[test]
fn div_operator_produces_inf_on_zero() {
let a = BigFloat::from_i64(5, 16, RoundingMode::HalfEven);
let z = BigFloat::zero(16);
let result = &a / &z;
assert!(result.is_infinite(), "5 / 0 should be +Inf per IEEE 754");
assert!(result.is_sign_positive(), "5 / 0 should be positive Inf");
}
#[test]
fn div_assign_works() {
let mut a = BigFloat::from_i64(20, 16, RoundingMode::HalfEven);
let b = BigFloat::from_i64(4, 16, RoundingMode::HalfEven);
a /= &b;
assert_eq!(a.to_f64(), 5.0);
}
#[test]
fn div_precision_propagation_takes_max() {
let a = BigFloat::from_i64(6, 10, RoundingMode::HalfEven);
let b = BigFloat::from_i64(2, 50, RoundingMode::HalfEven);
let q = a.div_ref(&b).expect("nonzero");
assert_eq!(q.precision(), 50);
assert_eq!(q.to_f64(), 3.0);
}
proptest! {
#[test]
fn proptest_a_div_a_is_one(x in proptest::num::f64::NORMAL) {
prop_assume!(x.is_finite() && x != 0.0);
let a = BigFloat::from_f64(x, 60).expect("finite");
let q = a.div_ref(&a).expect("nonzero");
prop_assert_eq!(q.to_f64(), 1.0);
}
#[test]
fn proptest_div_then_mul_roundtrip(
x in proptest::num::f64::NORMAL,
y in proptest::num::f64::NORMAL,
) {
prop_assume!(x.is_finite() && y.is_finite() && x != 0.0 && y != 0.0);
prop_assume!((x / y).is_finite() && x / y != 0.0);
prop_assume!(((x / y) * y).is_finite());
let a = BigFloat::from_f64(x, 60).expect("finite");
let b = BigFloat::from_f64(y, 60).expect("finite");
let q = a.div_ref(&b).expect("nonzero");
let back = &q * &b;
let af = a.to_f64();
let bf = back.to_f64();
if af.is_finite() && bf.is_finite() && af != 0.0 && bf != 0.0 {
let rel = (af - bf).abs() / af.abs().max(bf.abs());
prop_assert!(rel < 1e-14, "div-then-mul rel diff {rel} for {}/{}", x, y);
}
}
}
#[test]
fn cross_val_div_100_random_pairs() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config {
cases: 100,
..Config::default()
});
runner
.run(
&(proptest::num::f64::NORMAL, proptest::num::f64::NORMAL),
|(x, y)| {
if !x.is_finite() || !y.is_finite() || x == 0.0 || y == 0.0 {
return Ok(());
}
let expected = x / y;
if !expected.is_finite() || expected == 0.0 {
return Ok(());
}
let a = BigFloat::from_f64(x, 53).expect("finite");
let b = BigFloat::from_f64(y, 53).expect("finite");
let q = a.div_ref(&b).expect("nonzero divisor");
let qf = q.to_f64();
prop_assert_eq!(qf, expected, "for {} / {}", x, y);
Ok(())
},
)
.expect("cross-val div");
}
#[test]
fn sqrt_zero_is_zero() {
let z = BigFloat::zero(32);
let s = z.sqrt(32, RoundingMode::HalfEven).expect("real");
assert!(s.is_zero());
}
#[test]
fn sqrt_one_is_one() {
let one = BigFloat::from_i64(1, 32, RoundingMode::HalfEven);
let s = one.sqrt(32, RoundingMode::HalfEven).expect("real");
assert_eq!(s.to_f64(), 1.0);
}
#[test]
fn sqrt_four_is_two() {
let four = BigFloat::from_i64(4, 32, RoundingMode::HalfEven);
let s = four.sqrt(32, RoundingMode::HalfEven).expect("real");
assert_eq!(s.to_f64(), 2.0);
}
#[test]
fn sqrt_nine_is_three() {
let nine = BigFloat::from_i64(9, 32, RoundingMode::HalfEven);
let s = nine.sqrt(32, RoundingMode::HalfEven).expect("real");
assert_eq!(s.to_f64(), 3.0);
}
#[test]
fn sqrt_negative_is_domain_error() {
let neg_one = BigFloat::from_i64(-1, 16, RoundingMode::HalfEven);
let r = neg_one.sqrt(16, RoundingMode::HalfEven);
assert!(matches!(r, Err(OxiNumError::Domain(_))));
}
#[test]
fn sqrt_two_squared_is_two_at_high_precision() {
let two = BigFloat::from_i64(2, 200, RoundingMode::HalfEven);
let s = two.sqrt(200, RoundingMode::HalfEven).expect("real");
let sf = s.to_f64();
assert!((sf - 2.0_f64.sqrt()).abs() < 1e-15, "got {sf}");
let s2 = &s * &s;
let s2f = s2.to_f64();
assert!((s2f - 2.0).abs() < 1e-14, "sqrt(2)^2 = {s2f}");
}
#[test]
fn sqrt_half_is_known_value() {
let half = BigFloat::from_f64(0.5, 53).expect("0.5 finite");
let s = half.sqrt(53, RoundingMode::HalfEven).expect("real");
let sf = s.to_f64();
assert!((sf - (0.5_f64).sqrt()).abs() < 1e-15, "got {sf}");
}
#[test]
fn sqrt_large_value() {
use oxinum_int::native::BigUint;
let two_pow_100 = BigFloat::from_parts(
Sign::Positive,
BigUint::one(),
100,
53,
RoundingMode::HalfEven,
);
let s = two_pow_100.sqrt(53, RoundingMode::HalfEven).expect("real");
let expected = BigFloat::from_parts(
Sign::Positive,
BigUint::one(),
50,
53,
RoundingMode::HalfEven,
);
assert_eq!(s, expected);
}
#[test]
fn sqrt_odd_exponent_value() {
let two = BigFloat::from_i64(2, 53, RoundingMode::HalfEven);
let s = two.sqrt(53, RoundingMode::HalfEven).expect("real");
let sf = s.to_f64();
assert!((sf - 2.0_f64.sqrt()).abs() < 1e-15, "got {sf}");
}
#[test]
fn cross_val_sqrt_100_random_non_negative() {
use proptest::test_runner::{Config, TestRunner};
let mut runner = TestRunner::new(Config {
cases: 100,
..Config::default()
});
runner
.run(&proptest::num::f64::NORMAL, |x| {
if !x.is_finite() || x <= 0.0 {
return Ok(());
}
let a = BigFloat::from_f64(x, 53).expect("finite");
let s = a.sqrt(53, RoundingMode::HalfEven).expect("non-negative");
let sf = s.to_f64();
let expected = x.sqrt();
if expected != 0.0 {
let rel = (sf - expected).abs() / expected.abs();
prop_assert!(rel < 1e-14, "sqrt mismatch for {x}: {sf} vs {expected}");
}
Ok(())
})
.expect("cross-val sqrt");
}
#[cfg(feature = "serde")]
mod serde_tests {
use oxinum_float::native::{BigFloat, RoundingMode};
use serde_json::Value;
fn roundtrip(x: &BigFloat) {
let json = serde_json::to_string(x).expect("serialize");
let back: BigFloat = serde_json::from_str(&json).expect("deserialize");
assert_eq!(*x, back, "round-trip failed for {x:?}");
assert_eq!(x.precision(), back.precision(), "precision lost for {x:?}");
}
#[test]
fn serde_bigfloat_zero() {
roundtrip(&BigFloat::zero(53));
roundtrip(&BigFloat::zero(1));
roundtrip(&BigFloat::zero(256));
}
#[test]
fn serde_bigfloat_one() {
let x = BigFloat::from_i64(1, 53, RoundingMode::HalfEven);
roundtrip(&x);
}
#[test]
fn serde_bigfloat_negative() {
let x = BigFloat::from_i64(-12345, 64, RoundingMode::HalfEven);
roundtrip(&x);
}
#[test]
fn serde_bigfloat_many_from_i64() {
for i in -50i64..=50 {
if i == 0 {
continue;
}
let x = BigFloat::from_i64(i, 64, RoundingMode::HalfEven);
roundtrip(&x);
}
let max = BigFloat::from_i64(i64::MAX, 64, RoundingMode::HalfEven);
roundtrip(&max);
let min = BigFloat::from_i64(i64::MIN, 64, RoundingMode::HalfEven);
roundtrip(&min);
}
#[test]
fn serde_bigfloat_high_precision_pi() {
use oxinum_float::native::pi;
let pi_500 = pi(500).expect("pi");
roundtrip(&pi_500);
}
#[test]
fn serde_bigfloat_precision_zero_rejected() {
let valid = BigFloat::from_i64(1, 53, RoundingMode::HalfEven);
let mut val: Value = serde_json::to_value(&valid).expect("serialize");
val["precision"] = Value::Number(0.into());
let result: Result<BigFloat, _> = serde_json::from_value(val);
assert!(result.is_err(), "precision=0 should fail deserialization");
}
#[test]
fn serde_bigfloat_bit_length_mismatch_rejected() {
let valid = BigFloat::from_i64(1, 53, RoundingMode::HalfEven);
let mut val: Value = serde_json::to_value(&valid).expect("serialize");
val["precision"] = Value::Number(64.into());
let result: Result<BigFloat, _> = serde_json::from_value(val);
assert!(
result.is_err(),
"mantissa bit_length != precision must fail deserialization"
);
}
#[test]
fn serde_bigfloat_zero_canonical_sign_forced() {
let valid = BigFloat::from_i64(5, 53, RoundingMode::HalfEven);
let mut val: Value = serde_json::to_value(&valid).expect("serialize");
val["mantissa"]["limbs"] = Value::Array(vec![]);
let back: BigFloat = serde_json::from_value(val).expect("deserialize zero variant");
assert!(back.is_zero(), "deserialized value should be zero");
assert_eq!(back.sign(), oxinum_core::Sign::Positive);
assert_eq!(back.exponent(), 0);
}
}
#[cfg(test)]
mod bigfloat_to_bigint {
use oxinum_float::native::{BigFloat, RoundingMode};
use oxinum_int::native::BigInt;
#[test]
fn positive_trunc_floor_ceil_round() {
let x = BigFloat::from_f64(3.7, 64).expect("3.7");
assert_eq!(x.to_bigint_trunc(), BigInt::from(3i64), "trunc(3.7)");
assert_eq!(x.to_bigint_floor(), BigInt::from(3i64), "floor(3.7)");
assert_eq!(x.to_bigint_ceil(), BigInt::from(4i64), "ceil(3.7)");
assert_eq!(x.to_bigint_round(), BigInt::from(4i64), "round(3.7)");
}
#[test]
fn negative_trunc_floor_ceil_round() {
let y = BigFloat::from_f64(-3.7, 64).expect("-3.7");
assert_eq!(y.to_bigint_trunc(), BigInt::from(-3i64), "trunc(-3.7)");
assert_eq!(y.to_bigint_floor(), BigInt::from(-4i64), "floor(-3.7)");
assert_eq!(y.to_bigint_ceil(), BigInt::from(-3i64), "ceil(-3.7)");
assert_eq!(y.to_bigint_round(), BigInt::from(-4i64), "round(-3.7)");
}
#[test]
fn exact_integer_unchanged() {
let z = BigFloat::from_i64(42, 64, RoundingMode::HalfEven);
let expected = BigInt::from(42i64);
assert_eq!(z.to_bigint_trunc(), expected, "trunc(42)");
assert_eq!(z.to_bigint_floor(), expected, "floor(42)");
assert_eq!(z.to_bigint_ceil(), expected, "ceil(42)");
assert_eq!(z.to_bigint_round(), expected, "round(42)");
}
#[test]
fn negative_exact_integer_unchanged() {
let z = BigFloat::from_i64(-7, 64, RoundingMode::HalfEven);
let expected = BigInt::from(-7i64);
assert_eq!(z.to_bigint_trunc(), expected, "trunc(-7)");
assert_eq!(z.to_bigint_floor(), expected, "floor(-7)");
assert_eq!(z.to_bigint_ceil(), expected, "ceil(-7)");
assert_eq!(z.to_bigint_round(), expected, "round(-7)");
}
#[test]
fn zero_is_zero() {
let z = BigFloat::zero(64);
assert_eq!(z.to_bigint_trunc(), BigInt::zero(), "trunc(0)");
assert_eq!(z.to_bigint_floor(), BigInt::zero(), "floor(0)");
assert_eq!(z.to_bigint_ceil(), BigInt::zero(), "ceil(0)");
assert_eq!(z.to_bigint_round(), BigInt::zero(), "round(0)");
}
#[test]
fn half_positive_rounds_away() {
let h = BigFloat::from_f64(2.5, 64).expect("2.5");
assert_eq!(h.to_bigint_trunc(), BigInt::from(2i64), "trunc(2.5)");
assert_eq!(h.to_bigint_floor(), BigInt::from(2i64), "floor(2.5)");
assert_eq!(h.to_bigint_ceil(), BigInt::from(3i64), "ceil(2.5)");
assert_eq!(h.to_bigint_round(), BigInt::from(3i64), "round(2.5)");
}
#[test]
fn half_negative_rounds_away() {
let h = BigFloat::from_f64(-2.5, 64).expect("-2.5");
assert_eq!(h.to_bigint_trunc(), BigInt::from(-2i64), "trunc(-2.5)");
assert_eq!(h.to_bigint_floor(), BigInt::from(-3i64), "floor(-2.5)");
assert_eq!(h.to_bigint_ceil(), BigInt::from(-2i64), "ceil(-2.5)");
assert_eq!(h.to_bigint_round(), BigInt::from(-3i64), "round(-2.5)");
}
#[test]
fn small_fraction_truncated() {
let x = BigFloat::from_f64(0.3, 64).expect("0.3");
assert_eq!(x.to_bigint_trunc(), BigInt::zero(), "trunc(0.3)");
assert_eq!(x.to_bigint_floor(), BigInt::zero(), "floor(0.3)");
assert_eq!(x.to_bigint_ceil(), BigInt::one(), "ceil(0.3)");
assert_eq!(x.to_bigint_round(), BigInt::zero(), "round(0.3)");
}
#[test]
fn negative_small_fraction() {
let x = BigFloat::from_f64(-0.3, 64).expect("-0.3");
assert_eq!(x.to_bigint_trunc(), BigInt::zero(), "trunc(-0.3)");
assert_eq!(x.to_bigint_floor(), BigInt::from(-1i64), "floor(-0.3)");
assert_eq!(x.to_bigint_ceil(), BigInt::zero(), "ceil(-0.3)");
assert_eq!(x.to_bigint_round(), BigInt::zero(), "round(-0.3)");
}
}