use oxinum_core::Sign;
use oxinum_float::native::{BigFloat, RoundingMode};
use oxinum_int::native::BigUint;
const HE: RoundingMode = RoundingMode::HalfEven;
struct Rng(u64);
impl Rng {
fn new(seed: u64) -> Self {
Rng(seed | 1)
}
fn next_u64(&mut self) -> u64 {
let mut x = self.0;
x ^= x >> 12;
x ^= x << 25;
x ^= x >> 27;
self.0 = x;
x.wrapping_mul(0x2545_F491_4F6C_DD1D)
}
}
#[test]
fn engineering_worked_examples() {
let x = BigFloat::from_i64(12345, 64, HE);
assert_eq!(x.to_engineering_string(5), "12.345e3");
let y = BigFloat::from_f64(0.0001234, 60).expect("finite");
assert_eq!(y.to_engineering_string(4), "123.4e-6");
let one = BigFloat::from_i64(1, 64, HE);
assert_eq!(one.to_engineering_string(1), "1e0");
}
#[test]
fn engineering_one_more_digit_widths() {
let one = BigFloat::from_i64(1, 64, HE);
assert_eq!(one.to_engineering_string(3), "1.00e0");
let x = BigFloat::from_i64(12345, 64, HE);
assert_eq!(x.to_engineering_string(3), "12.3e3");
assert_eq!(x.to_engineering_string(2), "12e3");
assert_eq!(x.to_engineering_string(1), "10e3"); }
#[test]
fn engineering_negative_values() {
let x = BigFloat::from_i64(-12345, 64, HE);
assert_eq!(x.to_engineering_string(5), "-12.345e3");
let y = BigFloat::from_f64(-0.0001234, 60).expect("finite");
assert_eq!(y.to_engineering_string(4), "-123.4e-6");
}
#[test]
fn engineering_exponent_always_multiple_of_three() {
let mut rng = Rng::new(0xEEEE_0001);
for _ in 0..500 {
let m = BigUint::from_u64(1 + (rng.next_u64() % (1u64 << 40)));
let e = (rng.next_u64() % 400) as i64 - 200;
let val = BigFloat::from_parts(Sign::Positive, m, e, 64, HE);
if val.is_zero() {
continue;
}
for sig in [1usize, 2, 3, 4, 7, 10] {
let s = val.to_engineering_string(sig);
let exp = parse_e_exponent(&s);
assert_eq!(
exp.rem_euclid(3),
0,
"engineering exponent {exp} not a multiple of 3 (sig={sig}, s={s})"
);
let mant = mantissa_magnitude(&s);
assert!(
(1.0..1000.0).contains(&mant),
"engineering mantissa {mant} out of [1,1000) (s={s})"
);
}
}
}
#[test]
fn engineering_zero() {
let z = BigFloat::zero(53);
assert_eq!(z.to_engineering_string(1), "0e0");
assert_eq!(z.to_engineering_string(4), "0.000e0");
}
fn parse_e_exponent(s: &str) -> i64 {
let idx = s.find('e').expect("rendered string has an 'e'");
s[idx + 1..].parse::<i64>().expect("exponent parses")
}
fn mantissa_magnitude(s: &str) -> f64 {
let idx = s.find('e').expect("rendered string has an 'e'");
let mant = &s[..idx];
let mant = mant.strip_prefix('-').unwrap_or(mant);
mant.parse::<f64>().expect("mantissa parses")
}
#[test]
fn scientific_worked_examples() {
let x = BigFloat::from_i64(12345, 64, HE);
assert_eq!(x.to_scientific_string(5), "1.2345e4");
assert_eq!(x.to_scientific_string(3), "1.23e4");
assert_eq!(x.to_scientific_string(1), "1e4");
let one = BigFloat::from_i64(1, 64, HE);
assert_eq!(one.to_scientific_string(1), "1e0");
assert_eq!(one.to_scientific_string(3), "1.00e0");
let neg = BigFloat::from_i64(-42, 64, HE);
assert_eq!(neg.to_scientific_string(2), "-4.2e1");
}
#[test]
fn scientific_rounding_carry() {
let x = BigFloat::from_f64(9.999, 60).expect("finite");
assert_eq!(x.to_scientific_string(3), "1.00e1");
}
#[test]
fn scientific_small_value() {
let x = BigFloat::from_f64(0.0009765625, 60).expect("finite");
assert_eq!(x.to_scientific_string(7), "9.765625e-4");
}
#[test]
fn hex_worked_example() {
let x = BigFloat::from_hex_float("0x1.8p3", 53).expect("valid");
assert_eq!(x.to_f64(), 12.0);
let twelve = BigFloat::from_f64(12.0, 53).expect("finite");
assert_eq!(x, twelve);
assert_eq!(twelve.to_hex_string(), "0x1.8p3");
}
#[test]
fn hex_zero() {
let z = BigFloat::zero(53);
assert_eq!(z.to_hex_string(), "0x0p0");
let parsed = BigFloat::from_hex_float("0x0p0", 53).expect("valid");
assert!(parsed.is_zero());
assert!(BigFloat::from_hex_float("0x0.0p5", 53)
.expect("valid")
.is_zero());
assert!(BigFloat::from_hex_float("-0x0p0", 53)
.expect("valid")
.is_zero());
}
#[test]
fn hex_integer_part_more_than_one() {
let x = BigFloat::from_hex_float("0x1a.bp0", 60).expect("valid");
assert_eq!(x.to_f64(), 26.6875);
}
#[test]
fn hex_negative_and_signs() {
let x = BigFloat::from_hex_float("-0x1.8p3", 53).expect("valid");
assert_eq!(x.to_f64(), -12.0);
assert_eq!(x.sign(), Sign::Negative);
let y = BigFloat::from_hex_float("+0x1.8p+3", 53).expect("valid");
assert_eq!(y.to_f64(), 12.0);
let z = BigFloat::from_hex_float("0x1p-2", 53).expect("valid");
assert_eq!(z.to_f64(), 0.25);
}
#[test]
fn hex_uppercase_markers() {
let x = BigFloat::from_hex_float("0X1.8P3", 53).expect("valid");
assert_eq!(x.to_f64(), 12.0);
let y = BigFloat::from_hex_float("0x1.AbP0", 60).expect("valid");
assert_eq!(y.to_f64(), 427.0 / 256.0);
}
#[test]
fn hex_round_trip_random_bit_exact() {
let mut rng = Rng::new(0x1234_5678_9ABC_DEF0);
let mut checked = 0usize;
for i in 0..300 {
let prec = 1 + (rng.next_u64() % 256) as u32;
let limbs = (rng.next_u64() % 4) as usize; let mut bytes = Vec::new();
for _ in 0..limbs {
bytes.extend_from_slice(&rng.next_u64().to_le_bytes());
}
let mantissa = BigUint::from_bytes_le(&bytes);
let raw = rng.next_u64();
let exponent: i64 = match i % 4 {
0 => (raw % 2_000_000) as i64 - 1_000_000,
1 => (raw % 200) as i64 - 100,
2 => i64::MAX / 2 - (raw % 1000) as i64, _ => i64::MIN / 2 + (raw % 1000) as i64, };
let sign = if raw & 1 == 0 {
Sign::Positive
} else {
Sign::Negative
};
let x = BigFloat::from_parts(sign, mantissa, exponent, prec, HE);
let s = x.to_hex_string();
let back = BigFloat::from_hex_float(&s, x.precision())
.unwrap_or_else(|e| panic!("re-parse of {s:?} failed: {e:?}"));
assert_eq!(back, x, "hex round-trip mismatch: {s:?}");
assert_eq!(back.precision(), x.precision(), "precision drift: {s:?}");
checked += 1;
}
assert!(
checked >= 200,
"expected to check >= 200 values, got {checked}"
);
}
#[test]
fn hex_round_trip_specific_powers() {
for k in -60i64..=60 {
let two_k = BigFloat::from_parts(Sign::Positive, BigUint::one(), k, 32, HE);
let s = two_k.to_hex_string();
let back = BigFloat::from_hex_float(&s, two_k.precision()).expect("valid");
assert_eq!(back, two_k, "power 2^{k} round-trip via {s}");
}
}
#[test]
fn hex_malformed_inputs_err() {
let bad = [
"",
"0x",
"0X",
"1.5p3", "0x1.5", "0xG.1p0", "0x1.1pZ", "0x1.8p", "0xp3", "0x.p3", "0x1.8p3x", " 0x1.8p3", "0x1.8 p3", "0x1.8p3 ", "0x1.8p1e3", "++0x1p0", ];
for s in bad {
let r = BigFloat::from_hex_float(s, 53);
assert!(
r.is_err(),
"expected Err for malformed input {s:?}, got {r:?}"
);
}
}
#[test]
fn hex_parser_never_panics_on_random_ascii() {
let mut rng = Rng::new(0xC0FF_EE00_1234_5678);
let alphabet: &[u8] = b"0123456789abcdefABCDEFxXpP+-. ";
for _ in 0..5000 {
let n = (rng.next_u64() % 16) as usize;
let mut s = String::with_capacity(n);
for _ in 0..n {
let c = alphabet[(rng.next_u64() as usize) % alphabet.len()];
s.push(c as char);
}
let _ = BigFloat::from_hex_float(&s, 53);
}
for _ in 0..5000 {
let n = (rng.next_u64() % 20) as usize;
let mut s = String::with_capacity(n);
for _ in 0..n {
let c = (0x20 + (rng.next_u64() % 0x5F) as u8) as char;
s.push(c);
}
let _ = BigFloat::from_hex_float(&s, 53);
}
}