use core::str::FromStr;
use dashu_base::{Abs, ParseError, Sign};
use dashu_float::{round::mode, Context, DBig, FBig, Repr};
mod helper_macros;
type F = FBig;
fn r2(significand: i32, exponent: isize) -> Repr<2> {
Repr::new(significand.into(), exponent)
}
fn assert_neg_zero(v: &FBig) {
assert!(v.repr().is_neg_zero(), "expected -0, got {:?}", v.repr());
}
fn assert_pos_zero(v: &FBig) {
assert!(v.repr().is_pos_zero(), "expected +0, got {:?}", v.repr());
}
#[test]
fn test_f64_round_trip() {
let negz: F = FBig::try_from(-0.0f64).unwrap();
assert_neg_zero(&negz);
let posz: F = FBig::try_from(0.0f64).unwrap();
assert_pos_zero(&posz);
assert!(negz.to_f64().value().is_sign_negative());
assert!(!posz.to_f64().value().is_sign_negative());
}
#[test]
fn test_equality_and_order() {
let negz: F = FBig::try_from(-0.0f64).unwrap();
let posz: F = FBig::try_from(0.0f64).unwrap();
assert_eq!(negz, posz); assert!(negz >= posz); assert!(negz <= posz);
assert_eq!(Repr::<2>::neg_zero(), Repr::<2>::zero());
}
#[test]
fn test_neg_and_abs() {
let negz: F = FBig::try_from(-0.0f64).unwrap();
let posz: F = FBig::try_from(0.0f64).unwrap();
assert_pos_zero(&-negz.clone()); assert_neg_zero(&-posz.clone()); assert_pos_zero(&negz.abs()); assert_pos_zero(&posz.abs());
}
#[test]
fn test_signum() {
let negz: F = FBig::try_from(-0.0f64).unwrap();
let posz: F = FBig::try_from(0.0f64).unwrap();
assert_pos_zero(&negz.signum());
assert_pos_zero(&posz.signum());
assert_eq!(negz.sign(), Sign::Negative);
assert_eq!(posz.sign(), Sign::Positive);
}
#[test]
fn test_mul_signed_zero() {
let f = |x: f64| -> F { FBig::try_from(x).unwrap() };
let r = f(-0.0) * f(5.0);
assert_neg_zero(&r);
let r = f(-0.0) * f(-5.0);
assert_pos_zero(&r);
let r = f(0.0) * f(5.0);
assert_pos_zero(&r);
let r = f(0.0) * f(-5.0);
assert_neg_zero(&r);
}
#[test]
fn test_div_signed_zero() {
let ctx = Context::<mode::HalfEven>::new(53);
let negz = ctx
.div::<2>(&Repr::<2>::neg_zero(), &r2(5, 0))
.unwrap()
.value();
assert!(negz.repr().is_neg_zero()); let posz = ctx.div::<2>(&Repr::<2>::zero(), &r2(5, 0)).unwrap().value();
assert!(posz.repr().is_pos_zero()); }
#[test]
fn test_sqrt_signed_zero() {
let ctx = Context::<mode::HalfEven>::new(53);
let negz = ctx.sqrt::<2>(&Repr::<2>::neg_zero()).unwrap().value();
assert!(negz.repr().is_neg_zero()); let posz = ctx.sqrt::<2>(&Repr::<2>::zero()).unwrap().value();
assert!(posz.repr().is_pos_zero()); }
#[test]
fn test_trig_signed_zero() {
let ctx = Context::<mode::HalfEven>::new(53);
let sin_neg0 = ctx.sin::<2>(&Repr::<2>::neg_zero(), None).unwrap().value();
assert!(sin_neg0.repr().is_neg_zero()); let sin_pos0 = ctx.sin::<2>(&Repr::<2>::zero(), None).unwrap().value();
assert!(sin_pos0.repr().is_pos_zero()); let tan_neg0 = ctx.tan::<2>(&Repr::<2>::neg_zero(), None).unwrap().value();
assert!(tan_neg0.repr().is_neg_zero()); let cos_neg0 = ctx.cos::<2>(&Repr::<2>::neg_zero(), None).unwrap().value();
assert_eq!(cos_neg0, FBig::<mode::HalfEven>::ONE); }
#[test]
fn test_rounding_ops_signed_zero() -> Result<(), ParseError> {
let half_neg = DBig::from_str("-0.5")?;
assert!(half_neg.trunc().repr().is_neg_zero(), "trunc(-0.5) = -0");
let third_neg = DBig::from_str("-0.3")?;
assert!(third_neg.round().repr().is_neg_zero(), "round(-0.3) = -0");
let neg_zero_d = -DBig::ZERO;
assert!(neg_zero_d.repr().is_neg_zero());
assert!(neg_zero_d.ceil().repr().is_neg_zero(), "ceil(-0) = -0");
assert!(neg_zero_d.floor().repr().is_neg_zero(), "floor(-0) = -0");
assert!(neg_zero_d.trunc().repr().is_neg_zero(), "trunc(-0) = -0");
let neg_five = DBig::from_str("-5")?;
assert!(neg_five.fract().repr().is_neg_zero(), "fract(-5) = -0");
Ok(())
}
#[test]
fn test_cancellation_under_down() -> Result<(), ParseError> {
let three = DBig::from_str("3")?;
let neg_three = DBig::from_str("-3")?;
let down = Context::<mode::Down>::new(10);
let sum_down = down
.add::<10>(three.repr(), neg_three.repr())
.unwrap()
.value();
assert!(sum_down.repr().is_neg_zero(), "(-3)+3 under Down = -0");
let up = Context::<mode::Up>::new(10);
let sum_up = up
.add::<10>(three.repr(), neg_three.repr())
.unwrap()
.value();
assert!(sum_up.repr().is_pos_zero(), "(-3)+3 under Up = +0");
let sub_down = down.sub::<10>(three.repr(), three.repr()).unwrap().value();
assert!(sub_down.repr().is_neg_zero(), "3-3 under Down = -0");
let a = FBig::<mode::Down, 10>::from_str("3")?;
let a2 = FBig::<mode::Down, 10>::from_str("3")?;
let diff = a - a2;
assert!(diff.repr().is_neg_zero(), "FBig 3-3 under Down = -0");
Ok(())
}
#[test]
fn test_powi_signed_zero() {
let ctx = Context::<mode::HalfEven>::new(53);
let negz = ctx
.powi::<2>(&Repr::<2>::neg_zero(), 3.into())
.unwrap()
.value();
assert!(negz.repr().is_neg_zero()); let posz = ctx
.powi::<2>(&Repr::<2>::neg_zero(), 2.into())
.unwrap()
.value();
assert!(posz.repr().is_pos_zero()); }
#[test]
fn test_num_traits_sign() {
use dashu_base::Signed;
let negz: F = FBig::try_from(-0.0f64).unwrap();
let posz: F = FBig::try_from(0.0f64).unwrap();
assert!(!negz.is_positive());
assert!(negz.is_negative());
assert!(posz.is_positive());
assert!(!posz.is_negative());
}
#[test]
fn test_ln_1p_signed_zero() {
let ctx = Context::<mode::HalfEven>::new(53);
let r = ctx
.ln_1p::<2>(&Repr::<2>::neg_zero(), None)
.unwrap()
.value();
assert!(r.repr().is_neg_zero()); }
#[test]
fn test_exp_m1_signed_zero() {
let ctx = Context::<mode::HalfEven>::new(53);
let neg = ctx
.exp_m1::<2>(&Repr::<2>::neg_zero(), None)
.unwrap()
.value();
assert!(neg.repr().is_neg_zero());
let pos = ctx.exp_m1::<2>(&Repr::<2>::zero(), None).unwrap().value();
assert!(pos.repr().is_pos_zero() && !pos.repr().is_neg_zero());
}
#[test]
fn test_powf_neg_zero_exponent() {
let ctx = Context::<mode::HalfEven>::new(53);
let one = FBig::<mode::HalfEven>::ONE.with_precision(53).unwrap();
for base in [r2(5, 0), r2(-5, 0), Repr::<2>::neg_zero()] {
let r_neg = ctx
.powf::<2>(&base, &Repr::<2>::neg_zero(), None)
.unwrap()
.value();
let r_pos = ctx
.powf::<2>(&base, &Repr::<2>::zero(), None)
.unwrap()
.value();
assert_eq!(r_neg, one, "powf({:?}, -0) should be 1", base);
assert_eq!(r_pos, one, "powf({:?}, +0) should be 1", base);
}
}
#[test]
fn test_quantize_preserves_neg_zero() {
let negz = FBig::<mode::HalfEven, 2>::from_repr(Repr::<2>::neg_zero(), Context::new(53));
for q in [0isize, 3, -1] {
let r = negz.clone().quantize(q).value();
assert!(r.repr().is_neg_zero(), "quantize(-0, {}) should keep -0, got {:?}", q, r.repr());
}
}
#[test]
fn test_error_bounds_unlimited_precision_is_exact() {
use dashu_float::round::ErrorBounds;
let mk = |repr: Repr<2>| FBig::<mode::Away, 2>::from_repr(repr, Context::new(0));
for v in [
mk(Repr::neg_zero()),
mk(Repr::zero()),
mk(r2(7, 0)),
mk(r2(-7, 0)),
] {
let (l, r, il, ir) = mode::Away::error_bounds(&v);
assert_eq!(l, FBig::<mode::Away, 2>::ZERO, "lower bound nonzero for {:?}", v.repr());
assert_eq!(r, FBig::<mode::Away, 2>::ZERO, "upper bound nonzero for {:?}", v.repr());
assert!(il && ir, "bounds not inclusive for {:?}", v.repr());
}
}
#[test]
fn test_error_bounds_signed_zero_is_one_sided() {
use dashu_float::round::ErrorBounds;
let mk_z = |repr: Repr<2>| FBig::<mode::Zero, 2>::from_repr(repr, Context::new(8));
let half = |repr: Repr<2>| FBig::<mode::HalfEven, 2>::from_repr(repr, Context::new(8));
let (_, _, il, ir) = mode::Zero::error_bounds(&mk_z(Repr::zero()));
assert_eq!((il, ir), (false, false), "Zero +0 symmetric");
let (_, _, il, ir) = mode::Zero::error_bounds(&mk_z(Repr::neg_zero()));
assert_eq!((il, ir), (false, true), "Zero -0 one-sided");
let mk_a = |repr: Repr<2>| FBig::<mode::HalfAway, 2>::from_repr(repr, Context::new(8));
let (_, _, il, ir) = mode::HalfAway::error_bounds(&mk_a(Repr::zero()));
assert_eq!((il, ir), (false, false), "HalfAway +0 symmetric");
let (_, _, il, ir) = mode::HalfAway::error_bounds(&mk_a(Repr::neg_zero()));
assert_eq!((il, ir), (false, true), "HalfAway -0 one-sided");
let (_, _, il, ir) = mode::HalfEven::error_bounds(&half(Repr::zero()));
assert_eq!((il, ir), (false, false), "HalfEven +0 symmetric");
let (_, _, il, ir) = mode::HalfEven::error_bounds(&half(Repr::neg_zero()));
assert_eq!((il, ir), (false, true), "HalfEven -0 one-sided (aligned with HalfAway)");
}
#[cfg(feature = "num-order")]
#[test]
fn test_numord_neg_zero_equals_primitive_zero() {
use num_order::NumOrd;
let negz = FBig::<mode::HalfEven, 2>::from_repr(Repr::<2>::neg_zero(), Context::new(53));
assert_eq!(negz.num_partial_cmp(&0.0f64), Some(core::cmp::Ordering::Equal));
assert_eq!(negz.num_partial_cmp(&-0.0f64), Some(core::cmp::Ordering::Equal));
let neg = FBig::<mode::HalfEven, 2>::from_repr(r2(-5, 0), Context::new(53));
assert_eq!(neg.num_partial_cmp(&0.0f64), Some(core::cmp::Ordering::Less));
}
#[test]
fn test_asin_acos_unit_under_down() {
let down = Context::<mode::Down>::new(53);
let asin1 = down.asin::<2>(&Repr::<2>::one(), None).unwrap().value();
let asinm1 = down.asin::<2>(&Repr::<2>::neg_one(), None).unwrap().value();
assert_eq!(asin1.sign(), Sign::Positive);
assert_eq!(asinm1.sign(), Sign::Negative);
assert!((asin1.to_f64().value() - core::f64::consts::FRAC_PI_2).abs() < 1e-15);
assert!((asinm1.to_f64().value() + core::f64::consts::FRAC_PI_2).abs() < 1e-15);
let acos1 = down.acos::<2>(&Repr::<2>::one(), None).unwrap().value();
let acosm1 = down.acos::<2>(&Repr::<2>::neg_one(), None).unwrap().value();
assert!(
acos1.repr().is_pos_zero() || acos1.repr().is_neg_zero(),
"acos(+1) = 0, got {:?}",
acos1.repr()
);
assert!((acosm1.to_f64().value() - core::f64::consts::PI).abs() < 1e-14, "acos(-1) ≈ π");
}