use crate::linalg::integer::gcd_u128;
use crate::scalar::Scalar;
use std::cmp::Ordering;
use std::fmt;
#[derive(Clone)]
pub struct Rational {
num: i128,
den: i128, }
fn isqrt_exact(n: i128) -> Option<i128> {
if n < 0 {
return None;
}
if n == 0 {
return Some(0);
}
let mut x = (n as f64).sqrt() as i128;
while x > 0 && x.checked_mul(x).is_none_or(|v| v > n) {
x -= 1;
}
while (x + 1).checked_mul(x + 1).is_some_and(|v| v <= n) {
x += 1;
}
if x.checked_mul(x) == Some(n) {
Some(x)
} else {
None
}
}
fn inth_root_exact(n: i128, k: u128) -> Option<i128> {
if k == 0 {
return None;
}
if k == 1 {
return Some(n);
}
if n == 0 {
return Some(0);
}
let neg = n < 0;
if neg && k.is_multiple_of(2) {
return None; }
let a = n.abs();
let k_pow = k.try_into().ok()?;
let pw = |b: i128| -> Option<i128> { b.checked_pow(k_pow) };
let mut x = (a as f64).powf(1.0 / k as f64) as i128;
while x > 0 && pw(x).is_none_or(|v| v > a) {
x -= 1;
}
while pw(x + 1).is_some_and(|v| v <= a) {
x += 1;
}
if pw(x) == Some(a) {
Some(if neg { -x } else { x })
} else {
None
}
}
impl Rational {
pub fn try_new(num: i128, den: i128) -> Option<Self> {
if den == 0 {
return None;
}
let (num, den) = if den < 0 {
(num.checked_neg()?, den.checked_neg()?)
} else {
(num, den)
};
let g = gcd_u128(num.unsigned_abs(), den as u128).max(1);
let g = i128::try_from(g).ok()?;
Some(Rational {
num: num / g,
den: den / g,
})
}
pub fn new(num: i128, den: i128) -> Self {
Self::try_new(num, den).expect("Rational::new received zero denominator or overflowed i128")
}
pub fn sign(&self) -> Ordering {
self.num.cmp(&0)
}
pub fn is_integer(&self) -> bool {
self.den == 1
}
pub fn numer(&self) -> i128 {
self.num
}
pub fn denom(&self) -> i128 {
self.den
}
#[allow(clippy::should_implement_trait)]
pub fn cmp(&self, other: &Self) -> Ordering {
self.sub(other).sign()
}
pub fn floor(&self) -> i128 {
self.num.div_euclid(self.den)
}
pub fn sqrt(&self) -> Option<Rational> {
let sn = isqrt_exact(self.num)?;
let sd = isqrt_exact(self.den)?;
Some(Rational::new(sn, sd))
}
pub fn nth_root(&self, k: u128) -> Option<Rational> {
if k == 0 {
return None;
}
let rn = inth_root_exact(self.num, k)?;
let rd = inth_root_exact(self.den, k)?; Some(Rational::new(rn, rd))
}
pub fn checked_add(&self, rhs: &Self) -> Option<Self> {
let g = gcd_u128(self.den as u128, rhs.den as u128).max(1);
let g = i128::try_from(g).ok()?;
let lhs_scale = rhs.den / g;
let rhs_scale = self.den / g;
let num = self
.num
.checked_mul(lhs_scale)?
.checked_add(rhs.num.checked_mul(rhs_scale)?)?;
let den = self.den.checked_mul(lhs_scale)?;
Rational::try_new(num, den)
}
pub fn checked_mul(&self, rhs: &Self) -> Option<Self> {
let mut lhs_num = self.num;
let mut lhs_den = self.den;
let mut rhs_num = rhs.num;
let mut rhs_den = rhs.den;
let g1 = gcd_u128(lhs_num.unsigned_abs(), rhs_den as u128);
if g1 > 1 {
let g1 = i128::try_from(g1).ok()?;
lhs_num /= g1;
rhs_den /= g1;
}
let g2 = gcd_u128(rhs_num.unsigned_abs(), lhs_den as u128);
if g2 > 1 {
let g2 = i128::try_from(g2).ok()?;
rhs_num /= g2;
lhs_den /= g2;
}
Rational::try_new(lhs_num.checked_mul(rhs_num)?, lhs_den.checked_mul(rhs_den)?)
}
}
impl fmt::Display for Rational {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.den == 1 {
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 {
fmt::Display::fmt(self, f)
}
}
impl PartialEq for Rational {
fn eq(&self, other: &Self) -> bool {
self.num == other.num && self.den == other.den
}
}
impl Eq for Rational {}
impl PartialOrd for Rational {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(std::cmp::Ord::cmp(self, other))
}
}
impl Ord for Rational {
fn cmp(&self, other: &Self) -> Ordering {
Rational::cmp(self, other)
}
}
impl From<i128> for Rational {
fn from(n: i128) -> Self {
Rational::from_int(n)
}
}
impl Scalar for Rational {
fn zero() -> Self {
Rational { num: 0, den: 1 }
}
fn one() -> Self {
Rational { num: 1, den: 1 }
}
fn from_int(n: i128) -> Self {
Rational { num: n, den: 1 }
}
fn add(&self, rhs: &Self) -> Self {
self.checked_add(rhs)
.expect("Rational addition overflowed i128")
}
fn neg(&self) -> Self {
Rational {
num: self
.num
.checked_neg()
.expect("Rational negation overflowed i128"),
den: self.den,
}
}
fn mul(&self, rhs: &Self) -> Self {
self.checked_mul(rhs)
.expect("Rational multiplication overflowed i128")
}
fn characteristic() -> u128 {
0
}
fn inv(&self) -> Option<Self> {
if self.num == 0 {
None
} else {
Some(Rational::new(self.den, self.num))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Scalar;
#[test]
fn rational_arithmetic() {
let half = Rational::new(1, 2);
let third = Rational::new(1, 3);
assert_eq!(half.add(&third), Rational::new(5, 6));
assert_eq!(half.mul(&third), Rational::new(1, 6));
assert_eq!(half.sub(&half), Rational::zero());
assert_eq!(half.add(&half), Rational::one());
assert_eq!(Rational::new(2, 4), Rational::new(1, 2)); }
#[test]
fn standard_order_delegates_to_value_order() {
assert!(Rational::new(1, 3) < Rational::new(1, 2));
assert_eq!(
std::cmp::Ord::cmp(&Rational::new(2, 4), &Rational::new(1, 2)),
Ordering::Equal
);
}
#[test]
fn rational_adds_before_denominator_product_overflows() {
let huge_den = 1i128 << 100;
let x = Rational::new(1, huge_den);
assert_eq!(x.add(&x), Rational::new(1, 1i128 << 99));
}
#[test]
fn checked_add_returns_none_on_overflow() {
let huge = Rational::new(i128::MAX, 1);
assert_eq!(huge.checked_add(&Rational::new(1, 1)), None);
}
#[test]
fn checked_mul_returns_none_on_overflow() {
let huge = Rational::new(i128::MAX, 1);
assert_eq!(huge.checked_mul(&Rational::new(2, 1)), None);
}
#[test]
#[should_panic(expected = "Rational addition overflowed i128")]
fn add_panics_where_checked_add_returns_none() {
let huge = Rational::new(i128::MAX, 1);
let _ = huge.add(&Rational::new(1, 1));
}
#[test]
#[should_panic(expected = "Rational multiplication overflowed i128")]
fn mul_panics_where_checked_mul_returns_none() {
let huge = Rational::new(i128::MAX, 1);
let _ = huge.mul(&Rational::new(2, 1));
}
}