A value doesn’t have to be attacker-controlled to be dangerous. 2^-1_000_000_000 is a perfectly legitimate BigFloat — and asking this crate to lift it to an exact rational used to try to allocate a denominator a billion bits wide.
Today we released OxiNum 0.1.4 — the COOLJAPAN Pure-Rust arbitrary-precision math layer: bignum integers, exact rationals, high-precision floats, and arbitrary-precision complex numbers. This release closes a whole class of unbounded-allocation bugs that a legitimately valid, merely extreme, value could trigger — no adversarial input required, just a number far enough from zero or from one.
No GMP. No MPFR. No rug. No FFI, no -sys crates, no build-time C toolchain. Just clean, memory-safe Rust that compiles to a single static binary and goes wherever Rust goes.
Why OxiNum 0.1.4 is a game changer
BigFloat stores an unbounded i64 exponent alongside its mantissa — which is exactly the design that let a valid value turn a routine operation into a process-ending allocation:
Add/Subaligning two operands by exponent shifted the higher-exponent mantissa the full exponent difference — unbounded for a legitimately extreme gap.%/Remhad the identical unbounded-shift shape in its own alignment step.- Binary-splitting
exp/sin/cos(precision ≥ 512 bits) lifted an argument to an exact rational, and a tiny-but-valid value like2^-1_000_000_000could request a denominator proportional to that exponent’s magnitude. to_bigint_*conversions requested an allocation sized to the exact integer result, with no ceiling.
None of this needed malicious input. A single valid BigFloat at an extreme-but-legal exponent was enough.
OxiNum 0.1.4 closes all of it with one governing idea — MAX_EXACT_CONVERSION_BITS (2^27 bits), a budget every exact-conversion path now checks before it allocates:
align_to_common_expnow caps the alignment shift and folds the far operand into a single sticky bit once it can no longer affect the rounded result — same fix applied toRem’s reduction, now done by square-and-multiply bounded to the modulus width instead of materializing the full shift.split_arg’s exact-rational lift is now capped; negligible arguments short-circuit to a 1-2 term Taylor approximation before ever reaching it.to_bigint_trunc/floor/ceil/roundgained non-panickingtry_*counterparts that returnOxiNumError::Overflowinstead of requesting the unbounded allocation; the infallible originals now panic with# Panicsdocumentation instead of aborting the process outright.- A new enforced exponent range.
BigFloat::EMAX/EMIN(±2^40) bound every finite non-zero value’s binary magnitude, exposed via the newilogb(). The new fallibletry_from_partsconstructor rejects an out-of-range magnitude cleanly; the existing infalliblefrom_partsnow saturates instead of accepting anything, turning what used to be an unbounded invariant into a checked one at the one place values are born.
Technical Deep Dive: two correctness fixes underneath the hardening
to_f64double-rounding, fixed. ABigFloatthat was itself already the rounded result of an earlier operation got re-rounded blind on conversion tof64— and ties-to-even then resolved a tie the un-rounded value never actually sat on. That’s a 1-ULP error in roughly a quarter of all quotients landing in[2^-1023, 2^-1022), where the 52-bit subnormal grid’s halfway points coincide with the 53-bit normal grid’s.BigFloatnow records MPFR’s ternary value (the direction of its last rounding) andto_f64breaks exact ties with it —div_ref(..).to_f64()and(a * b).to_f64()now agree with the hardware bit for bit, all the way down to2^-1074.- Subnormal flush-to-zero, fixed. Every magnitude below
2^-1074used to flush straight to0.0instead of rounding onto the subnormal grid — but IEEE 754 requires gradual underflow, and a value in(2^-1075, 2^-1074)is above half the smallest subnormal and owes2^-1074, not zero.to_f64now rounds once, directly from the exact stored value, correctly handling the overflow threshold at the exact IEEE halfway point and preserving-0.0on negative underflow. Remcorrectness.rem_corerounded the quotient toprecsignificant bits before truncating, which could produce a remainder outside[0, |b|)whenever|a/b|’s integer part needed more bits thanprec. It now works directly on exact mantissas.
Getting Started
[dependencies]
oxinum = "0.1.5"
use oxinum_core::{OxiNumError, Sign};
use oxinum_float::native::{BigFloat, RoundingMode};
use oxinum_int::native::BigUint;
// One bit past EMAX is refused rather than silently accepted --
// try_from_parts is the fallible constructor introduced this release.
let err = BigFloat::try_from_parts(
Sign::Positive,
BigUint::one(),
BigFloat::EMAX + 1,
53,
RoundingMode::HalfEven,
)
.expect_err("2^(EMAX+1) is out of range");
assert!(matches!(err, OxiNumError::Overflow(_)));
// Non-panicking integer conversion, guarded by MAX_EXACT_CONVERSION_BITS.
let x = BigFloat::from_f64(1.5);
match x.try_to_bigint_round() {
Ok(n) => println!("{n}"),
Err(e) => println!("conversion budget exceeded: {e:?}"),
}
What’s New in 0.1.4
- Fixed: unbounded-allocation class across
Add/Sub/Remalignment and binary-splitting transcendentals for an extreme-but-valid exponent;to_f64double-rounding and subnormal flush-to-zero (both IEEE-754 correctness bugs);div_ref/div_ref_with_modeunder-sizing the quotient shift when the divisor is much wider than the dividend (previously1/3at precision 53 could return only 17 correct bits);oxinum::parse()now rejects"<n>/0"-style strings instead of building an invalid rational that panics later. - Added:
checked_divrem_int(oxinum-int),checked_div/checked_rem(oxinum-rational),try_to_bigint_{trunc,floor,ceil,round}andtry_float_to_rational— non-panicking counterparts across the board;BigFloat::EMAX/EMIN/ilogb()/try_from_parts;MAX_EXACT_CONVERSION_BITS; regression suites for every fix above plus a 1024-case fuzz harness foroxinum::parse;SECURITY.md,CONTRIBUTING.md,rustfmt.toml,clippy.toml. - Changed: all 17 production
panic!sites reachable from safe public APIs now carry# Panicsdocs pointing at a non-panicking alternative;to_scientific_string/to_engineering_stringfall back to losslessto_hex_stringinstead of attempting an exact decimal conversion past the budget; theserdefeature now enforces the exponent range on deserialize, not just precision/mantissa.
Tips
- If you work with
BigFloatvalues at extreme exponents (very large or very small, not just “big”), reach for the newtry_*APIs —try_to_bigint_*,try_float_to_rational,try_from_parts— instead of the infallible originals, which now panic (documented) rather than risk an unbounded allocation. BigFloat::ilogb()tells you a value’s binary magnitude directly — useful if you want to pre-check againstEMAX/EMINbefore an operation you know is exponent-sensitive.- Untrusted floats parsed via
from_hex_floatare now rejected, not saturated, when out of range — if your code was relying on a well-formed-but-extreme hex literal silently clamping, it now getsOxiNumError::Overflowinstead. to_f64()results changed at the bit level for values near the subnormal boundary — if you have golden-file tests comparingBigFloat::to_f64()output against hardcodedf64bit patterns in[2^-1075, 2^-1022), expect them to now match the hardware exactly (they were wrong before).Rem/%results changed for divisors where|a/b|’s integer part exceeded the working precision — if you depended on the old (incorrect) out-of-range remainder, re-verify against the corrected[0, |b|)behavior.
This is the foundation
Correct, allocation-bounded arbitrary-precision arithmetic matters most for whatever builds numerically on top without wanting to think about denial-of-service from its own math library. SciRS2 pins the full oxinum-* family as its Pure-Rust GMP/MPFR-free arbitrary-precision layer.
Repository: https://github.com/cool-japan/oxinum
Star the repo if “a valid number shouldn’t be able to abort your process” is a bar every bignum library should clear.
The era of allocation size scaling with an untrusted (or merely extreme) exponent is over. Pure Rust arbitrary-precision math that’s fast, safe, and sovereign — is here.
— KitaSan at COOLJAPAN OÜ August 6, 2026