use oxinum_core::{OxiNumError, OxiNumResult};
use oxinum_float::native::{BigFloat, RoundingMode};
use oxinum_int::native::{BigInt, BigUint};
use super::BigRational;
pub fn rational_to_float(r: &BigRational, prec: u32, mode: RoundingMode) -> OxiNumResult<BigFloat> {
assert!(prec > 0, "BigFloat precision must be > 0");
let guard = 16u32;
let work_prec = prec.saturating_add(guard);
let num_f = BigFloat::from_bigint(r.num(), work_prec, mode);
let den_f = BigFloat::from_biguint(r.den(), work_prec, mode);
if den_f.is_zero() {
return Err(OxiNumError::DivByZero);
}
let result = num_f.div_ref_with_mode(&den_f, mode)?;
Ok(result.with_precision(prec, mode))
}
pub fn float_to_rational(f: &BigFloat) -> BigRational {
if f.is_zero() {
return BigRational::zero();
}
let mantissa = f.mantissa().clone();
let exponent = f.exponent();
let sign = f.sign();
if exponent >= 0 {
let shift = exponent as u64;
let num_u = mantissa.shl_bits(shift);
let num = BigInt::from_parts(sign, num_u);
BigRational::from_integer(num)
} else {
let neg_exp = (-exponent) as u64;
let den = BigUint::one().shl_bits(neg_exp);
let num = BigInt::from_parts(sign, mantissa);
BigRational::from_parts(num, den)
.unwrap_or_else(|e| panic!("float_to_rational: broken invariant: {e}"))
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxinum_float::native::RoundingMode;
use oxinum_int::native::{BigInt, BigUint};
const MODE: RoundingMode = RoundingMode::HalfEven;
fn br(n: i64, d: u64) -> BigRational {
BigRational::from_parts(BigInt::from(n), BigUint::from_u64(d)).expect("br")
}
#[test]
fn rational_to_float_one_third() {
let r = br(1, 3);
let f = rational_to_float(&r, 64, MODE).expect("1/3 to float");
let expected = 1.0f64 / 3.0;
assert!(
(f.to_f64() - expected).abs() < 1e-14,
"1/3 as float: got {}, expected {}",
f.to_f64(),
expected
);
}
#[test]
fn rational_to_float_negative() {
let r = br(-7, 4);
let f = rational_to_float(&r, 64, MODE).expect("-7/4 to float");
assert!(
(f.to_f64() - (-1.75)).abs() < 1e-14,
"-7/4 as float: {}",
f.to_f64()
);
}
#[test]
fn rational_to_float_zero() {
let r = BigRational::zero();
let f = rational_to_float(&r, 64, MODE).expect("0 to float");
assert!(f.is_zero());
}
#[test]
fn float_to_rational_half() {
let f = BigFloat::from_f64(0.5, 64).expect("0.5");
let r = float_to_rational(&f);
assert_eq!(r, br(1, 2), "0.5 → {r}");
}
#[test]
fn float_to_rational_one_and_a_half() {
let f = BigFloat::from_f64(1.5, 64).expect("1.5");
let r = float_to_rational(&f);
assert_eq!(r, br(3, 2), "1.5 → {r}");
}
#[test]
fn float_to_rational_integer() {
let f = BigFloat::from_i64(42, 64, MODE);
let r = float_to_rational(&f);
assert_eq!(r, br(42, 1), "42 → {r}");
}
#[test]
fn float_to_rational_zero() {
let f = BigFloat::zero(64);
let r = float_to_rational(&f);
assert!(r.is_zero());
}
#[test]
fn float_to_rational_negative() {
let f = BigFloat::from_f64(-0.25, 64).expect("-0.25");
let r = float_to_rational(&f);
assert_eq!(r, br(-1, 4), "-0.25 → {r}");
}
#[test]
fn binary_rational_roundtrip() {
for (n, d) in [(1i64, 2u64), (3, 4), (7, 8), (1, 16), (-3, 8)] {
let r = br(n, d);
let f = rational_to_float(&r, 128, MODE).expect("to_float");
let back = float_to_rational(&f);
assert_eq!(back, r, "Round-trip failed for {n}/{d}: got {back}");
}
}
#[test]
fn from_bigint_positive() {
use oxinum_int::native::BigInt;
let n = BigInt::from(12345i64);
let f = BigFloat::from_bigint(&n, 64, MODE);
assert!((f.to_f64() - 12345.0).abs() < 1e-10);
}
#[test]
fn from_bigint_negative() {
use oxinum_int::native::BigInt;
let n = BigInt::from(-999i64);
let f = BigFloat::from_bigint(&n, 64, MODE);
assert!((f.to_f64() - (-999.0)).abs() < 1e-10);
}
#[test]
fn from_biguint_large() {
let n = BigUint::from_u64(u64::MAX);
let f = BigFloat::from_biguint(&n, 64, MODE);
let expected = u64::MAX as f64;
assert!((f.to_f64() / expected - 1.0).abs() < 1e-13);
}
}