use std::cmp::Ordering;
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
enum Sign {
Neg,
Zero,
Pos,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct BigInt {
sign: Sign,
mag: Mag,
}
#[derive(Clone, PartialEq, Eq, Hash)]
enum Mag {
Inline(u64),
Heap(Vec<u64>),
}
impl Mag {
fn from_vec(mut v: Vec<u64>) -> Mag {
while v.last() == Some(&0) {
v.pop();
}
match v.len() {
0 => Mag::Inline(0),
1 => Mag::Inline(v[0]),
_ => Mag::Heap(v),
}
}
fn limbs(&self) -> &[u64] {
match self {
Mag::Inline(0) => &[],
Mag::Inline(x) => std::slice::from_ref(x),
Mag::Heap(v) => v,
}
}
fn as_single(&self) -> Option<u64> {
match self {
Mag::Inline(x) => Some(*x),
Mag::Heap(_) => None,
}
}
}
fn normalize(mut mag: Vec<u64>) -> Vec<u64> {
while mag.last() == Some(&0) {
mag.pop();
}
mag
}
fn mag_cmp(a: &[u64], b: &[u64]) -> Ordering {
match a.len().cmp(&b.len()) {
Ordering::Equal => {
for i in (0..a.len()).rev() {
match a[i].cmp(&b[i]) {
Ordering::Equal => {}
other => return other,
}
}
Ordering::Equal
}
other => other,
}
}
fn mag_add(a: &[u64], b: &[u64]) -> Vec<u64> {
let mut out = Vec::with_capacity(a.len().max(b.len()) + 1);
let mut carry = 0u128;
for i in 0..a.len().max(b.len()) {
let av = *a.get(i).unwrap_or(&0) as u128;
let bv = *b.get(i).unwrap_or(&0) as u128;
let sum = av + bv + carry;
out.push(sum as u64);
carry = sum >> 64;
}
if carry != 0 {
out.push(carry as u64);
}
normalize(out)
}
fn mag_sub(a: &[u64], b: &[u64]) -> Vec<u64> {
debug_assert!(mag_cmp(a, b) != Ordering::Less, "mag_sub underflow");
let mut out = Vec::with_capacity(a.len());
let mut borrow = 0i128;
for i in 0..a.len() {
let av = a[i] as i128;
let bv = *b.get(i).unwrap_or(&0) as i128;
let mut diff = av - bv - borrow;
if diff < 0 {
diff += 1i128 << 64;
borrow = 1;
} else {
borrow = 0;
}
out.push(diff as u64);
}
debug_assert_eq!(borrow, 0, "mag_sub left a borrow");
normalize(out)
}
fn mag_mul(a: &[u64], b: &[u64]) -> Vec<u64> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut out = vec![0u64; a.len() + b.len()];
for (i, &av) in a.iter().enumerate() {
let mut carry = 0u128;
for (j, &bv) in b.iter().enumerate() {
let cur = out[i + j] as u128 + (av as u128) * (bv as u128) + carry;
out[i + j] = cur as u64;
carry = cur >> 64;
}
out[i + b.len()] += carry as u64;
}
normalize(out)
}
fn mag_shl1(a: &[u64], bit: u64) -> Vec<u64> {
let mut out = Vec::with_capacity(a.len() + 1);
let mut carry = bit & 1;
for &limb in a {
out.push((limb << 1) | carry);
carry = limb >> 63;
}
if carry != 0 {
out.push(carry);
}
normalize(out)
}
fn mag_divrem(a: &[u64], b: &[u64]) -> (Vec<u64>, Vec<u64>) {
debug_assert!(!b.is_empty(), "division by zero magnitude");
if mag_cmp(a, b) == Ordering::Less {
return (Vec::new(), a.to_vec());
}
let nbits = a.len() * 64;
let mut q = vec![0u64; a.len()];
let mut r: Vec<u64> = Vec::new();
for i in (0..nbits).rev() {
let bit = (a[i / 64] >> (i % 64)) & 1;
r = mag_shl1(&r, bit);
if mag_cmp(&r, b) != Ordering::Less {
r = mag_sub(&r, b);
q[i / 64] |= 1u64 << (i % 64);
}
}
(normalize(q), normalize(r))
}
impl BigInt {
pub fn zero() -> Self {
BigInt { sign: Sign::Zero, mag: Mag::Inline(0) }
}
fn from_sign_mag(neg: bool, mag: Vec<u64>) -> Self {
let mag = Mag::from_vec(mag);
let sign = if mag.limbs().is_empty() {
Sign::Zero
} else if neg {
Sign::Neg
} else {
Sign::Pos
};
BigInt { sign, mag }
}
fn from_sign_mag_u128(neg: bool, m: u128) -> Self {
if m == 0 {
return Self::zero();
}
let lo = m as u64;
let hi = (m >> 64) as u64;
let mag = if hi == 0 { Mag::Inline(lo) } else { Mag::Heap(vec![lo, hi]) };
BigInt { sign: if neg { Sign::Neg } else { Sign::Pos }, mag }
}
pub fn from_i64(x: i64) -> Self {
Self::from_sign_mag_u128(x < 0, (x as i128).unsigned_abs())
}
pub fn from_u64(x: u64) -> Self {
Self::from_sign_mag_u128(false, x as u128)
}
pub fn to_i64(&self) -> Option<i64> {
let m = self.mag.as_single()? as i128;
let v = if self.sign == Sign::Neg { -m } else { m };
if (i64::MIN as i128..=i64::MAX as i128).contains(&v) {
Some(v as i64)
} else {
None
}
}
pub fn is_zero(&self) -> bool {
self.sign == Sign::Zero
}
pub fn is_odd(&self) -> bool {
self.mag.limbs().first().is_some_and(|limb| limb & 1 == 1)
}
pub fn to_f64(&self) -> f64 {
const TWO64: f64 = 18_446_744_073_709_551_616.0; let mut acc = 0.0f64;
for &limb in self.mag.limbs().iter().rev() {
acc = acc * TWO64 + limb as f64;
}
if self.sign == Sign::Neg {
-acc
} else {
acc
}
}
pub fn is_negative(&self) -> bool {
self.sign == Sign::Neg
}
pub fn is_positive(&self) -> bool {
self.sign == Sign::Pos
}
pub fn abs(&self) -> Self {
BigInt { sign: if self.sign == Sign::Zero { Sign::Zero } else { Sign::Pos }, mag: self.mag.clone() }
}
pub fn negated(&self) -> Self {
let sign = match self.sign {
Sign::Neg => Sign::Pos,
Sign::Zero => Sign::Zero,
Sign::Pos => Sign::Neg,
};
BigInt { sign, mag: self.mag.clone() }
}
pub fn add(&self, other: &Self) -> Self {
if let (Some(a), Some(b)) = (self.mag.as_single(), other.mag.as_single()) {
let av = if self.sign == Sign::Neg { -(a as i128) } else { a as i128 };
let bv = if other.sign == Sign::Neg { -(b as i128) } else { b as i128 };
let sum = av + bv;
return Self::from_sign_mag_u128(sum < 0, sum.unsigned_abs());
}
match (self.sign, other.sign) {
(Sign::Zero, _) => other.clone(),
(_, Sign::Zero) => self.clone(),
(a, b) if a == b => {
Self::from_sign_mag(a == Sign::Neg, mag_add(self.mag.limbs(), other.mag.limbs()))
}
_ => match mag_cmp(self.mag.limbs(), other.mag.limbs()) {
Ordering::Equal => Self::zero(),
Ordering::Greater => Self::from_sign_mag(
self.sign == Sign::Neg,
mag_sub(self.mag.limbs(), other.mag.limbs()),
),
Ordering::Less => Self::from_sign_mag(
other.sign == Sign::Neg,
mag_sub(other.mag.limbs(), self.mag.limbs()),
),
},
}
}
pub fn sub(&self, other: &Self) -> Self {
self.add(&other.negated())
}
pub fn mul(&self, other: &Self) -> Self {
let neg = (self.sign == Sign::Neg) ^ (other.sign == Sign::Neg);
if let (Some(a), Some(b)) = (self.mag.as_single(), other.mag.as_single()) {
return Self::from_sign_mag_u128(neg, (a as u128) * (b as u128));
}
if self.is_zero() || other.is_zero() {
return Self::zero();
}
Self::from_sign_mag(neg, mag_mul(self.mag.limbs(), other.mag.limbs()))
}
pub fn div_rem(&self, other: &Self) -> Option<(Self, Self)> {
if other.is_zero() {
return None;
}
let q_neg = (self.sign == Sign::Neg) ^ (other.sign == Sign::Neg);
if let (Some(a), Some(b)) = (self.mag.as_single(), other.mag.as_single()) {
return Some((
Self::from_sign_mag_u128(q_neg, (a / b) as u128),
Self::from_sign_mag_u128(self.sign == Sign::Neg, (a % b) as u128),
));
}
if self.is_zero() {
return Some((Self::zero(), Self::zero()));
}
let (qm, rm) = mag_divrem(self.mag.limbs(), other.mag.limbs());
let q = Self::from_sign_mag(q_neg, qm);
let r = Self::from_sign_mag(self.sign == Sign::Neg, rm);
Some((q, r))
}
pub fn parse_decimal(s: &str) -> Option<Self> {
let bytes = s.as_bytes();
let (neg, digits) = match bytes.first() {
Some(b'-') => (true, &bytes[1..]),
Some(b'+') => (false, &bytes[1..]),
_ => (false, bytes),
};
if digits.is_empty() {
return None;
}
let ten = BigInt::from_u64(10);
let mut acc = BigInt::zero();
for &d in digits {
if !d.is_ascii_digit() {
return None;
}
acc = acc.mul(&ten).add(&BigInt::from_u64((d - b'0') as u64));
}
Some(if neg { acc.negated() } else { acc })
}
pub fn pow(&self, mut exp: u32) -> BigInt {
let mut result = BigInt::from_u64(1);
let mut base = self.clone();
while exp > 0 {
if exp & 1 == 1 {
result = result.mul(&base);
}
exp >>= 1;
if exp > 0 {
base = base.mul(&base);
}
}
result
}
pub fn to_le_bytes(&self) -> (bool, Vec<u8>) {
let limbs = self.mag.limbs();
let mut bytes = Vec::with_capacity(limbs.len() * 8);
for &limb in limbs {
bytes.extend_from_slice(&limb.to_le_bytes());
}
(self.sign == Sign::Neg, bytes)
}
pub fn from_le_bytes(negative: bool, bytes: &[u8]) -> Self {
let mut mag = Vec::with_capacity(bytes.len().div_ceil(8));
for chunk in bytes.chunks(8) {
let mut limb = [0u8; 8];
limb[..chunk.len()].copy_from_slice(chunk);
mag.push(u64::from_le_bytes(limb));
}
Self::from_sign_mag(negative, mag)
}
}
impl Ord for BigInt {
fn cmp(&self, other: &Self) -> Ordering {
let rank = |s: Sign| match s {
Sign::Neg => -1i8,
Sign::Zero => 0,
Sign::Pos => 1,
};
match rank(self.sign).cmp(&rank(other.sign)) {
Ordering::Equal => match self.sign {
Sign::Zero => Ordering::Equal,
Sign::Pos => mag_cmp(self.mag.limbs(), other.mag.limbs()),
Sign::Neg => mag_cmp(other.mag.limbs(), self.mag.limbs()),
},
other => other,
}
}
}
impl PartialOrd for BigInt {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for BigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
return write!(f, "0");
}
const TEN19: u64 = 10_000_000_000_000_000_000;
let ten19 = BigInt::from_u64(TEN19);
let mut chunks: Vec<u64> = Vec::new();
let mut cur = self.abs();
while !cur.is_zero() {
let (q, r) = cur.div_rem(&ten19).expect("10^19 is nonzero");
chunks.push(r.mag.limbs().first().copied().unwrap_or(0));
cur = q;
}
if self.is_negative() {
write!(f, "-")?;
}
write!(f, "{}", chunks.last().unwrap())?;
for &chunk in chunks.iter().rev().skip(1) {
write!(f, "{chunk:019}")?;
}
Ok(())
}
}
impl fmt::Debug for BigInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "BigInt({self})")
}
}
impl From<i64> for BigInt {
fn from(x: i64) -> Self {
BigInt::from_i64(x)
}
}
fn bigint_gcd(a: &BigInt, b: &BigInt) -> BigInt {
let mut a = a.abs();
let mut b = b.abs();
while !b.is_zero() {
let (_q, r) = a.div_rem(&b).expect("b is nonzero inside the loop");
a = b;
b = r;
}
a
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Rational {
num: BigInt,
den: BigInt,
}
impl Rational {
pub fn new(num: BigInt, den: BigInt) -> Option<Rational> {
if den.is_zero() {
return None;
}
let (mut num, mut den) = (num, den);
if den.is_negative() {
num = num.negated();
den = den.negated();
}
if num.is_zero() {
return Some(Rational { num: BigInt::zero(), den: BigInt::from_i64(1) });
}
let g = bigint_gcd(&num, &den);
let (num, _) = num.div_rem(&g).expect("gcd divides the numerator exactly");
let (den, _) = den.div_rem(&g).expect("gcd divides the denominator exactly");
Some(Rational { num, den })
}
pub fn from_bigint(n: BigInt) -> Rational {
Rational { num: n, den: BigInt::from_i64(1) }
}
pub fn from_i64(x: i64) -> Rational {
Rational::from_bigint(BigInt::from_i64(x))
}
pub fn from_ratio_i64(n: i64, d: i64) -> Option<Rational> {
Rational::new(BigInt::from_i64(n), BigInt::from_i64(d))
}
pub fn zero() -> Rational {
Rational { num: BigInt::zero(), den: BigInt::from_i64(1) }
}
pub fn one() -> Rational {
Rational::from_i64(1)
}
pub fn numerator(&self) -> &BigInt {
&self.num
}
pub fn denominator(&self) -> &BigInt {
&self.den
}
pub fn is_integer(&self) -> bool {
self.den == BigInt::from_i64(1)
}
pub fn is_zero(&self) -> bool {
self.num.is_zero()
}
pub fn is_negative(&self) -> bool {
self.num.is_negative()
}
pub fn is_positive(&self) -> bool {
self.num.is_positive()
}
pub fn to_bigint(&self) -> Option<BigInt> {
if self.is_integer() {
Some(self.num.clone())
} else {
None
}
}
pub fn to_i64(&self) -> Option<i64> {
if self.is_integer() {
self.num.to_i64()
} else {
None
}
}
pub fn to_f64(&self) -> f64 {
self.num.to_f64() / self.den.to_f64()
}
pub fn floor(&self) -> BigInt {
let (q, r) = self.num.div_rem(&self.den).expect("denominator is nonzero");
if self.num.is_negative() && !r.is_zero() {
q.sub(&BigInt::from_i64(1))
} else {
q
}
}
pub fn ceil(&self) -> BigInt {
let (q, r) = self.num.div_rem(&self.den).expect("denominator is nonzero");
if !self.num.is_negative() && !r.is_zero() {
q.add(&BigInt::from_i64(1))
} else {
q
}
}
pub fn round(&self) -> BigInt {
let two = BigInt::from_i64(2);
let numerator = self.num.abs().mul(&two).add(&self.den);
let denominator = self.den.mul(&two);
let (mag, _) = numerator.div_rem(&denominator).expect("denominator is nonzero");
if self.num.is_negative() {
mag.negated()
} else {
mag
}
}
pub fn negated(&self) -> Rational {
Rational { num: self.num.negated(), den: self.den.clone() }
}
pub fn abs(&self) -> Rational {
Rational { num: self.num.abs(), den: self.den.clone() }
}
pub fn recip(&self) -> Option<Rational> {
Rational::new(self.den.clone(), self.num.clone())
}
pub fn add(&self, other: &Rational) -> Rational {
let num = self.num.mul(&other.den).add(&other.num.mul(&self.den));
let den = self.den.mul(&other.den);
Rational::new(num, den).expect("product of positive denominators is nonzero")
}
pub fn sub(&self, other: &Rational) -> Rational {
let num = self.num.mul(&other.den).sub(&other.num.mul(&self.den));
let den = self.den.mul(&other.den);
Rational::new(num, den).expect("product of positive denominators is nonzero")
}
pub fn mul(&self, other: &Rational) -> Rational {
let num = self.num.mul(&other.num);
let den = self.den.mul(&other.den);
Rational::new(num, den).expect("product of positive denominators is nonzero")
}
pub fn div(&self, other: &Rational) -> Option<Rational> {
let num = self.num.mul(&other.den);
let den = self.den.mul(&other.num);
Rational::new(num, den)
}
pub fn pow(&self, exp: i32) -> Option<Rational> {
if exp >= 0 {
let k = exp as u32;
Some(
Rational::new(self.num.pow(k), self.den.pow(k))
.expect("denominator^k stays positive"),
)
} else {
if self.num.is_zero() {
return None;
}
let k = exp.unsigned_abs();
Rational::new(self.den.pow(k), self.num.pow(k))
}
}
pub fn parse(s: &str) -> Option<Rational> {
let s = s.trim();
if let Some((n, d)) = s.split_once('/') {
let num = BigInt::parse_decimal(n.trim())?;
let den = BigInt::parse_decimal(d.trim())?;
Rational::new(num, den)
} else {
Some(Rational::from_bigint(BigInt::parse_decimal(s)?))
}
}
}
impl Ord for Rational {
fn cmp(&self, other: &Self) -> Ordering {
self.num.mul(&other.den).cmp(&other.num.mul(&self.den))
}
}
impl PartialOrd for Rational {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_integer() {
write!(f, "{}", self.num)
} else {
write!(f, "{}/{}", self.num, self.den)
}
}
}
impl fmt::Debug for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Rational({}/{})", self.num, self.den)
}
}
impl From<i64> for Rational {
fn from(x: i64) -> Self {
Rational::from_i64(x)
}
}
impl From<BigInt> for Rational {
fn from(x: BigInt) -> Self {
Rational::from_bigint(x)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub enum RoundingMode {
Down,
Up,
Floor,
Ceiling,
HalfUp,
HalfDown,
HalfEven,
}
fn ten_pow(k: u32) -> BigInt {
BigInt::from_u64(10).pow(k)
}
fn round_rational_to_bigint(x: &Rational, mode: RoundingMode) -> BigInt {
let num = x.numerator();
let den = x.denominator(); let (q, r) = num.div_rem(den).expect("rational denominator is nonzero");
if r.is_zero() {
return q; }
let neg = num.is_negative();
let twice = r.abs().mul(&BigInt::from_i64(2));
let half = twice.cmp(den);
let away = match mode {
RoundingMode::Down => false,
RoundingMode::Up => true,
RoundingMode::Floor => neg,
RoundingMode::Ceiling => !neg,
RoundingMode::HalfUp => half != Ordering::Less,
RoundingMode::HalfDown => half == Ordering::Greater,
RoundingMode::HalfEven => half == Ordering::Greater || (half == Ordering::Equal && q.is_odd()),
};
if away {
if neg { q.sub(&BigInt::from_i64(1)) } else { q.add(&BigInt::from_i64(1)) }
} else {
q
}
}
#[derive(Clone)]
pub struct Decimal {
coeff: BigInt,
scale: u32,
}
impl Decimal {
pub fn from_bigint(coeff: BigInt) -> Decimal {
Decimal { coeff, scale: 0 }
}
pub fn from_i64(x: i64) -> Decimal {
Decimal::from_bigint(BigInt::from_i64(x))
}
pub fn from_coeff_scale(coeff: BigInt, scale: u32) -> Decimal {
Decimal { coeff, scale }
}
pub fn zero() -> Decimal {
Decimal::from_i64(0)
}
pub fn one() -> Decimal {
Decimal::from_i64(1)
}
pub fn scale(&self) -> u32 {
self.scale
}
pub fn coefficient(&self) -> &BigInt {
&self.coeff
}
pub fn is_zero(&self) -> bool {
self.coeff.is_zero()
}
pub fn is_negative(&self) -> bool {
self.coeff.is_negative()
}
pub fn negated(&self) -> Decimal {
Decimal { coeff: self.coeff.negated(), scale: self.scale }
}
pub fn abs(&self) -> Decimal {
Decimal { coeff: self.coeff.abs(), scale: self.scale }
}
pub fn to_rational(&self) -> Rational {
Rational::new(self.coeff.clone(), ten_pow(self.scale)).expect("10^scale is nonzero")
}
pub fn from_rational(value: &Rational, scale: u32, mode: RoundingMode) -> Decimal {
let scaled = value.mul(&Rational::from_bigint(ten_pow(scale)));
Decimal { coeff: round_rational_to_bigint(&scaled, mode), scale }
}
pub fn rescale(&self, scale: u32, mode: RoundingMode) -> Decimal {
if scale >= self.scale {
Decimal { coeff: self.coeff.mul(&ten_pow(scale - self.scale)), scale }
} else {
Decimal::from_rational(&self.to_rational(), scale, mode)
}
}
fn aligned(&self, other: &Decimal) -> (BigInt, BigInt, u32) {
let s = self.scale.max(other.scale);
let a = self.coeff.mul(&ten_pow(s - self.scale));
let b = other.coeff.mul(&ten_pow(s - other.scale));
(a, b, s)
}
pub fn add(&self, other: &Decimal) -> Decimal {
let (a, b, s) = self.aligned(other);
Decimal { coeff: a.add(&b), scale: s }
}
pub fn sub(&self, other: &Decimal) -> Decimal {
let (a, b, s) = self.aligned(other);
Decimal { coeff: a.sub(&b), scale: s }
}
pub fn mul(&self, other: &Decimal) -> Decimal {
Decimal { coeff: self.coeff.mul(&other.coeff), scale: self.scale + other.scale }
}
pub fn div(&self, other: &Decimal, scale: u32, mode: RoundingMode) -> Option<Decimal> {
let q = self.to_rational().div(&other.to_rational())?;
Some(Decimal::from_rational(&q, scale, mode))
}
fn normalized(&self) -> (BigInt, u32) {
if self.coeff.is_zero() {
return (BigInt::zero(), 0);
}
let ten = BigInt::from_u64(10);
let mut c = self.coeff.clone();
let mut s = self.scale;
while s > 0 {
let (q, r) = c.div_rem(&ten).expect("ten is nonzero");
if !r.is_zero() {
break;
}
c = q;
s -= 1;
}
(c, s)
}
pub fn to_le_bytes(&self) -> (bool, Vec<u8>, u32) {
let (neg, bytes) = self.coeff.to_le_bytes();
(neg, bytes, self.scale)
}
pub fn from_le_bytes(negative: bool, bytes: &[u8], scale: u32) -> Decimal {
Decimal { coeff: BigInt::from_le_bytes(negative, bytes), scale }
}
pub fn parse(s: &str) -> Option<Decimal> {
let s = s.trim();
let (neg, body) = match s.as_bytes().first() {
Some(b'-') => (true, &s[1..]),
Some(b'+') => (false, &s[1..]),
_ => (false, s),
};
if body.is_empty() {
return None;
}
let (int_part, frac_part) = match body.split_once('.') {
Some((i, f)) => (i, f),
None => (body, ""),
};
if int_part.is_empty() && frac_part.is_empty() {
return None; }
if !int_part.bytes().all(|c| c.is_ascii_digit()) || !frac_part.bytes().all(|c| c.is_ascii_digit()) {
return None;
}
let digits = format!("{int_part}{frac_part}");
let mag = if digits.is_empty() { BigInt::zero() } else { BigInt::parse_decimal(&digits)? };
let coeff = if neg { mag.negated() } else { mag };
Some(Decimal { coeff, scale: frac_part.len() as u32 })
}
}
impl PartialEq for Decimal {
fn eq(&self, other: &Self) -> bool {
self.normalized() == other.normalized()
}
}
impl Eq for Decimal {}
impl std::hash::Hash for Decimal {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.normalized().hash(state);
}
}
impl Ord for Decimal {
fn cmp(&self, other: &Self) -> Ordering {
self.to_rational().cmp(&other.to_rational())
}
}
impl PartialOrd for Decimal {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.scale == 0 {
return write!(f, "{}", self.coeff);
}
let neg = self.coeff.is_negative();
let mut digits = self.coeff.abs().to_string();
let scale = self.scale as usize;
if digits.len() <= scale {
digits = format!("{}{}", "0".repeat(scale + 1 - digits.len()), digits);
}
let point = digits.len() - scale;
if neg {
write!(f, "-")?;
}
write!(f, "{}.{}", &digits[..point], &digits[point..])
}
}
impl fmt::Debug for Decimal {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Decimal({self})")
}
}
impl From<i64> for Decimal {
fn from(x: i64) -> Self {
Decimal::from_i64(x)
}
}
impl From<BigInt> for Decimal {
fn from(x: BigInt) -> Self {
Decimal::from_bigint(x)
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Complex {
re: Rational,
im: Rational,
}
impl Complex {
pub fn new(re: Rational, im: Rational) -> Complex {
Complex { re, im }
}
pub fn from_rational(re: Rational) -> Complex {
Complex { re, im: Rational::zero() }
}
pub fn from_i64(x: i64) -> Complex {
Complex::from_rational(Rational::from_i64(x))
}
pub fn zero() -> Complex {
Complex { re: Rational::zero(), im: Rational::zero() }
}
pub fn one() -> Complex {
Complex::from_i64(1)
}
pub fn i() -> Complex {
Complex { re: Rational::zero(), im: Rational::one() }
}
pub fn re(&self) -> &Rational {
&self.re
}
pub fn im(&self) -> &Rational {
&self.im
}
pub fn is_zero(&self) -> bool {
self.re.is_zero() && self.im.is_zero()
}
pub fn is_real(&self) -> bool {
self.im.is_zero()
}
pub fn conjugate(&self) -> Complex {
Complex { re: self.re.clone(), im: self.im.negated() }
}
pub fn norm_sqr(&self) -> Rational {
self.re.mul(&self.re).add(&self.im.mul(&self.im))
}
pub fn abs_f64(&self) -> f64 {
self.norm_sqr().to_f64().sqrt()
}
pub fn negated(&self) -> Complex {
Complex { re: self.re.negated(), im: self.im.negated() }
}
pub fn add(&self, other: &Complex) -> Complex {
Complex { re: self.re.add(&other.re), im: self.im.add(&other.im) }
}
pub fn sub(&self, other: &Complex) -> Complex {
Complex { re: self.re.sub(&other.re), im: self.im.sub(&other.im) }
}
pub fn mul(&self, other: &Complex) -> Complex {
let (a, b, c, d) = (&self.re, &self.im, &other.re, &other.im);
Complex { re: a.mul(c).sub(&b.mul(d)), im: a.mul(d).add(&b.mul(c)) }
}
pub fn recip(&self) -> Option<Complex> {
let n = self.norm_sqr();
if n.is_zero() {
return None;
}
let inv = n.recip().expect("norm is nonzero here");
Some(Complex { re: self.re.mul(&inv), im: self.im.negated().mul(&inv) })
}
pub fn div(&self, other: &Complex) -> Option<Complex> {
Some(self.mul(&other.recip()?))
}
pub fn pow(&self, exp: i32) -> Option<Complex> {
if exp < 0 {
return self.recip()?.pow(-exp);
}
let mut result = Complex::one();
let mut base = self.clone();
let mut e = exp as u32;
while e > 0 {
if e & 1 == 1 {
result = result.mul(&base);
}
e >>= 1;
if e > 0 {
base = base.mul(&base);
}
}
Some(result)
}
pub fn parse(s: &str) -> Option<Complex> {
let s = s.trim();
if s.is_empty() {
return None;
}
let bytes = s.as_bytes();
if bytes[bytes.len() - 1] != b'i' {
return Some(Complex::from_rational(Rational::parse(s)?));
}
let body = &s[..s.len() - 1]; let split = body
.bytes()
.enumerate()
.rev()
.find(|&(i, c)| i > 0 && (c == b'+' || c == b'-'))
.map(|(i, _)| i);
let (real_str, imag_str) = match split {
Some(i) => (&body[..i], &body[i..]),
None => ("", body),
};
let re = if real_str.is_empty() { Rational::zero() } else { Rational::parse(real_str)? };
let im = match imag_str {
"" | "+" => Rational::one(),
"-" => Rational::from_i64(-1),
other => Rational::parse(other)?,
};
Some(Complex { re, im })
}
}
impl fmt::Display for Complex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.im.is_zero() {
return write!(f, "{}", self.re);
}
let mag = self.im.abs();
let unit = mag == Rational::one(); if self.re.is_zero() {
return if self.im.is_negative() {
if unit { write!(f, "-i") } else { write!(f, "-{mag}i") }
} else if unit {
write!(f, "i")
} else {
write!(f, "{mag}i")
};
}
let sign = if self.im.is_negative() { "-" } else { "+" };
if unit {
write!(f, "{}{}i", self.re, sign)
} else {
write!(f, "{}{}{}i", self.re, sign, mag)
}
}
}
impl fmt::Debug for Complex {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Complex({self})")
}
}
impl From<i64> for Complex {
fn from(x: i64) -> Self {
Complex::from_i64(x)
}
}
impl From<Rational> for Complex {
fn from(x: Rational) -> Self {
Complex::from_rational(x)
}
}
fn bigint_egcd(a: &BigInt, b: &BigInt) -> (BigInt, BigInt, BigInt) {
let (mut old_r, mut r) = (a.clone(), b.clone());
let (mut old_s, mut s) = (BigInt::from_i64(1), BigInt::zero());
let (mut old_t, mut t) = (BigInt::zero(), BigInt::from_i64(1));
while !r.is_zero() {
let (q, _) = old_r.div_rem(&r).expect("r is nonzero inside the loop");
let nr = old_r.sub(&q.mul(&r));
old_r = std::mem::replace(&mut r, nr);
let ns = old_s.sub(&q.mul(&s));
old_s = std::mem::replace(&mut s, ns);
let nt = old_t.sub(&q.mul(&t));
old_t = std::mem::replace(&mut t, nt);
}
(old_r, old_s, old_t)
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Modular {
value: BigInt,
modulus: BigInt,
}
fn mod_reduce(value: BigInt, modulus: &BigInt) -> BigInt {
let (_, r) = value.div_rem(modulus).expect("modulus is nonzero");
if r.is_negative() {
r.add(modulus)
} else {
r
}
}
impl Modular {
pub fn new(value: BigInt, modulus: BigInt) -> Option<Modular> {
if modulus.is_zero() || modulus.is_negative() {
return None;
}
let value = mod_reduce(value, &modulus);
Some(Modular { value, modulus })
}
pub fn from_i64(value: i64, modulus: i64) -> Option<Modular> {
Modular::new(BigInt::from_i64(value), BigInt::from_i64(modulus))
}
pub fn value(&self) -> &BigInt {
&self.value
}
pub fn modulus(&self) -> &BigInt {
&self.modulus
}
pub fn is_zero(&self) -> bool {
self.value.is_zero()
}
pub fn add(&self, other: &Modular) -> Option<Modular> {
if self.modulus != other.modulus {
return None;
}
Modular::new(self.value.add(&other.value), self.modulus.clone())
}
pub fn sub(&self, other: &Modular) -> Option<Modular> {
if self.modulus != other.modulus {
return None;
}
Modular::new(self.value.sub(&other.value), self.modulus.clone())
}
pub fn mul(&self, other: &Modular) -> Option<Modular> {
if self.modulus != other.modulus {
return None;
}
Modular::new(self.value.mul(&other.value), self.modulus.clone())
}
pub fn negated(&self) -> Modular {
Modular::new(self.value.negated(), self.modulus.clone()).expect("modulus stays valid")
}
pub fn pow(&self, mut exp: u64) -> Modular {
let one = Modular::new(BigInt::from_i64(1), self.modulus.clone()).expect("modulus valid");
let mut result = one;
let mut base = self.clone();
while exp > 0 {
if exp & 1 == 1 {
result = result.mul(&base).expect("same modulus");
}
exp >>= 1;
if exp > 0 {
base = base.mul(&base).expect("same modulus");
}
}
result
}
pub fn inverse(&self) -> Option<Modular> {
let (g, x, _) = bigint_egcd(&self.value, &self.modulus);
if g != BigInt::from_i64(1) {
return None;
}
Modular::new(x, self.modulus.clone())
}
pub fn div(&self, other: &Modular) -> Option<Modular> {
if self.modulus != other.modulus {
return None;
}
self.mul(&other.inverse()?)
}
}
impl fmt::Display for Modular {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (mod {})", self.value, self.modulus)
}
}
impl fmt::Debug for Modular {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Modular({} mod {})", self.value, self.modulus)
}
}
pub const NUMERIC_HASH_P: u64 = (1u64 << 61) - 1;
fn decompose_f64(f: f64) -> Option<(bool, u64, i32)> {
if !f.is_finite() {
return None;
}
let bits = f.to_bits();
let neg = bits >> 63 == 1;
let biased = ((bits >> 52) & 0x7ff) as i32;
let frac = bits & ((1u64 << 52) - 1);
let (m, e) = if biased == 0 {
(frac, -1074) } else {
(frac | (1u64 << 52), biased - 1075)
};
Some((neg, m, e))
}
pub fn rational_from_f64_exact(f: f64) -> Option<Rational> {
let (neg, m, e) = decompose_f64(f)?;
let mag = BigInt::from_u64(m);
let two = BigInt::from_u64(2);
let (num, den) = if e >= 0 {
(mag.mul(&two.pow(e as u32)), BigInt::from_i64(1))
} else {
(mag, two.pow((-e) as u32))
};
let num = if neg { num.negated() } else { num };
Rational::new(num, den)
}
pub fn cmp_i64_f64_exact(i: i64, f: f64) -> Option<Ordering> {
if f.is_nan() {
return None;
}
if f == f64::INFINITY {
return Some(Ordering::Less);
}
if f == f64::NEG_INFINITY {
return Some(Ordering::Greater);
}
if i.unsigned_abs() <= (1u64 << 53) {
return (i as f64).partial_cmp(&f);
}
let fr = rational_from_f64_exact(f).expect("finite f64 is an exact rational");
Some(Rational::from_i64(i).cmp(&fr))
}
pub fn cmp_bigint_f64_exact(b: &BigInt, f: f64) -> Option<Ordering> {
if f.is_nan() {
return None;
}
if f == f64::INFINITY {
return Some(Ordering::Less);
}
if f == f64::NEG_INFINITY {
return Some(Ordering::Greater);
}
let fr = rational_from_f64_exact(f).expect("finite f64 is an exact rational");
Some(Rational::from_bigint(b.clone()).cmp(&fr))
}
pub fn cmp_rational_f64_exact(r: &Rational, f: f64) -> Option<Ordering> {
if f.is_nan() {
return None;
}
if f == f64::INFINITY {
return Some(Ordering::Less);
}
if f == f64::NEG_INFINITY {
return Some(Ordering::Greater);
}
let fr = rational_from_f64_exact(f).expect("finite f64 is an exact rational");
Some(r.cmp(&fr))
}
fn mod_p(x: u64) -> u64 {
let r = (x & NUMERIC_HASH_P) + (x >> 61);
if r >= NUMERIC_HASH_P { r - NUMERIC_HASH_P } else { r }
}
fn mul_mod_p(a: u64, b: u64) -> u64 {
((a as u128 * b as u128) % (NUMERIC_HASH_P as u128)) as u64
}
fn pow_mod_p(mut base: u64, mut exp: u64) -> u64 {
let mut acc = 1u64;
base %= NUMERIC_HASH_P;
while exp > 0 {
if exp & 1 == 1 {
acc = mul_mod_p(acc, base);
}
base = mul_mod_p(base, base);
exp >>= 1;
}
acc
}
fn signed_hash(neg: bool, h: u64) -> u64 {
if neg && h != 0 { NUMERIC_HASH_P - h } else { h }
}
pub fn numeric_hash_i64(n: i64) -> u64 {
signed_hash(n < 0, mod_p(n.unsigned_abs()))
}
pub fn numeric_hash_bigint(b: &BigInt) -> u64 {
let mut acc: u64 = 0;
for &limb in b.mag.limbs().iter().rev() {
let t = (acc as u128) * 8 + (mod_p(limb) as u128);
acc = (t % (NUMERIC_HASH_P as u128)) as u64;
}
signed_hash(b.is_negative(), acc)
}
pub fn numeric_hash_f64(f: f64) -> u64 {
if f.is_nan() {
return 0x6e616e; }
if f == f64::INFINITY {
return 314159;
}
if f == f64::NEG_INFINITY {
return NUMERIC_HASH_P - 314159;
}
let (neg, m, e) = decompose_f64(f).expect("finite");
let h = mul_mod_p(mod_p(m), pow_mod_p(2, e.rem_euclid(61) as u64));
signed_hash(neg, h)
}
pub fn numeric_hash_rational(r: &Rational) -> u64 {
let num = numeric_hash_bigint(&r.numerator().abs());
let den = numeric_hash_bigint(&r.denominator().abs());
let inv = pow_mod_p(den, NUMERIC_HASH_P - 2);
signed_hash(r.is_negative(), mul_mod_p(num, inv))
}
#[cfg(test)]
mod tests {
use super::*;
fn b(x: i64) -> BigInt {
BigInt::from_i64(x)
}
#[test]
fn from_to_i64_round_trips_the_extremes() {
for x in [0i64, 1, -1, 42, -42, i64::MAX, i64::MIN, i64::MAX - 1, i64::MIN + 1] {
assert_eq!(BigInt::from_i64(x).to_i64(), Some(x), "round trip {x}");
}
}
#[test]
fn to_i64_is_none_just_past_the_boundary() {
let over = b(i64::MAX).add(&b(1));
let under = b(i64::MIN).sub(&b(1));
assert_eq!(over.to_i64(), None);
assert_eq!(under.to_i64(), None);
assert_eq!(over.to_string(), "9223372036854775808");
assert_eq!(under.to_string(), "-9223372036854775809");
}
#[test]
fn add_sub_mul_match_i128_on_a_dense_grid() {
let xs: [i64; 11] =
[0, 1, -1, 2, -2, 1000, -1000, i32::MAX as i64, i32::MIN as i64, i64::MAX, i64::MIN];
for &x in &xs {
for &y in &xs {
let (bx, by) = (b(x), b(y));
assert_eq!(bx.add(&by).to_string(), (x as i128 + y as i128).to_string(), "{x}+{y}");
assert_eq!(bx.sub(&by).to_string(), (x as i128 - y as i128).to_string(), "{x}-{y}");
assert_eq!(bx.mul(&by).to_string(), (x as i128 * y as i128).to_string(), "{x}*{y}");
}
}
}
#[test]
fn div_rem_matches_i64_truncation_including_signs() {
let xs = [0i64, 1, -1, 7, -7, 100, -100, 9_999_999, -9_999_999, i64::MAX, i64::MIN];
let ys = [1i64, -1, 2, -2, 3, -3, 7, -7, 1000, -1000];
for &x in &xs {
for &y in &ys {
let (q, r) = b(x).div_rem(&b(y)).expect("nonzero divisor");
let (eq, er) = ((x as i128) / (y as i128), (x as i128) % (y as i128));
assert_eq!(q.to_string(), eq.to_string(), "{x}/{y} quotient");
assert_eq!(r.to_string(), er.to_string(), "{x}%{y} remainder");
assert_eq!(b(x), q.mul(&b(y)).add(&r), "x = q*y + r for {x},{y}");
}
}
}
#[test]
fn division_by_zero_is_none_not_a_panic() {
assert!(b(5).div_rem(&BigInt::zero()).is_none());
assert!(BigInt::zero().div_rem(&BigInt::zero()).is_none());
}
#[test]
fn huge_factorial_is_exact() {
let mut acc = BigInt::from_u64(1);
for k in 1..=50u64 {
acc = acc.mul(&BigInt::from_u64(k));
}
assert_eq!(acc.to_string(), "30414093201713378043612608166064768844377641568960512000000000000");
for k in 1..=50u64 {
let (q, r) = acc.div_rem(&BigInt::from_u64(k)).unwrap();
assert!(r.is_zero(), "{k}! divides cleanly");
acc = q;
}
assert_eq!(acc, BigInt::from_u64(1));
}
#[test]
fn parse_and_display_round_trip_big_decimals() {
for s in [
"0",
"-0",
"7",
"-7",
"9223372036854775808",
"-9223372036854775809",
"123456789012345678901234567890",
"-100000000000000000000000000000000000000000",
] {
let parsed = BigInt::parse_decimal(s).expect("parse");
let expected = if s == "-0" { "0" } else { s };
assert_eq!(parsed.to_string(), expected, "round trip {s}");
}
assert!(BigInt::parse_decimal("12x3").is_none());
assert!(BigInt::parse_decimal("").is_none());
assert!(BigInt::parse_decimal("-").is_none());
}
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
self.0 = self.0.wrapping_add(0x9E37_79B9_7F4A_7C15);
let mut z = self.0;
z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
z ^ (z >> 31)
}
fn big(&mut self) -> BigInt {
let limbs = (self.next() % 4) as usize;
let mut bytes = Vec::new();
for _ in 0..limbs {
bytes.extend_from_slice(&self.next().to_le_bytes());
}
BigInt::from_le_bytes(self.next() & 1 == 1, &bytes)
}
}
#[test]
fn fuzz_algebraic_laws_hold_for_random_bigints() {
let mut r = Rng(0x0BAD_F00D_DEAD_BEEF);
for _ in 0..3000 {
let (a, b, c) = (r.big(), r.big(), r.big());
assert_eq!(a.add(&b), b.add(&a), "add commutes");
assert_eq!(a.mul(&b), b.mul(&a), "mul commutes");
assert_eq!(a.add(&b).add(&c), a.add(&b.add(&c)), "add associates");
assert_eq!(a.mul(&b).mul(&c), a.mul(&b.mul(&c)), "mul associates");
assert_eq!(a.mul(&b.add(&c)), a.mul(&b).add(&a.mul(&c)), "mul distributes over add");
assert_eq!(a.add(&BigInt::zero()), a, "0 is the additive identity");
assert_eq!(a.mul(&BigInt::from_u64(1)), a, "1 is the multiplicative identity");
assert!(a.mul(&BigInt::zero()).is_zero(), "x*0 = 0");
assert_eq!(a.sub(&a), BigInt::zero(), "x - x = 0");
assert_eq!(a.add(&a.negated()), BigInt::zero(), "x + (-x) = 0");
assert_eq!(a.negated().negated(), a, "double negation");
assert!(!a.abs().is_negative(), "|x| >= 0");
assert_eq!(a.negated().mul(&b), a.mul(&b).negated(), "(-a)*b = -(a*b)");
if !b.is_zero() {
let (q, rem) = a.div_rem(&b).unwrap();
assert_eq!(q.mul(&b).add(&rem), a, "a = q*b + r exactly");
assert!(rem.is_zero() || rem.abs() < b.abs(), "|r| < |b|");
}
let (neg, bytes) = a.to_le_bytes();
assert_eq!(BigInt::from_le_bytes(neg, &bytes), a, "byte round-trip");
assert_eq!(BigInt::parse_decimal(&a.to_string()).unwrap(), a, "decimal round-trip");
assert_eq!(a < b, b > a, "antisymmetry");
if a < b && b < c {
assert!(a < c, "transitivity");
}
}
}
#[test]
fn fuzz_differential_against_i128() {
let mut r = Rng(0xC0FF_EE00_1234_5678);
for _ in 0..5000 {
let x = r.next() as i64;
let y = r.next() as i64;
assert_eq!(b(x).add(&b(y)).to_string(), (x as i128 + y as i128).to_string(), "{x}+{y}");
assert_eq!(b(x).sub(&b(y)).to_string(), (x as i128 - y as i128).to_string(), "{x}-{y}");
assert_eq!(b(x).mul(&b(y)).to_string(), (x as i128 * y as i128).to_string(), "{x}*{y}");
if y != 0 {
let (q, rem) = b(x).div_rem(&b(y)).unwrap();
assert_eq!(q.to_string(), (x as i128 / y as i128).to_string(), "{x}/{y}");
assert_eq!(rem.to_string(), (x as i128 % y as i128).to_string(), "{x}%{y}");
}
}
}
#[test]
fn limb_boundary_edge_cases_are_exact() {
let two64 = BigInt::parse_decimal("18446744073709551616").unwrap(); assert_eq!(two64.sub(&BigInt::from_u64(1)).to_string(), "18446744073709551615");
let two128 = two64.mul(&two64);
assert_eq!(two128.to_string(), "340282366920938463463374607431768211456");
assert_eq!(two128.sub(&BigInt::from_u64(1)).to_string(), "340282366920938463463374607431768211455");
assert_eq!(BigInt::from_u64(u64::MAX).add(&BigInt::from_u64(1)), two64);
let (q, rem) = two128.div_rem(&two64).unwrap();
assert_eq!(q, two64);
assert!(rem.is_zero());
}
#[test]
fn pow_is_exact_and_unbounded() {
assert_eq!(b(2).pow(63).to_string(), "9223372036854775808"); assert_eq!(b(2).pow(100).to_string(), "1267650600228229401496703205376");
assert_eq!(b(10).pow(0).to_string(), "1");
assert_eq!(b(0).pow(0).to_string(), "1");
assert_eq!(b(-3).pow(3).to_string(), "-27");
assert_eq!(b(-2).pow(10).to_string(), "1024");
let mut acc = BigInt::from_u64(1);
for _ in 0..50 {
acc = acc.mul(&b(3));
}
assert_eq!(b(3).pow(50), acc);
}
#[test]
fn ordering_is_total_and_sign_aware() {
let mut v = vec![b(3), b(-5), BigInt::zero(), b(i64::MAX), b(i64::MIN), b(-5).mul(&b(i64::MAX))];
v.sort();
let as_str: Vec<String> = v.iter().map(|x| x.to_string()).collect();
assert_eq!(
as_str,
vec![
"-46116860184273879035", "-9223372036854775808", "-5",
"0",
"3",
"9223372036854775807", ]
);
}
fn r(n: i64, d: i64) -> Rational {
Rational::from_ratio_i64(n, d).expect("nonzero denominator in test")
}
#[test]
fn rational_reduces_to_lowest_terms_on_construction() {
assert_eq!(r(6, 8).to_string(), "3/4");
assert_eq!(r(10, 5).to_string(), "2");
assert_eq!(r(0, 7).to_string(), "0");
assert_eq!(r(100, 1000).to_string(), "1/10");
assert_eq!(r(6, 8), r(3, 4));
assert_eq!(r(0, 7), r(0, 1));
}
#[test]
fn rational_normalizes_sign_onto_a_positive_denominator() {
assert_eq!(r(1, -2).to_string(), "-1/2");
assert_eq!(r(-1, -2).to_string(), "1/2");
assert_eq!(r(-3, 4), r(3, -4));
assert!(r(1, -2).is_negative());
assert!(!r(-1, -2).is_negative());
assert!(!r(1, -2).denominator().is_negative());
}
#[test]
fn rational_zero_denominator_is_none() {
assert!(Rational::from_ratio_i64(5, 0).is_none());
assert!(Rational::new(BigInt::from_i64(1), BigInt::zero()).is_none());
assert!(r(3, 4).div(&Rational::zero()).is_none());
assert!(Rational::zero().recip().is_none());
assert!(Rational::parse("7/0").is_none());
}
#[test]
fn rational_arithmetic_is_exact() {
assert_eq!(r(1, 3).add(&r(1, 6)).to_string(), "1/2");
assert_eq!(r(1, 2).sub(&r(1, 3)).to_string(), "1/6");
assert_eq!(r(2, 3).mul(&r(3, 4)).to_string(), "1/2");
assert_eq!(r(1, 2).div(&r(3, 4)).unwrap().to_string(), "2/3");
assert!(r(5, 7).add(&r(5, 7).negated()).is_zero());
assert_eq!(r(5, 7).mul(&r(5, 7).recip().unwrap()), Rational::one());
}
#[test]
fn one_third_stays_exact_where_a_json_double_would_round() {
let third = r(1, 3);
let sum = third.add(&third).add(&third);
assert_eq!(sum, Rational::one());
assert_eq!(third.to_string(), "1/3");
assert_ne!(0.1_f64 + 0.2, 0.3);
assert_eq!(r(1, 10).add(&r(2, 10)), r(3, 10));
}
#[test]
fn rational_ordering_cross_multiplies_without_rounding() {
assert!(r(1, 3) < r(1, 2));
assert!(r(-1, 2) < r(1, 3));
assert!(r(2, 4) == r(1, 2));
let mut v = vec![r(1, 2), r(1, 3), r(-1, 4), r(2, 3), Rational::zero(), r(1, 2)];
v.sort();
let s: Vec<String> = v.iter().map(|x| x.to_string()).collect();
assert_eq!(s, ["-1/4", "0", "1/3", "1/2", "1/2", "2/3"]);
}
#[test]
fn rational_terms_overflow_i64_into_bigint() {
let big = Rational::new(BigInt::from_i64(1), BigInt::from_i64(i64::MAX)).unwrap();
let twice = big.add(&big);
assert_eq!(twice.numerator().to_string(), "2");
assert_eq!(twice.denominator().to_string(), i64::MAX.to_string());
let p = r(i64::MAX, 1).mul(&r(3, 1));
assert_eq!(p.numerator().to_i64(), None);
assert_eq!(p.numerator().to_string(), (i64::MAX as i128 * 3).to_string());
}
#[test]
fn rational_pow_handles_negative_and_zero_exponents() {
assert_eq!(r(2, 3).pow(3).unwrap().to_string(), "8/27");
assert_eq!(r(2, 3).pow(0).unwrap(), Rational::one());
assert_eq!(r(2, 3).pow(-2).unwrap().to_string(), "9/4");
assert_eq!(r(-2, 3).pow(-3).unwrap().to_string(), "-27/8");
assert!(Rational::zero().pow(-1).is_none());
assert_eq!(Rational::zero().pow(3).unwrap(), Rational::zero());
}
#[test]
fn rational_parse_round_trips_and_rejects_garbage() {
assert_eq!(Rational::parse("3/4").unwrap().to_string(), "3/4");
assert_eq!(Rational::parse("-3/4").unwrap().to_string(), "-3/4");
assert_eq!(Rational::parse("6/8").unwrap().to_string(), "3/4");
assert_eq!(Rational::parse("5").unwrap().to_string(), "5");
assert_eq!(Rational::parse(" 7 / 14 ").unwrap().to_string(), "1/2");
assert!(Rational::parse("abc").is_none());
assert!(Rational::parse("1/2/3").is_none());
}
#[test]
fn rational_integer_predicate_and_narrowing() {
assert!(r(10, 2).is_integer());
assert_eq!(r(10, 2).to_i64(), Some(5));
assert_eq!(r(10, 2).to_bigint().unwrap().to_string(), "5");
assert!(!r(3, 4).is_integer());
assert_eq!(r(3, 4).to_i64(), None);
assert!(r(3, 4).to_bigint().is_none());
}
#[test]
fn rational_floor_and_ceil_round_toward_neg_and_pos_infinity() {
assert_eq!(r(7, 2).floor().to_string(), "3");
assert_eq!(r(7, 2).ceil().to_string(), "4");
assert_eq!(r(-7, 2).floor().to_string(), "-4");
assert_eq!(r(-7, 2).ceil().to_string(), "-3");
assert_eq!(r(6, 2).floor().to_string(), "3");
assert_eq!(r(6, 2).ceil().to_string(), "3");
assert_eq!(r(7, 2).round().to_string(), "4");
assert_eq!(r(-7, 2).round().to_string(), "-4");
assert_eq!(r(1, 3).round().to_string(), "0");
assert_eq!(r(2, 3).round().to_string(), "1");
for n in -9i64..=9 {
for d in 1i64..=9 {
let q = r(n, d);
assert_eq!(q.floor().to_i64(), Some((n as f64 / d as f64).floor() as i64), "{n}/{d} floor");
assert_eq!(q.ceil().to_i64(), Some((n as f64 / d as f64).ceil() as i64), "{n}/{d} ceil");
assert_eq!(q.round().to_i64(), Some((n as f64 / d as f64).round() as i64), "{n}/{d} round");
}
}
}
#[test]
fn rational_to_f64_matches_the_division_on_small_terms() {
for n in -8i64..=8 {
for d in 1i64..=8 {
let approx = r(n, d).to_f64();
assert!((approx - (n as f64 / d as f64)).abs() < 1e-12, "{n}/{d}");
}
}
}
#[test]
fn rational_obeys_the_field_laws_over_random_fractions() {
let mut rng = Rng(0x5A7F_104E_2C19_8B63);
for _ in 0..2000 {
let pick = |rng: &mut Rng| -> i64 { (rng.next() % 4001) as i64 - 2000 };
let (a, b, c, d, e, g) = (pick(&mut rng), pick(&mut rng), pick(&mut rng), pick(&mut rng), pick(&mut rng), pick(&mut rng));
let (Some(x), Some(y), Some(z)) =
(Rational::from_ratio_i64(a, b.max(1)), Rational::from_ratio_i64(c, d.max(1)), Rational::from_ratio_i64(e, g.max(1)))
else { continue };
assert_eq!(x.add(&y), y.add(&x));
assert_eq!(x.mul(&y), y.mul(&x));
assert_eq!(x.add(&y).add(&z), x.add(&y.add(&z)));
assert_eq!(x.mul(&y).mul(&z), x.mul(&y.mul(&z)));
assert_eq!(x.mul(&y.add(&z)), x.mul(&y).add(&x.mul(&z)));
assert!(x.add(&x.negated()).is_zero());
assert_eq!(x.sub(&y), x.add(&y.negated()));
if !x.is_zero() {
assert_eq!(x.mul(&x.recip().unwrap()), Rational::one());
assert_eq!(y.div(&x).unwrap(), y.mul(&x.recip().unwrap()));
}
}
}
fn dec(s: &str) -> Decimal {
Decimal::parse(s).unwrap_or_else(|| panic!("parse decimal {s:?}"))
}
#[test]
fn decimal_parse_and_display_round_trip() {
for s in ["0", "19.99", "0.05", "-0.005", "1999", "100.00", "-7", "0.10", "3.14159"] {
assert_eq!(dec(s).to_string(), s, "round trip {s}");
}
assert_eq!(dec("-0").to_string(), "0");
assert_eq!(dec("+5").to_string(), "5");
assert_eq!(dec(".5").to_string(), "0.5");
assert_eq!(dec("5.").to_string(), "5");
assert!(Decimal::parse("1.2.3").is_none());
assert!(Decimal::parse("abc").is_none());
assert!(Decimal::parse("").is_none());
assert!(Decimal::parse("-").is_none());
assert!(Decimal::parse(".").is_none());
}
#[test]
fn decimal_has_no_binary_float_drift() {
assert_ne!(0.1_f64 + 0.2, 0.3);
assert_eq!(dec("0.1").add(&dec("0.2")), dec("0.3"));
assert_eq!(dec("0.1").add(&dec("0.2")).to_string(), "0.3");
}
#[test]
fn decimal_add_sub_align_scales() {
assert_eq!(dec("19.99").add(&dec("0.01")).to_string(), "20.00");
assert_eq!(dec("1.1").add(&dec("2.22")).to_string(), "3.32");
assert_eq!(dec("5").add(&dec("0.5")).to_string(), "5.5");
assert_eq!(dec("20.00").sub(&dec("0.01")).to_string(), "19.99");
assert_eq!(dec("0").sub(&dec("0.005")).to_string(), "-0.005");
}
#[test]
fn decimal_mul_adds_scales_and_is_exact() {
assert_eq!(dec("1.1").mul(&dec("1.1")).to_string(), "1.21");
assert_eq!(dec("0.05").mul(&dec("0.05")).to_string(), "0.0025");
assert_eq!(dec("19.99").mul(&Decimal::from_i64(3)).to_string(), "59.97");
assert_eq!(dec("-2.5").mul(&dec("4")).to_string(), "-10.0");
}
#[test]
fn decimal_div_rounds_to_scale() {
assert_eq!(dec("10").div(&dec("3"), 2, RoundingMode::HalfEven).unwrap().to_string(), "3.33");
assert_eq!(dec("1").div(&dec("8"), 3, RoundingMode::HalfEven).unwrap().to_string(), "0.125");
assert_eq!(dec("1").div(&dec("8"), 2, RoundingMode::HalfEven).unwrap().to_string(), "0.12");
assert_eq!(dec("1").div(&dec("8"), 2, RoundingMode::HalfUp).unwrap().to_string(), "0.13");
assert!(dec("1").div(&dec("0"), 2, RoundingMode::HalfEven).is_none());
}
#[test]
fn decimal_rescale_quantizes_with_rounding() {
assert_eq!(dec("19.999").rescale(2, RoundingMode::HalfEven).to_string(), "20.00");
assert_eq!(dec("2.5").rescale(0, RoundingMode::HalfEven).to_string(), "2");
assert_eq!(dec("3.5").rescale(0, RoundingMode::HalfEven).to_string(), "4");
assert_eq!(dec("-2.5").rescale(0, RoundingMode::HalfEven).to_string(), "-2");
assert_eq!(dec("1.5").rescale(4, RoundingMode::HalfEven).to_string(), "1.5000");
}
#[test]
fn decimal_rounding_modes_on_a_tie_and_a_non_tie() {
let tie = dec("2.5");
for (mode, want) in [
(RoundingMode::Down, "2"),
(RoundingMode::Up, "3"),
(RoundingMode::Floor, "2"),
(RoundingMode::Ceiling, "3"),
(RoundingMode::HalfUp, "3"),
(RoundingMode::HalfDown, "2"),
(RoundingMode::HalfEven, "2"),
] {
assert_eq!(tie.rescale(0, mode).to_string(), want, "2.5 under {mode:?}");
}
let ntie = dec("-2.5");
assert_eq!(ntie.rescale(0, RoundingMode::Floor).to_string(), "-3");
assert_eq!(ntie.rescale(0, RoundingMode::Ceiling).to_string(), "-2");
assert_eq!(ntie.rescale(0, RoundingMode::HalfUp).to_string(), "-3");
assert_eq!(ntie.rescale(0, RoundingMode::Down).to_string(), "-2");
}
#[test]
fn decimal_equality_is_value_based_and_hash_agrees() {
use std::collections::HashSet;
assert_eq!(dec("1.0"), dec("1.00"));
assert_eq!(dec("1.0"), dec("1"));
assert_eq!(dec("0.0"), dec("0"));
let mut set = HashSet::new();
set.insert(dec("1.00"));
assert!(set.contains(&dec("1")));
assert!(set.contains(&dec("1.0")));
assert_ne!(dec("1.0"), dec("1.01"));
}
#[test]
fn decimal_ordering_compares_across_scales_without_rounding() {
assert!(dec("0.1") < dec("0.11"));
assert!(dec("1.5") < dec("2"));
assert!(dec("-0.005") < dec("0"));
assert!(dec("1.00") == dec("1.0"));
let mut v = vec![dec("0.5"), dec("0.05"), dec("-0.1"), dec("2"), dec("0.50")];
v.sort();
let s: Vec<String> = v.iter().map(|x| x.to_string()).collect();
assert_eq!(s, ["-0.1", "0.05", "0.5", "0.50", "2"]);
}
#[test]
fn decimal_to_and_from_rational_is_exact() {
assert_eq!(dec("19.99").to_rational(), Rational::from_ratio_i64(1999, 100).unwrap());
assert_eq!(dec("0.125").to_rational(), Rational::from_ratio_i64(1, 8).unwrap());
assert_eq!(dec("-0.005").to_rational(), Rational::from_ratio_i64(-1, 200).unwrap());
let third = Rational::from_ratio_i64(1, 3).unwrap();
assert_eq!(Decimal::from_rational(&third, 4, RoundingMode::HalfEven).to_string(), "0.3333");
}
#[test]
fn decimal_wire_components_round_trip() {
for s in ["0", "19.99", "-0.005", "100.00", "123456789.000001", "-7"] {
let d = dec(s);
let (neg, bytes, scale) = d.to_le_bytes();
let back = Decimal::from_le_bytes(neg, &bytes, scale);
assert_eq!(back, d, "value round-trip {s}");
assert_eq!(back.to_string(), d.to_string(), "display round-trip {s}");
}
}
#[test]
fn decimal_money_scenario_is_exact() {
let subtotal = dec("19.99").mul(&Decimal::from_i64(3)); assert_eq!(subtotal.to_string(), "59.97");
let tax = subtotal.mul(&dec("0.08")).rescale(2, RoundingMode::HalfEven); assert_eq!(tax.to_string(), "4.80");
assert_eq!(subtotal.add(&tax).to_string(), "64.77");
}
#[test]
fn decimal_add_sub_mul_match_the_rational_oracle_under_fuzz() {
let mut rng = Rng(0xDEC1_2A1B_0000_FACE);
for _ in 0..3000 {
let ca = (rng.next() % 2_000_001) as i64 - 1_000_000;
let cb = (rng.next() % 2_000_001) as i64 - 1_000_000;
let sa = (rng.next() % 6) as u32;
let sb = (rng.next() % 6) as u32;
let a = Decimal::from_coeff_scale(BigInt::from_i64(ca), sa);
let b = Decimal::from_coeff_scale(BigInt::from_i64(cb), sb);
let (ra, rb) = (a.to_rational(), b.to_rational());
assert_eq!(a.add(&b).to_rational(), ra.add(&rb), "add {a} {b}");
assert_eq!(a.sub(&b).to_rational(), ra.sub(&rb), "sub {a} {b}");
assert_eq!(a.mul(&b).to_rational(), ra.mul(&rb), "mul {a} {b}");
if !b.is_zero() {
let scale = 8u32;
let q = a.div(&b, scale, RoundingMode::HalfEven).unwrap();
let exact = ra.div(&rb).unwrap();
let ulp = Rational::new(BigInt::from_i64(1), BigInt::from_u64(10).pow(scale)).unwrap();
let err = q.to_rational().sub(&exact).abs();
assert!(err.mul(&Rational::from_i64(2)) <= ulp, "div within half-ulp {a}/{b}");
}
}
}
fn c(re: i64, im: i64) -> Complex {
Complex::new(Rational::from_i64(re), Rational::from_i64(im))
}
fn cq(rn: i64, rd: i64, in_: i64, id: i64) -> Complex {
Complex::new(Rational::from_ratio_i64(rn, rd).unwrap(), Rational::from_ratio_i64(in_, id).unwrap())
}
#[test]
fn complex_i_squared_is_minus_one() {
assert_eq!(Complex::i().mul(&Complex::i()), c(-1, 0));
assert_eq!(Complex::i().mul(&Complex::i()), Complex::from_i64(-1));
}
#[test]
fn complex_construction_and_accessors() {
let z = c(3, 4);
assert_eq!(z.re(), &Rational::from_i64(3));
assert_eq!(z.im(), &Rational::from_i64(4));
assert!(!z.is_real());
assert!(c(5, 0).is_real());
assert!(Complex::zero().is_zero());
assert!(!Complex::i().is_zero());
assert_eq!(Complex::from_i64(7), c(7, 0));
}
#[test]
fn complex_add_sub_mul_are_exact() {
assert_eq!(c(2, 3).add(&c(1, -1)), c(3, 2));
assert_eq!(c(2, 3).sub(&c(1, -1)), c(1, 4));
assert_eq!(c(1, 1).mul(&c(1, -1)), c(2, 0));
assert_eq!(c(2, 3).mul(&c(4, 5)), c(-7, 22));
}
#[test]
fn complex_div_recip_and_zero_guard() {
assert_eq!(c(2, 3).div(&c(2, 3)).unwrap(), Complex::one());
assert_eq!(Complex::i().recip().unwrap(), c(0, -1));
assert_eq!(c(3, 4).div(&c(1, 2)).unwrap(), cq(11, 5, -2, 5));
assert!(c(1, 1).div(&Complex::zero()).is_none());
assert!(Complex::zero().recip().is_none());
}
#[test]
fn complex_conjugate_and_norm_are_exact() {
assert_eq!(c(3, 4).conjugate(), c(3, -4));
assert_eq!(c(3, 4).norm_sqr(), Rational::from_i64(25));
assert!((c(3, 4).abs_f64() - 5.0).abs() < 1e-12);
let z = c(2, -5);
assert_eq!(z.mul(&z.conjugate()), Complex::from_rational(z.norm_sqr()));
}
#[test]
fn complex_pow_handles_the_cycle_and_negatives() {
assert_eq!(Complex::i().pow(2).unwrap(), c(-1, 0));
assert_eq!(Complex::i().pow(3).unwrap(), c(0, -1));
assert_eq!(Complex::i().pow(4).unwrap(), c(1, 0));
assert_eq!(Complex::i().pow(0).unwrap(), Complex::one());
assert_eq!(Complex::i().pow(-1).unwrap(), c(0, -1)); assert_eq!(c(1, 1).pow(2).unwrap(), c(0, 2));
assert_eq!(Complex::zero().pow(0).unwrap(), Complex::one());
assert!(Complex::zero().pow(-1).is_none());
}
#[test]
fn complex_display_round_trips_every_form() {
for (z, want) in [
(c(0, 0), "0"),
(c(3, 0), "3"),
(c(0, 1), "i"),
(c(0, -1), "-i"),
(c(0, 4), "4i"),
(c(0, -4), "-4i"),
(c(3, 4), "3+4i"),
(c(3, -4), "3-4i"),
(c(3, 1), "3+i"),
(c(3, -1), "3-i"),
(c(-2, 5), "-2+5i"),
] {
assert_eq!(z.to_string(), want, "display");
assert_eq!(Complex::parse(want).unwrap(), z, "parse round-trip of {want}");
}
let z = cq(1, 2, -3, 4);
assert_eq!(z.to_string(), "1/2-3/4i");
assert_eq!(Complex::parse("1/2-3/4i").unwrap(), z);
assert!(Complex::parse("").is_none());
assert!(Complex::parse("3+xi").is_none());
}
#[test]
fn complex_equality_and_hash_are_structural() {
use std::collections::HashSet;
assert_eq!(c(3, 4), c(3, 4));
assert_ne!(c(3, 4), c(3, -4));
assert_eq!(cq(2, 4, 6, 8), cq(1, 2, 3, 4)); let mut set = HashSet::new();
set.insert(cq(2, 4, 6, 8));
assert!(set.contains(&cq(1, 2, 3, 4)));
}
#[test]
fn complex_obeys_the_field_laws_under_fuzz() {
let mut rng = Rng(0xC011_9EE5_A11C_E5ED);
let pick = |rng: &mut Rng| -> Rational {
let n = (rng.next() % 41) as i64 - 20;
let d = ((rng.next() % 9) as i64) + 1;
Rational::from_ratio_i64(n, d).unwrap()
};
let pick_c = |rng: &mut Rng| -> Complex { Complex::new(pick(rng), pick(rng)) };
for _ in 0..2000 {
let (x, y, z) = (pick_c(&mut rng), pick_c(&mut rng), pick_c(&mut rng));
assert_eq!(x.add(&y), y.add(&x));
assert_eq!(x.mul(&y), y.mul(&x));
assert_eq!(x.add(&y).add(&z), x.add(&y.add(&z)));
assert_eq!(x.mul(&y).mul(&z), x.mul(&y.mul(&z)));
assert_eq!(x.mul(&y.add(&z)), x.mul(&y).add(&x.mul(&z)));
assert!(x.add(&x.negated()).is_zero());
assert_eq!(x.sub(&y), x.add(&y.negated()));
assert_eq!(x.conjugate().conjugate(), x);
assert_eq!(x.mul(&y).conjugate(), x.conjugate().mul(&y.conjugate()));
assert_eq!(x.mul(&y).norm_sqr(), x.norm_sqr().mul(&y.norm_sqr()));
if !x.is_zero() {
assert_eq!(x.mul(&x.recip().unwrap()), Complex::one());
assert_eq!(y.div(&x).unwrap(), y.mul(&x.recip().unwrap()));
}
}
}
fn m(v: i64, n: i64) -> Modular {
Modular::from_i64(v, n).unwrap_or_else(|| panic!("bad modulus {n}"))
}
#[test]
fn modular_reduces_into_range_on_construction() {
assert_eq!(m(10, 7).value().to_i64(), Some(3));
assert_eq!(m(7, 7).value().to_i64(), Some(0));
assert_eq!(m(-1, 7).value().to_i64(), Some(6));
assert_eq!(m(-10, 7).value().to_i64(), Some(4));
assert_eq!(m(5, 1).value().to_i64(), Some(0));
assert!(Modular::from_i64(3, 0).is_none());
assert!(Modular::from_i64(3, -7).is_none());
}
#[test]
fn modular_arithmetic_wraps_in_the_ring() {
assert_eq!(m(5, 7).add(&m(4, 7)).unwrap(), m(2, 7)); assert_eq!(m(3, 7).sub(&m(5, 7)).unwrap(), m(5, 7)); assert_eq!(m(4, 7).mul(&m(5, 7)).unwrap(), m(6, 7)); assert_eq!(m(3, 7).negated(), m(4, 7));
assert!(m(3, 7).add(&m(3, 7).negated()).unwrap().is_zero());
assert!(m(3, 7).add(&m(3, 5)).is_none());
assert!(m(3, 7).mul(&m(3, 5)).is_none());
}
#[test]
fn modular_exponentiation_is_fast_and_exact() {
assert_eq!(m(3, 5).pow(4), m(1, 5)); assert_eq!(m(2, 7).pow(3), m(1, 7)); assert_eq!(m(5, 13).pow(0), m(1, 13)); let mut acc = m(1, 13);
for _ in 0..100 {
acc = acc.mul(&m(7, 13)).unwrap();
}
assert_eq!(m(7, 13).pow(100), acc);
}
#[test]
fn modular_inverse_and_division() {
assert_eq!(m(3, 7).inverse().unwrap(), m(5, 7));
assert_eq!(m(3, 7).mul(&m(3, 7).inverse().unwrap()).unwrap(), m(1, 7));
assert_eq!(m(1, 7).div(&m(3, 7)).unwrap(), m(5, 7));
assert_eq!(m(4, 7).div(&m(2, 7)).unwrap(), m(2, 7));
assert!(m(2, 4).inverse().is_none());
assert!(m(1, 4).div(&m(2, 4)).is_none());
assert!(m(1, 7).div(&m(3, 5)).is_none());
}
#[test]
fn modular_generalizes_the_word_wrapping_ring() {
let modulus = 1i64 << 32; let (a, b) = (4_000_000_000u32, 1_000_000_000u32); let word_add = (crate::Word32(a) + crate::Word32(b)).0 as i64;
let mod_add = m(a as i64, modulus).add(&m(b as i64, modulus)).unwrap();
assert_eq!(mod_add.value().to_i64(), Some(word_add), "ℤ/2³² add == Word32 add");
let word_mul = (crate::Word32(a) * crate::Word32(b)).0 as i64;
let mod_mul = m(a as i64, modulus).mul(&m(b as i64, modulus)).unwrap();
assert_eq!(mod_mul.value().to_i64(), Some(word_mul), "ℤ/2³² mul == Word32 mul");
}
#[test]
fn modular_equality_is_per_ring_and_displays_the_modulus() {
assert_ne!(m(3, 7), m(3, 5));
assert_eq!(m(10, 7), m(3, 7));
assert_eq!(m(3, 7).to_string(), "3 (mod 7)");
assert_eq!(m(-1, 7).to_string(), "6 (mod 7)");
}
#[test]
fn modular_obeys_fermats_little_theorem() {
for &p in &[2i64, 3, 5, 7, 13, 97, 101] {
for a in 1..p.min(40) {
assert_eq!(m(a, p).pow((p - 1) as u64), m(1, p), "{a}^({p}-1) ≡ 1 (mod {p})");
}
}
}
#[test]
fn modular_obeys_the_ring_laws_and_inverse_under_fuzz() {
let mut rng = Rng(0x_0DDF_ACE5_ABCD_1234);
for _ in 0..3000 {
let n = ((rng.next() % 50) as i64) + 2; let pick = |rng: &mut Rng| (rng.next() % 1_000_000) as i64 - 500_000;
let (a, b, c) = (m(pick(&mut rng), n), m(pick(&mut rng), n), m(pick(&mut rng), n));
assert_eq!(a.add(&b).unwrap(), b.add(&a).unwrap());
assert_eq!(a.mul(&b).unwrap(), b.mul(&a).unwrap());
assert_eq!(a.add(&b).unwrap().add(&c).unwrap(), a.add(&b.add(&c).unwrap()).unwrap());
assert_eq!(a.mul(&b).unwrap().mul(&c).unwrap(), a.mul(&b.mul(&c).unwrap()).unwrap());
assert_eq!(
a.mul(&b.add(&c).unwrap()).unwrap(),
a.mul(&b).unwrap().add(&a.mul(&c).unwrap()).unwrap()
);
assert!(a.add(&a.negated()).unwrap().is_zero());
if let Some(inv) = a.inverse() {
assert_eq!(a.mul(&inv).unwrap(), m(1, n), "a·a⁻¹ = 1 in ℤ/{n}ℤ");
}
}
}
#[test]
fn exact_rational_from_f64_values() {
assert_eq!(rational_from_f64_exact(0.5).unwrap(), Rational::from_ratio_i64(1, 2).unwrap());
assert_eq!(rational_from_f64_exact(3.0).unwrap(), Rational::from_i64(3));
assert_eq!(rational_from_f64_exact(-2.25).unwrap(), Rational::from_ratio_i64(-9, 4).unwrap());
assert_eq!(rational_from_f64_exact(0.0).unwrap(), Rational::zero());
assert_eq!(rational_from_f64_exact(-0.0).unwrap(), Rational::zero());
assert!(rational_from_f64_exact(f64::NAN).is_none());
assert!(rational_from_f64_exact(f64::INFINITY).is_none());
assert_ne!(rational_from_f64_exact(0.1).unwrap(), Rational::from_ratio_i64(1, 10).unwrap());
}
#[test]
fn exact_i64_f64_cmp_at_the_representability_boundary() {
let big = 9_007_199_254_740_993_i64; let f = 9_007_199_254_740_992.0_f64; assert_eq!(cmp_i64_f64_exact(big, f), Some(Ordering::Greater));
assert_eq!(cmp_i64_f64_exact(big - 1, f), Some(Ordering::Equal));
assert_eq!(cmp_i64_f64_exact(3, 3.5), Some(Ordering::Less));
assert_eq!(cmp_i64_f64_exact(4, 3.5), Some(Ordering::Greater));
assert_eq!(cmp_i64_f64_exact(1, 1.0), Some(Ordering::Equal));
assert_eq!(cmp_i64_f64_exact(0, f64::NAN), None);
assert_eq!(cmp_i64_f64_exact(i64::MAX, f64::INFINITY), Some(Ordering::Less));
assert_eq!(cmp_i64_f64_exact(i64::MIN, f64::NEG_INFINITY), Some(Ordering::Greater));
}
#[test]
fn unified_numeric_hash_coherence() {
for k in [0i64, 1, -1, 2, 42, -42, 1_000_000_007, -(1i64 << 53), 1i64 << 53] {
assert_eq!(
numeric_hash_i64(k),
numeric_hash_f64(k as f64),
"int/float hash mismatch at {k}"
);
assert_eq!(
numeric_hash_i64(k),
numeric_hash_bigint(&BigInt::from_i64(k)),
"int/bigint hash mismatch at {k}"
);
assert_eq!(
numeric_hash_i64(k),
numeric_hash_rational(&Rational::from_i64(k)),
"int/rational hash mismatch at {k}"
);
}
assert_eq!(
numeric_hash_f64(0.5),
numeric_hash_rational(&Rational::from_ratio_i64(1, 2).unwrap())
);
assert_eq!(
numeric_hash_f64(-2.25),
numeric_hash_rational(&Rational::from_ratio_i64(-9, 4).unwrap())
);
assert_eq!(numeric_hash_f64(-0.0), numeric_hash_f64(0.0));
assert_eq!(
numeric_hash_f64(0.1),
numeric_hash_rational(&rational_from_f64_exact(0.1).unwrap())
);
}
#[test]
fn arithmetic_is_exact_across_the_one_limb_boundary() {
let u64max = BigInt::from_u64(u64::MAX);
let two64 = u64max.add(&b(1)); assert_eq!(two64.to_string(), "18446744073709551616");
assert_eq!(two64.sub(&b(1)), u64max);
let two32 = b(1i64 << 32);
assert_eq!(two32.mul(&two32), two64);
assert_eq!(b(1i64 << 62).mul(&b(4)), two64);
let (q, r) = two64.div_rem(&b(2)).expect("nonzero divisor");
assert!(r.is_zero());
assert_eq!(q.to_string(), "9223372036854775808");
let neg = two64.negated();
assert_eq!(neg.add(&two64), BigInt::zero());
assert_eq!(neg.to_string(), "-18446744073709551616");
}
#[test]
fn ordering_and_hash_are_canonical_across_the_limb_boundary() {
let two64 = BigInt::from_u64(u64::MAX).add(&b(1));
let big = two64.mul(&two64); assert!(b(1) < two64);
assert!(two64 < big);
assert!(big.negated() < b(-1));
assert!(two64.negated() < two64);
let via_mul = b(1i64 << 32).mul(&b(1i64 << 32));
let via_add = BigInt::from_u64(u64::MAX).add(&b(1));
assert_eq!(via_mul, via_add);
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let h = |v: &BigInt| {
let mut s = DefaultHasher::new();
v.hash(&mut s);
s.finish()
};
assert_eq!(h(&via_mul), h(&via_add));
let shrunk = two64.sub(&b(1));
assert_eq!(shrunk, BigInt::from_u64(u64::MAX));
assert_eq!(h(&shrunk), h(&BigInt::from_u64(u64::MAX)));
}
#[test]
fn le_bytes_round_trip_across_the_limb_boundary() {
for s in [
"0",
"1",
"-1",
"18446744073709551615",
"18446744073709551616",
"-18446744073709551616",
"340282366920938463463374607431768211456",
] {
let v = BigInt::parse_decimal(s).expect("valid decimal");
let (neg, bytes) = v.to_le_bytes();
assert_eq!(BigInt::from_le_bytes(neg, &bytes), v, "round trip {s}");
assert_eq!(v.to_string(), s, "display {s}");
}
}
#[test]
fn div_rem_identity_holds_for_multi_limb_values() {
let a = b(1_000_003).pow(7); for dividend in [a.clone(), a.negated()] {
for divisor in [b(97), b(-97), b(1i64 << 40), a.sub(&b(1))] {
let (q, r) = dividend.div_rem(&divisor).expect("nonzero divisor");
assert_eq!(dividend, q.mul(&divisor).add(&r));
assert!(r.abs() < divisor.abs());
assert!(r.is_zero() || r.is_negative() == dividend.is_negative());
}
}
}
}