use core::cmp::Ordering;
use core::fmt;
use crate::int::Int;
use crate::nat::Nat;
use crate::rational::Rational;
#[derive(Clone)]
pub struct Quadratic {
a: Rational,
b: Rational,
d: Int, }
fn squarefree_part(d: &Int) -> (Int, Int) {
if d.is_zero() {
return (Int::ZERO, Int::ONE);
}
let neg = d.is_negative();
let factors = d.magnitude().factorize(); let mut sf = Nat::one();
let mut ext = Nat::one();
let mut i = 0;
while i < factors.len() {
let p = factors[i].clone();
let mut e = 0u32;
while i < factors.len() && factors[i] == p {
e += 1;
i += 1;
}
if e % 2 == 1 {
sf = sf.mul(&p);
}
ext = ext.mul(&p.pow(e / 2));
}
let sf = if neg {
Int::from(sf).neg()
} else {
Int::from(sf)
};
(sf, Int::from(ext))
}
impl Quadratic {
pub fn new(a: Rational, b: Rational, d: Int) -> Quadratic {
if b.is_zero() {
return Quadratic::rational(a);
}
let (sf, ext) = squarefree_part(&d);
let b = b.mul(&Rational::from_integer(ext));
if sf.is_one() {
Quadratic::rational(a.add(&b))
} else {
Quadratic { a, b, d: sf }
}
}
pub fn rational(a: Rational) -> Quadratic {
Quadratic {
a,
b: Rational::ZERO,
d: Int::ONE,
}
}
pub fn sqrt(d: Int) -> Quadratic {
Quadratic::new(Rational::ZERO, Rational::ONE, d)
}
#[inline]
pub fn rational_part(&self) -> &Rational {
&self.a
}
#[inline]
pub fn surd_coefficient(&self) -> &Rational {
&self.b
}
#[inline]
pub fn radicand(&self) -> &Int {
&self.d
}
#[inline]
pub fn is_rational(&self) -> bool {
self.b.is_zero()
}
fn common_d(&self, other: &Quadratic) -> Int {
if self.is_rational() {
other.d.clone()
} else if other.is_rational() || self.d == other.d {
self.d.clone()
} else {
panic!(
"Quadratic: operands lie in different fields (√{} vs √{})",
self.d, other.d
)
}
}
pub fn add(&self, rhs: &Quadratic) -> Quadratic {
let d = self.common_d(rhs);
Quadratic::new(self.a.add(&rhs.a), self.b.add(&rhs.b), d)
}
pub fn sub(&self, rhs: &Quadratic) -> Quadratic {
let d = self.common_d(rhs);
Quadratic::new(self.a.sub(&rhs.a), self.b.sub(&rhs.b), d)
}
pub fn neg(&self) -> Quadratic {
Quadratic {
a: self.a.neg(),
b: self.b.neg(),
d: self.d.clone(),
}
}
pub fn conjugate(&self) -> Quadratic {
Quadratic {
a: self.a.clone(),
b: self.b.neg(),
d: self.d.clone(),
}
}
pub fn norm(&self) -> Rational {
let d = Rational::from_integer(self.d.clone());
self.a.mul(&self.a).sub(&self.b.mul(&self.b).mul(&d))
}
pub fn mul(&self, rhs: &Quadratic) -> Quadratic {
let d = self.common_d(rhs);
let dr = Rational::from_integer(d.clone());
let a = self.a.mul(&rhs.a).add(&self.b.mul(&rhs.b).mul(&dr));
let b = self.a.mul(&rhs.b).add(&rhs.a.mul(&self.b));
Quadratic::new(a, b, d)
}
pub fn recip(&self) -> Quadratic {
let n = self.norm();
assert!(!n.is_zero(), "Quadratic::recip: reciprocal of zero");
Quadratic {
a: self.a.div(&n),
b: self.b.neg().div(&n),
d: self.d.clone(),
}
}
pub fn div(&self, rhs: &Quadratic) -> Quadratic {
self.mul(&rhs.recip())
}
pub fn pow(&self, mut n: u32) -> Quadratic {
let mut base = self.clone();
let mut acc = Quadratic::rational(Rational::ONE);
while n > 0 {
if n & 1 == 1 {
acc = acc.mul(&base);
}
n >>= 1;
if n > 0 {
base = base.mul(&base);
}
}
acc
}
pub fn to_float(
&self,
precision: u64,
mode: crate::float::RoundingMode,
) -> crate::float::Float {
use crate::float::Float;
assert!(
!self.d.is_negative(),
"Quadratic::to_float: value is not real (d < 0)"
);
let work = precision + 16;
let a = Float::from_rational(&self.a, work, mode);
if self.b.is_zero() {
return a.round(precision, mode);
}
let b = Float::from_rational(&self.b, work, mode);
let root = Float::from_int(&self.d, work, mode).sqrt(work, mode);
a.add(&b.mul(&root, work, mode), precision, mode)
}
}
impl PartialEq for Quadratic {
fn eq(&self, other: &Self) -> bool {
if self.is_rational() && other.is_rational() {
return self.a == other.a;
}
self.a == other.a && self.b == other.b && self.d == other.d
}
}
impl Eq for Quadratic {}
impl PartialOrd for Quadratic {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if !self.is_rational() && !other.is_rational() && self.d != other.d {
return None;
}
let d = if self.is_rational() {
&other.d
} else {
&self.d
};
if d.is_negative() {
return None; }
let big_a = self.a.sub(&other.a);
let big_b = self.b.sub(&other.b);
Some(sign_of_a_plus_b_root(&big_a, &big_b, d))
}
}
fn sign_of_a_plus_b_root(a: &Rational, b: &Rational, d: &Int) -> Ordering {
if b.is_zero() {
return a.signum().cmp(&0);
}
let dr = Rational::from_integer(d.clone());
let a2 = a.mul(a);
let b2d = b.mul(b).mul(&dr);
match (a.signum(), b.signum()) {
(sa, sb) if sa >= 0 && sb >= 0 => {
if a.is_zero() && b.is_zero() {
Ordering::Equal
} else {
Ordering::Greater
}
}
(sa, sb) if sa <= 0 && sb <= 0 => Ordering::Less,
(sa, _) if sa > 0 => a2.cmp(&b2d),
_ => b2d.cmp(&a2),
}
}
impl fmt::Display for Quadratic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_rational() {
return fmt::Display::fmt(&self.a, f);
}
if !self.a.is_zero() {
write!(f, "{} + ", self.a)?;
}
write!(f, "{}·√{}", self.b, self.d)
}
}
impl fmt::Debug for Quadratic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Quadratic({} + {}·√{})", self.a, self.b, self.d)
}
}
impl From<Rational> for Quadratic {
#[inline]
fn from(r: Rational) -> Quadratic {
Quadratic::rational(r)
}
}
impl From<Int> for Quadratic {
#[inline]
fn from(n: Int) -> Quadratic {
Quadratic::rational(Rational::from_integer(n))
}
}
macro_rules! quad_binop {
($tr:ident, $m:ident, $atr:ident, $am:ident) => {
impl core::ops::$tr for Quadratic {
type Output = Quadratic;
#[inline]
fn $m(self, rhs: Quadratic) -> Quadratic {
Quadratic::$m(&self, &rhs)
}
}
impl core::ops::$tr<&Quadratic> for &Quadratic {
type Output = Quadratic;
#[inline]
fn $m(self, rhs: &Quadratic) -> Quadratic {
Quadratic::$m(self, rhs)
}
}
impl core::ops::$atr for Quadratic {
#[inline]
fn $am(&mut self, rhs: Quadratic) {
*self = Quadratic::$m(self, &rhs);
}
}
};
}
quad_binop!(Add, add, AddAssign, add_assign);
quad_binop!(Sub, sub, SubAssign, sub_assign);
quad_binop!(Mul, mul, MulAssign, mul_assign);
quad_binop!(Div, div, DivAssign, div_assign);
impl core::ops::Neg for Quadratic {
type Output = Quadratic;
#[inline]
fn neg(self) -> Quadratic {
Quadratic::neg(&self)
}
}