use crate::BigInt;
use num_traits::{One, Zero};
use std::fmt;
use std::ops::{Add, Div, Mul, Neg, Rem, Sub};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct GaussInt {
real: BigInt,
imag: BigInt,
}
impl GaussInt {
pub fn new(real: BigInt, imag: BigInt) -> Self {
GaussInt { real, imag }
}
pub fn from_i64(real: i64, imag: i64) -> Self {
GaussInt {
real: BigInt::new(real),
imag: BigInt::new(imag),
}
}
pub fn real(&self) -> &BigInt {
&self.real
}
pub fn imag(&self) -> &BigInt {
&self.imag
}
pub fn is_zero(&self) -> bool {
self.real.is_zero() && self.imag.is_zero()
}
pub fn is_real(&self) -> bool {
self.imag.is_zero()
}
pub fn conjugate(&self) -> Self {
GaussInt {
real: self.real.clone(),
imag: -&self.imag,
}
}
pub fn norm(&self) -> BigInt {
&self.real * &self.real + &self.imag * &self.imag
}
pub fn is_unit(&self) -> bool {
self.norm() == BigInt::new(1)
}
pub fn pow_u32(&self, exp: u32) -> Self {
if exp == 0 {
return GaussInt::one();
}
let mut result = GaussInt::one();
let mut base = self.clone();
let mut e = exp;
while e > 0 {
if e & 1 == 1 {
result = result * base.clone();
}
base = base.clone() * base;
e >>= 1;
}
result
}
}
impl Zero for GaussInt {
fn zero() -> Self {
GaussInt {
real: BigInt::zero(),
imag: BigInt::zero(),
}
}
fn is_zero(&self) -> bool {
self.is_zero()
}
}
impl One for GaussInt {
fn one() -> Self {
GaussInt {
real: BigInt::one(),
imag: BigInt::zero(),
}
}
}
impl Neg for GaussInt {
type Output = GaussInt;
fn neg(self) -> GaussInt {
GaussInt {
real: -self.real,
imag: -self.imag,
}
}
}
impl Neg for &GaussInt {
type Output = GaussInt;
fn neg(self) -> GaussInt {
GaussInt {
real: -&self.real,
imag: -&self.imag,
}
}
}
impl Add for GaussInt {
type Output = GaussInt;
fn add(self, other: GaussInt) -> GaussInt {
GaussInt {
real: self.real + other.real,
imag: self.imag + other.imag,
}
}
}
impl Add for &GaussInt {
type Output = GaussInt;
fn add(self, other: &GaussInt) -> GaussInt {
GaussInt {
real: &self.real + &other.real,
imag: &self.imag + &other.imag,
}
}
}
impl Add<&GaussInt> for GaussInt {
type Output = GaussInt;
fn add(self, other: &GaussInt) -> GaussInt {
&self + other
}
}
impl Add<GaussInt> for &GaussInt {
type Output = GaussInt;
fn add(self, other: GaussInt) -> GaussInt {
self + &other
}
}
impl Sub for GaussInt {
type Output = GaussInt;
fn sub(self, other: GaussInt) -> GaussInt {
GaussInt {
real: self.real - other.real,
imag: self.imag - other.imag,
}
}
}
impl Sub for &GaussInt {
type Output = GaussInt;
fn sub(self, other: &GaussInt) -> GaussInt {
GaussInt {
real: &self.real - &other.real,
imag: &self.imag - &other.imag,
}
}
}
impl Sub<&GaussInt> for GaussInt {
type Output = GaussInt;
fn sub(self, other: &GaussInt) -> GaussInt {
&self - other
}
}
impl Sub<GaussInt> for &GaussInt {
type Output = GaussInt;
fn sub(self, other: GaussInt) -> GaussInt {
self - &other
}
}
impl Mul for GaussInt {
type Output = GaussInt;
fn mul(self, other: GaussInt) -> GaussInt {
let ac = self.real.clone() * other.real.clone();
let bd = self.imag.clone() * other.imag.clone();
let ad = self.real * other.imag;
let bc = self.imag * other.real;
GaussInt {
real: ac - bd,
imag: ad + bc,
}
}
}
impl Mul for &GaussInt {
type Output = GaussInt;
fn mul(self, other: &GaussInt) -> GaussInt {
let ac = &self.real * &other.real;
let bd = &self.imag * &other.imag;
let ad = &self.real * &other.imag;
let bc = &self.imag * &other.real;
GaussInt {
real: ac - bd,
imag: ad + bc,
}
}
}
impl Mul<&GaussInt> for GaussInt {
type Output = GaussInt;
fn mul(self, other: &GaussInt) -> GaussInt {
&self * other
}
}
impl Mul<GaussInt> for &GaussInt {
type Output = GaussInt;
fn mul(self, other: GaussInt) -> GaussInt {
self * &other
}
}
fn round_div(a: &BigInt, b: &BigInt) -> BigInt {
let q = a / b;
let r = a % b;
let two_r = BigInt::new(2) * r.abs();
let b_abs = b.abs();
if two_r >= b_abs {
if (a.is_negative() && b.is_negative()) || (!a.is_negative() && !b.is_negative()) {
q + BigInt::one()
} else {
q - BigInt::one()
}
} else {
q
}
}
impl GaussInt {
pub fn div_rem(&self, other: &Self) -> Option<(Self, Self)> {
if other.is_zero() {
return None;
}
let conj = other.conjugate();
let numerator = self * conj; let denominator = other.norm();
let q_real = round_div(numerator.real(), &denominator);
let q_imag = round_div(numerator.imag(), &denominator);
let q = GaussInt::new(q_real, q_imag);
let r = self - &q * other;
Some((q, r))
}
}
impl Div for &GaussInt {
type Output = GaussInt;
fn div(self, other: Self) -> GaussInt {
self.div_rem(other).expect("division by zero").0
}
}
impl Div for GaussInt {
type Output = GaussInt;
fn div(self, other: Self) -> GaussInt {
self.div_rem(&other).expect("division by zero").0
}
}
impl Rem for &GaussInt {
type Output = GaussInt;
fn rem(self, other: Self) -> GaussInt {
self.div_rem(other).expect("division by zero").1
}
}
impl Rem for GaussInt {
type Output = GaussInt;
fn rem(self, other: Self) -> GaussInt {
self.div_rem(&other).expect("division by zero").1
}
}
impl fmt::Display for GaussInt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.imag.is_zero() {
write!(f, "{}", self.real)
} else if self.real.is_zero() {
if self.imag == BigInt::one() {
write!(f, "i")
} else if self.imag == -BigInt::one() {
write!(f, "-i")
} else {
write!(f, "{}i", self.imag)
}
} else {
let sign = if self.imag.is_positive() { "+" } else { "" };
write!(f, "{}{}{}i", self.real, sign, self.imag)
}
}
}
impl GaussInt {
fn canonicalize(&self) -> Self {
if self.is_zero() {
return self.clone();
}
let i = GaussInt::from_i64(0, 1);
let units = [GaussInt::one(), -GaussInt::one(), i.clone(), -i];
let mut best = &units[0] * self;
for u in &units[1..] {
let candidate = u * self;
let real_pos = candidate.real().is_positive();
let real_zero_imag_pos = candidate.real().is_zero() && candidate.imag().is_positive();
let best_real_pos = best.real().is_positive();
let best_real_zero_imag_pos = best.real().is_zero() && best.imag().is_positive();
if (real_pos || real_zero_imag_pos) && !(best_real_pos || best_real_zero_imag_pos) {
best = candidate;
}
}
best
}
pub fn gcd(&self, other: &Self) -> Self {
let mut a = self.clone();
let mut b = other.clone();
while !b.is_zero() {
let r = a.div_rem(&b).unwrap().1;
a = b;
b = r;
}
a.canonicalize()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gauss_int_creation() {
let z = GaussInt::from_i64(3, 4);
assert_eq!(*z.real(), BigInt::new(3));
assert_eq!(*z.imag(), BigInt::new(4));
}
#[test]
fn test_gauss_int_display() {
assert_eq!(GaussInt::from_i64(3, 4).to_string(), "3+4i");
assert_eq!(GaussInt::from_i64(3, -4).to_string(), "3-4i");
assert_eq!(GaussInt::from_i64(0, 5).to_string(), "5i");
assert_eq!(GaussInt::from_i64(7, 0).to_string(), "7");
assert_eq!(GaussInt::from_i64(0, 1).to_string(), "i");
assert_eq!(GaussInt::from_i64(0, -1).to_string(), "-i");
assert_eq!(GaussInt::from_i64(0, 0).to_string(), "0");
}
#[test]
fn test_gauss_int_conjugate() {
let z = GaussInt::from_i64(3, 4);
assert_eq!(z.conjugate(), GaussInt::from_i64(3, -4));
assert_eq!(z.conjugate().conjugate(), z);
}
#[test]
fn test_gauss_int_norm() {
assert_eq!(GaussInt::from_i64(3, 4).norm(), BigInt::new(25));
assert_eq!(GaussInt::from_i64(0, 0).norm(), BigInt::new(0));
assert_eq!(GaussInt::from_i64(1, 0).norm(), BigInt::new(1));
}
#[test]
fn test_gauss_int_arithmetic() {
let a = GaussInt::from_i64(3, 4);
let b = GaussInt::from_i64(1, 2);
assert_eq!(&a + &b, GaussInt::from_i64(4, 6));
assert_eq!(&a - &b, GaussInt::from_i64(2, 2));
assert_eq!(&a * &b, GaussInt::from_i64(-5, 10));
}
#[test]
fn test_gauss_int_neg() {
assert_eq!(-GaussInt::from_i64(3, 4), GaussInt::from_i64(-3, -4));
}
#[test]
fn test_gauss_int_units() {
assert!(GaussInt::from_i64(1, 0).is_unit());
assert!(GaussInt::from_i64(-1, 0).is_unit());
assert!(GaussInt::from_i64(0, 1).is_unit());
assert!(GaussInt::from_i64(0, -1).is_unit());
assert!(!GaussInt::from_i64(2, 0).is_unit());
assert!(!GaussInt::from_i64(0, 2).is_unit());
}
#[test]
fn test_gauss_int_pow() {
assert_eq!(
GaussInt::from_i64(1, 1).pow_u32(2),
GaussInt::from_i64(0, 2)
);
assert_eq!(
GaussInt::from_i64(1, 1).pow_u32(4),
GaussInt::from_i64(-4, 0)
);
assert_eq!(
GaussInt::from_i64(1, 1).pow_u32(8),
GaussInt::from_i64(16, 0)
);
assert_eq!(GaussInt::from_i64(5, 7).pow_u32(0), GaussInt::one());
}
#[test]
fn test_gauss_int_zero_one() {
assert!(GaussInt::zero().is_zero());
assert_eq!(GaussInt::one(), GaussInt::from_i64(1, 0));
}
#[test]
fn test_gauss_int_field_properties() {
let z = GaussInt::from_i64(3, 4);
let zero = GaussInt::zero();
let one = GaussInt::one();
assert_eq!(&z + &zero, z);
assert_eq!(&z * &one, z);
assert_eq!(&z + &(-&z), GaussInt::zero());
let product = GaussInt::new(z.real().clone(), z.imag().clone()) * z.conjugate();
assert!(product.is_real());
assert_eq!(product.real, BigInt::new(25));
}
#[test]
fn test_gauss_int_div_exact() {
let a = GaussInt::from_i64(3, 4);
let b = GaussInt::from_i64(1, 2);
let (q, r) = a.div_rem(&b).unwrap();
assert_eq!(q, GaussInt::from_i64(2, 0));
assert!(r.norm() < b.norm());
assert_eq!(&q * &b + &r, a);
}
#[test]
fn test_gauss_int_div_different_quadrants() {
let a = GaussInt::from_i64(-3, 4);
let b = GaussInt::from_i64(1, -2);
let (q, r) = a.div_rem(&b).unwrap();
assert!(
r.norm() < b.norm(),
"N(r)={} >= N(b)={}",
r.norm(),
b.norm()
);
assert_eq!(&q * &b + &r, a);
}
#[test]
fn test_gauss_int_div_by_unit() {
let a = GaussInt::from_i64(5, 7);
let i = GaussInt::from_i64(0, 1);
let (q, r) = a.div_rem(&i).unwrap();
assert_eq!(q, GaussInt::from_i64(7, -5));
assert!(r.is_zero());
}
#[test]
fn test_gauss_int_div_zero_returns_none() {
let a = GaussInt::from_i64(1, 1);
let zero = GaussInt::zero();
assert!(a.div_rem(&zero).is_none());
}
#[test]
fn test_gauss_int_div_trait() {
let a = GaussInt::from_i64(10, 0);
let b = GaussInt::from_i64(3, 0);
let q = a / b;
assert_eq!(q, GaussInt::from_i64(3, 0));
}
#[test]
fn test_gauss_int_rem_trait() {
let a = GaussInt::from_i64(10, 0);
let b = GaussInt::from_i64(3, 0);
let r = a % b;
assert_eq!(r, GaussInt::from_i64(1, 0));
}
#[test]
fn test_gauss_int_div_rem_euclidean_property() {
let cases = vec![
(GaussInt::from_i64(100, 0), GaussInt::from_i64(7, 0)),
(GaussInt::from_i64(0, 100), GaussInt::from_i64(0, 7)),
(GaussInt::from_i64(-100, -100), GaussInt::from_i64(3, 4)),
(GaussInt::from_i64(1, 1), GaussInt::from_i64(1, 1)),
(GaussInt::from_i64(7, 5), GaussInt::from_i64(1, 2)),
(GaussInt::from_i64(-3, -4), GaussInt::from_i64(2, 0)),
];
for (a, b) in cases {
let (q, r) = a.clone().div_rem(&b).unwrap();
assert!(
r.norm() < b.norm(),
"N({}) = {} >= N({}) = {} for a={}, b={}",
r,
r.norm(),
b,
b.norm(),
a,
b
);
assert_eq!(
&q * &b + &r,
a,
"a = q*b + r failed: {} != {}*{} + {}",
a,
q,
b,
r
);
}
}
#[test]
fn test_round_div_negative() {
assert_eq!(
round_div(&BigInt::new(-11), &BigInt::new(5)),
BigInt::new(-2)
);
assert_eq!(
round_div(&BigInt::new(-13), &BigInt::new(5)),
BigInt::new(-3)
);
assert_eq!(round_div(&BigInt::new(11), &BigInt::new(5)), BigInt::new(2));
assert_eq!(round_div(&BigInt::new(13), &BigInt::new(5)), BigInt::new(3));
assert_eq!(
round_div(&BigInt::new(-11), &BigInt::new(-5)),
BigInt::new(2)
);
assert_eq!(
round_div(&BigInt::new(-13), &BigInt::new(-5)),
BigInt::new(3)
);
assert_eq!(round_div(&BigInt::new(5), &BigInt::new(2)), BigInt::new(3));
assert_eq!(
round_div(&BigInt::new(-5), &BigInt::new(2)),
BigInt::new(-3)
);
}
#[test]
fn test_gauss_int_gcd_coprime() {
let a = GaussInt::from_i64(3, 4);
let b = GaussInt::from_i64(3, -4);
let g = a.gcd(&b);
assert!(g.is_unit(), "gcd({}, {}) = {} should be a unit", a, b, g);
}
#[test]
fn test_gauss_int_gcd_shared_factor() {
let a = GaussInt::from_i64(6, 8);
let b = GaussInt::from_i64(3, 4);
let g = a.gcd(&b);
assert_eq!(g.norm(), BigInt::new(25));
}
#[test]
fn test_gauss_int_gcd_with_zero() {
let a = GaussInt::from_i64(3, 4);
let zero = GaussInt::zero();
let g = a.gcd(&zero);
assert_eq!(g.norm(), a.norm());
}
#[test]
fn test_gauss_int_gcd_commutative() {
let a = GaussInt::from_i64(12, 18);
let b = GaussInt::from_i64(6, 8);
assert_eq!(a.gcd(&b).norm(), b.gcd(&a).norm());
}
#[test]
fn test_gauss_int_gcd_divides_both() {
let a = GaussInt::from_i64(15, 10);
let b = GaussInt::from_i64(5, 5);
let g = a.gcd(&b);
assert!(!g.is_zero());
assert!(a.div_rem(&g).unwrap().1.is_zero(), "gcd should divide a");
assert!(b.div_rem(&g).unwrap().1.is_zero(), "gcd should divide b");
}
}