neberu 0.0.0

Exact geometric algebra from the balanced ternary axiom. Governed rewriting, self-certifying canonicalization via the Kase Optimality Theorem.
Documentation
// ============================================================
// RAT — EXACT RATIONAL
// ============================================================
//
// A ratio of two Tern integers, always in lowest terms.
// Denominator always positive (sign is in numerator).
// Derived from Tern. No floating point. No approximation.

// Named arithmetic methods (neg, add, sub, mul, div) are intentional —
// the operator trait impls delegate to them for chained exact arithmetic.
#![allow(clippy::should_implement_trait)]

use crate::tern::Tern;
use crate::trit::{Trit, N};

/// An exact rational number: numerator / denominator, reduced.
/// Invariant: denominator > 0, gcd(|num|, den) = 1.
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
pub struct Rat {
    num: Tern,
    den: Tern,
}

impl Rat {
    /// Create a new rational, always reducing to lowest terms.
    /// Panics if denominator is zero.
    pub fn new(num: Tern, den: Tern) -> Rat {
        assert!(!den.is_zero(), "Rat: zero denominator");
        if num.is_zero() {
            return Rat {
                num: Tern::ZERO,
                den: Tern::ONE,
            };
        }
        let g = Tern::gcd(num, den);
        let (mut n, _) = num.div_rem(g);
        let (mut d, _) = den.div_rem(g);
        // Ensure denominator is positive.
        if d.sign() == N {
            n = n.neg();
            d = d.neg();
        }
        Rat { num: n, den: d }
    }

    pub fn from_i64(n: i64, d: i64) -> Rat {
        Rat::new(Tern::from_i64(n), Tern::from_i64(d))
    }

    pub fn integer(n: i64) -> Rat {
        Rat {
            num: Tern::from_i64(n),
            den: Tern::ONE,
        }
    }

    pub fn zero() -> Rat {
        Rat::integer(0)
    }
    pub fn one() -> Rat {
        Rat::integer(1)
    }
    pub fn neg_one() -> Rat {
        Rat::integer(-1)
    }

    pub fn is_zero(&self) -> bool {
        self.num.is_zero()
    }
    pub fn is_one(&self) -> bool {
        self.num == self.den
    }

    pub fn numerator(&self) -> Tern {
        self.num
    }
    pub fn denominator(&self) -> Tern {
        self.den
    }

    pub fn sign(&self) -> Trit {
        self.num.sign()
    }

    pub fn neg(self) -> Rat {
        Rat {
            num: self.num.neg(),
            den: self.den,
        }
    }
    pub fn abs(self) -> Rat {
        Rat {
            num: self.num.abs(),
            den: self.den,
        }
    }

    pub fn add(self, rhs: Rat) -> Rat {
        let num = self.num.mul(rhs.den).add(rhs.num.mul(self.den));
        let den = self.den.mul(rhs.den);
        Rat::new(num, den)
    }

    pub fn sub(self, rhs: Rat) -> Rat {
        self.add(rhs.neg())
    }

    pub fn mul(self, rhs: Rat) -> Rat {
        Rat::new(self.num.mul(rhs.num), self.den.mul(rhs.den))
    }

    pub fn div(self, rhs: Rat) -> Rat {
        assert!(!rhs.is_zero(), "Rat: division by zero");
        self.mul(rhs.recip())
    }

    pub fn recip(self) -> Rat {
        assert!(!self.is_zero(), "Rat: reciprocal of zero");
        Rat::new(self.den, self.num)
    }

    /// Lossy f64 conversion — display only, never for computation.
    pub fn to_f64(self) -> f64 {
        self.num.to_i64() as f64 / self.den.to_i64() as f64
    }
}

impl PartialOrd for Rat {
    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Rat {
    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
        // a/b vs c/d: compare a*d vs c*b (denominators positive).
        let lhs = self.num.mul(other.den);
        let rhs = other.num.mul(self.den);
        lhs.cmp(&rhs)
    }
}

impl std::fmt::Debug for Rat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.den == Tern::ONE {
            write!(f, "Rat({})", self.num)
        } else {
            write!(f, "Rat({}/{})", self.num, self.den)
        }
    }
}

impl std::fmt::Display for Rat {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        if self.den == Tern::ONE {
            write!(f, "{}", self.num)
        } else {
            write!(f, "{}/{}", self.num, self.den)
        }
    }
}

impl std::ops::Add for Rat {
    type Output = Rat;
    fn add(self, r: Rat) -> Rat {
        Rat::add(self, r)
    }
}
impl std::ops::Sub for Rat {
    type Output = Rat;
    fn sub(self, r: Rat) -> Rat {
        Rat::sub(self, r)
    }
}
impl std::ops::Mul for Rat {
    type Output = Rat;
    fn mul(self, r: Rat) -> Rat {
        Rat::mul(self, r)
    }
}
impl std::ops::Neg for Rat {
    type Output = Rat;
    fn neg(self) -> Rat {
        Rat::neg(self)
    }
}

// ── Tests ─────────────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::trit::{N, P, Z};

    fn r(n: i64, d: i64) -> Rat {
        Rat::from_i64(n, d)
    }

    #[test]
    fn reduction() {
        let x = r(6, 4);
        assert_eq!(x.numerator().to_i64(), 3);
        assert_eq!(x.denominator().to_i64(), 2);
    }

    #[test]
    fn negative_denominator_normalized() {
        let x = r(3, -5);
        assert_eq!(x.numerator().to_i64(), -3);
        assert_eq!(x.denominator().to_i64(), 5);
    }

    #[test]
    fn addition() {
        // 1/2 + 1/3 = 5/6
        let a = r(1, 2);
        let b = r(1, 3);
        let c = a + b;
        assert_eq!(c.numerator().to_i64(), 5);
        assert_eq!(c.denominator().to_i64(), 6);
    }

    #[test]
    fn multiplication() {
        // 2/3 * 3/4 = 1/2
        assert_eq!(r(2, 3) * r(3, 4), r(1, 2));
    }

    #[test]
    fn division() {
        // (2/3) / (4/5) = 10/12 = 5/6
        assert_eq!(r(2, 3).div(r(4, 5)), r(5, 6));
    }

    #[test]
    fn negation() {
        assert_eq!(-r(3, 7), r(-3, 7));
        assert_eq!(-(-r(3, 7)), r(3, 7));
    }

    #[test]
    fn ordering() {
        assert!(r(1, 3) < r(1, 2));
        assert!(r(-1, 2) < r(1, 2));
    }

    #[test]
    fn sign() {
        assert_eq!(r(-3, 5).sign(), N);
        assert_eq!(Rat::zero().sign(), Z);
        assert_eq!(r(7, 11).sign(), P);
    }

    #[test]
    fn zero_arithmetic() {
        let z = Rat::zero();
        let x = r(5, 3);
        assert_eq!(z + x, x);
        assert_eq!(z * x, z);
    }
}