use std::fmt;
use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
pub trait Ring: Clone + PartialEq + fmt::Debug + Sized {
fn zero() -> Self;
fn one() -> Self;
fn is_zero(&self) -> bool;
fn is_one(&self) -> bool {
*self == Self::one()
}
fn add(&self, rhs: &Self) -> Self;
fn sub(&self, rhs: &Self) -> Self;
fn mul(&self, rhs: &Self) -> Self;
fn neg(&self) -> Self;
fn pow_usize(&self, mut n: usize) -> Self {
if n == 0 {
return Self::one();
}
let mut base = self.clone();
let mut result = Self::one();
while n > 1 {
if n & 1 == 1 {
result = result.mul(&base);
}
base = base.mul(&base);
n >>= 1;
}
result.mul(&base)
}
}
pub trait EuclideanDomain: Ring {
fn div_rem(&self, other: &Self) -> (Self, Self);
fn gcd(a: &Self, b: &Self) -> Self {
let mut a = a.clone();
let mut b = b.clone();
while !b.is_zero() {
let (_, r) = a.div_rem(&b);
a = b;
b = r;
}
a
}
}
pub trait Field: EuclideanDomain {
fn div(&self, other: &Self) -> Self;
fn inv(&self) -> Self;
}
pub trait IntegralCoeff: Ring {
fn is_integer(&self) -> bool;
fn to_integer(&self) -> Option<BigInt>;
fn from_integer(n: BigInt) -> Self;
fn from_i64(n: i64) -> Self {
Self::from_integer(BigInt::from(n))
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum BindingStrength {
Weakest,
Sum,
Product,
Power,
Atom,
}
pub trait CoeffDisplay {
fn fmt_coeff(&self, f: &mut fmt::Formatter<'_>, env: BindingStrength) -> fmt::Result;
}
impl Ring for Ratio<BigInt> {
#[inline]
fn zero() -> Self {
<Ratio<BigInt> as Zero>::zero()
}
#[inline]
fn one() -> Self {
<Ratio<BigInt> as One>::one()
}
#[inline]
fn is_zero(&self) -> bool {
Zero::is_zero(self)
}
#[inline]
fn is_one(&self) -> bool {
One::is_one(self)
}
#[inline]
fn add(&self, rhs: &Self) -> Self {
self + rhs
}
#[inline]
fn sub(&self, rhs: &Self) -> Self {
self - rhs
}
#[inline]
fn mul(&self, rhs: &Self) -> Self {
self * rhs
}
#[inline]
fn neg(&self) -> Self {
-self
}
}
impl EuclideanDomain for Ratio<BigInt> {
#[inline]
fn div_rem(&self, other: &Self) -> (Self, Self) {
(self / other, <Self as Ring>::zero())
}
#[inline]
fn gcd(a: &Self, b: &Self) -> Self {
if Ring::is_zero(a) && Ring::is_zero(b) {
<Self as Ring>::zero()
} else {
<Self as Ring>::one()
}
}
}
impl Field for Ratio<BigInt> {
#[inline]
fn div(&self, other: &Self) -> Self {
self / other
}
#[inline]
fn inv(&self) -> Self {
Ratio::new(self.denom().clone(), self.numer().clone())
}
}
impl IntegralCoeff for Ratio<BigInt> {
#[inline]
fn is_integer(&self) -> bool {
self.denom().is_one()
}
fn to_integer(&self) -> Option<BigInt> {
if self.is_integer() {
Some(self.numer().clone())
} else {
None
}
}
#[inline]
fn from_integer(n: BigInt) -> Self {
Ratio::from_integer(n)
}
}
impl CoeffDisplay for Ratio<BigInt> {
fn fmt_coeff(&self, f: &mut fmt::Formatter<'_>, env: BindingStrength) -> fmt::Result {
if self.denom().is_one() {
let n = self.numer();
if n.sign() == num_bigint::Sign::Minus && env >= BindingStrength::Sum {
write!(f, "({})", n)
} else {
write!(f, "{}", n)
}
} else {
if env >= BindingStrength::Product {
write!(f, "({}/{})", self.numer(), self.denom())
} else {
write!(f, "{}/{}", self.numer(), self.denom())
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
type Q = Ratio<BigInt>;
fn q(n: i64, d: i64) -> Q {
Ratio::new(BigInt::from(n), BigInt::from(d))
}
fn qzero() -> Q {
<Q as Ring>::zero()
}
fn qone() -> Q {
<Q as Ring>::one()
}
#[test]
fn ring_additive_identity() {
let a = q(3, 4);
assert_eq!(Ring::add(&a, &qzero()), a);
assert_eq!(Ring::add(&qzero(), &a), a);
}
#[test]
fn ring_multiplicative_identity() {
let a = q(3, 4);
assert_eq!(Ring::mul(&a, &qone()), a);
assert_eq!(Ring::mul(&qone(), &a), a);
}
#[test]
fn ring_additive_inverse() {
let a = q(3, 4);
let neg_a = Ring::neg(&a);
assert_eq!(Ring::add(&a, &neg_a), qzero());
}
#[test]
fn ring_commutativity() {
let a = q(2, 3);
let b = q(5, 7);
assert_eq!(Ring::add(&a, &b), Ring::add(&b, &a));
assert_eq!(Ring::mul(&a, &b), Ring::mul(&b, &a));
}
#[test]
fn ring_associativity() {
let a = q(1, 2);
let b = q(1, 3);
let c = q(1, 5);
assert_eq!(
Ring::add(&Ring::add(&a, &b), &c),
Ring::add(&a, &Ring::add(&b, &c))
);
assert_eq!(
Ring::mul(&Ring::mul(&a, &b), &c),
Ring::mul(&a, &Ring::mul(&b, &c))
);
}
#[test]
fn ring_distributivity() {
let a = q(2, 3);
let b = q(1, 4);
let c = q(5, 6);
let lhs = Ring::mul(&a, &Ring::add(&b, &c));
let rhs = Ring::add(&Ring::mul(&a, &b), &Ring::mul(&a, &c));
assert_eq!(lhs, rhs);
}
#[test]
fn ring_is_zero_and_is_one() {
assert!(Ring::is_zero(&qzero()));
assert!(!Ring::is_zero(&qone()));
assert!(Ring::is_one(&qone()));
assert!(!Ring::is_one(&qzero()));
assert!(!Ring::is_zero(&q(2, 3)));
assert!(!Ring::is_one(&q(2, 3)));
}
#[test]
fn ring_pow_usize() {
let a = q(2, 3);
assert_eq!(a.pow_usize(0), qone());
assert_eq!(a.pow_usize(1), q(2, 3));
assert_eq!(a.pow_usize(2), q(4, 9));
assert_eq!(a.pow_usize(3), q(8, 27));
}
#[test]
fn field_division() {
let a = q(3, 4);
let b = q(2, 5);
assert_eq!(Field::div(&a, &b), q(15, 8));
}
#[test]
fn field_inverse() {
let a = q(3, 7);
let inv = Field::inv(&a);
assert_eq!(Ring::mul(&a, &inv), qone());
}
#[test]
fn field_div_rem_trivial() {
let a = q(5, 3);
let b = q(2, 7);
let (quot, rem) = EuclideanDomain::div_rem(&a, &b);
assert_eq!(quot, Field::div(&a, &b));
assert!(Ring::is_zero(&rem));
}
#[test]
fn field_gcd_is_one() {
let a = q(3, 4);
let b = q(5, 6);
assert_eq!(EuclideanDomain::gcd(&a, &b), qone());
}
#[test]
fn field_gcd_with_zero() {
let a = qzero();
let b = q(5, 6);
assert_eq!(EuclideanDomain::gcd(&a, &b), qone());
assert!(Ring::is_zero(&EuclideanDomain::gcd(&a, &a)));
}
#[test]
fn integral_is_integer() {
assert!(IntegralCoeff::is_integer(&q(5, 1)));
assert!(!IntegralCoeff::is_integer(&q(5, 3)));
assert!(IntegralCoeff::is_integer(&qzero()));
}
#[test]
fn integral_to_integer() {
assert_eq!(IntegralCoeff::to_integer(&q(7, 1)), Some(BigInt::from(7)));
assert_eq!(IntegralCoeff::to_integer(&q(7, 3)), None);
}
#[test]
fn integral_from_integer() {
let x = <Q as IntegralCoeff>::from_integer(BigInt::from(42));
assert_eq!(x, q(42, 1));
}
#[test]
fn integral_from_i64() {
let x = <Q as IntegralCoeff>::from_i64(-5);
assert_eq!(x, q(-5, 1));
}
fn format_coeff(c: &Q, env: BindingStrength) -> String {
use std::fmt::Write;
let mut s = String::new();
struct Wrapper<'a>(&'a Q, BindingStrength);
impl fmt::Display for Wrapper<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt_coeff(f, self.1)
}
}
write!(s, "{}", Wrapper(c, env)).unwrap();
s
}
#[test]
fn display_integer_in_sum() {
assert_eq!(format_coeff(&q(5, 1), BindingStrength::Sum), "5");
}
#[test]
fn display_integer_in_product() {
assert_eq!(format_coeff(&q(5, 1), BindingStrength::Product), "5");
}
#[test]
fn display_negative_integer_in_sum() {
assert_eq!(format_coeff(&q(-5, 1), BindingStrength::Sum), "(-5)");
}
#[test]
fn display_negative_integer_at_top_level() {
assert_eq!(format_coeff(&q(-5, 1), BindingStrength::Weakest), "-5");
}
#[test]
fn display_fraction_in_sum() {
assert_eq!(format_coeff(&q(3, 4), BindingStrength::Sum), "3/4");
}
#[test]
fn display_fraction_in_product() {
assert_eq!(format_coeff(&q(3, 4), BindingStrength::Product), "(3/4)");
}
#[test]
fn display_one_in_product() {
assert_eq!(format_coeff(&q(1, 1), BindingStrength::Product), "1");
}
#[test]
fn display_zero() {
assert_eq!(format_coeff(&qzero(), BindingStrength::Weakest), "0");
}
#[test]
fn binding_strength_ordering() {
assert!(BindingStrength::Weakest < BindingStrength::Sum);
assert!(BindingStrength::Sum < BindingStrength::Product);
assert!(BindingStrength::Product < BindingStrength::Power);
assert!(BindingStrength::Power < BindingStrength::Atom);
}
}