use super::linear_eqn::LinearEqn;
use super::roots::{RootType, Roots};
use crate::primitives::scalar::{SMALL, VSMALL};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct QuadraticEqn {
pub a: f64,
pub b: f64,
pub c: f64,
}
#[inline]
fn sign(x: f64) -> f64 {
if x >= 0.0 {
1.0
} else {
-1.0
}
}
impl QuadraticEqn {
#[inline]
pub fn new(a: f64, b: f64, c: f64) -> Self {
Self { a, b, c }
}
#[inline]
pub fn value(&self, x: f64) -> f64 {
x * (x * self.a + self.b) + self.c
}
#[inline]
pub fn derivative(&self, x: f64) -> f64 {
x * 2.0 * self.a + self.b
}
#[inline]
pub fn error(&self, x: f64) -> f64 {
SMALL * x.abs() * ((x * self.a).abs() + self.b.abs())
+ SMALL * ((x * (x * self.a + self.b)).abs() + self.c.abs())
}
pub fn roots(&self) -> Roots<2> {
let a = self.a;
let b = self.b;
let c = self.c;
if a.abs() < VSMALL {
return Roots::<2>::with_tail(LinearEqn::new(b, c).roots(), RootType::Nan, 0.0);
}
let w = a * c;
let num_discr = f64::mul_add(-a, c, w) + f64::mul_add(b, b / 4.0, -w);
let discr = if num_discr.abs() > VSMALL {
num_discr
} else {
0.0
};
if discr > 0.0 {
let x = -b / 2.0 - sign(b) * discr.sqrt();
Roots::from_pair(LinearEqn::new(-a, x).roots(), LinearEqn::new(-x, c).roots())
} else if discr < 0.0 {
let x_re = Roots::<1>::new(RootType::Complex, -b / 2.0 / a);
let x_im = Roots::<1>::new(RootType::Complex, sign(b) * (-discr).sqrt() / a);
Roots::from_pair(x_re, x_im)
} else {
let r = LinearEqn::new(a, b / 2.0).roots();
Roots::both(r)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn collect_real_roots(r: Roots<2>) -> Vec<f64> {
(0..2)
.filter(|&i| r.root_type(i) == RootType::Real)
.map(|i| r[i])
.collect()
}
#[test]
fn two_distinct_real() {
let eq = QuadraticEqn::new(1.0, -3.0, 2.0);
let mut roots = collect_real_roots(eq.roots());
roots.sort_by(f64::total_cmp);
assert!((roots[0] - 1.0).abs() < 1e-13);
assert!((roots[1] - 2.0).abs() < 1e-13);
}
#[test]
fn two_complex_roots() {
let eq = QuadraticEqn::new(1.0, 0.0, 1.0);
let r = eq.roots();
assert_eq!(r.root_type(0), RootType::Complex);
assert_eq!(r.root_type(1), RootType::Complex);
assert!((r[0]).abs() < 1e-14); assert!((r[1] - 1.0).abs() < 1e-14); }
#[test]
fn repeated_real_root() {
let eq = QuadraticEqn::new(1.0, -6.0, 9.0);
let r = eq.roots();
assert_eq!(r.root_type(0), RootType::Real);
assert_eq!(r.root_type(1), RootType::Real);
assert!((r[0] - 3.0).abs() < 1e-13);
assert!((r[1] - 3.0).abs() < 1e-13);
}
#[test]
fn degenerate_falls_back_to_linear() {
let eq = QuadraticEqn::new(0.0, 2.0, -4.0);
let r = eq.roots();
assert_eq!(r.root_type(0), RootType::Real);
assert!((r[0] - 2.0).abs() < 1e-14);
assert_eq!(r.root_type(1), RootType::Nan);
}
#[test]
fn value_at_root() {
let eq = QuadraticEqn::new(1.0, -3.0, 2.0);
for r in collect_real_roots(eq.roots()) {
assert!(eq.value(r).abs() < 1e-12);
}
}
}