use super::roots::{RootType, Roots};
use crate::primitives::scalar::{SMALL, VGREAT, VSMALL};
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinearEqn {
pub a: f64,
pub b: f64,
}
#[inline]
fn sign(x: f64) -> f64 {
if x >= 0.0 {
1.0
} else {
-1.0
}
}
impl LinearEqn {
#[inline]
pub fn new(a: f64, b: f64) -> Self {
Self { a, b }
}
#[inline]
pub fn value(&self, x: f64) -> f64 {
x * self.a + self.b
}
#[inline]
pub fn derivative(&self, _x: f64) -> f64 {
self.a
}
#[inline]
pub fn error(&self, x: f64) -> f64 {
SMALL * ((x * self.a).abs() + self.b.abs())
}
pub fn roots(&self) -> Roots<1> {
let a = self.a;
let b = self.b;
if a.abs() < VSMALL {
return Roots::new(RootType::Nan, 0.0);
}
if (b / VGREAT).abs() >= a.abs() {
let t = if sign(a) == sign(b) {
RootType::NegInf
} else {
RootType::PosInf
};
return Roots::new(t, 0.0);
}
Roots::new(RootType::Real, -b / a)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_root() {
let eq = LinearEqn::new(2.0, -4.0);
let r = eq.roots();
assert_eq!(r.root_type(0), RootType::Real);
assert!((r[0] - 2.0).abs() < 1e-14);
}
#[test]
fn degenerate_zero_leading() {
let r = LinearEqn::new(0.0, 5.0).roots();
assert_eq!(r.root_type(0), RootType::Nan);
}
#[test]
fn overflow_root() {
let r = LinearEqn::new(1e-300, 1.0).roots();
assert_eq!(r.root_type(0), RootType::NegInf);
}
#[test]
fn value_and_error() {
let eq = LinearEqn::new(3.0, -6.0);
assert_eq!(eq.value(2.0), 0.0);
assert_eq!(eq.derivative(0.0), 3.0);
assert!(eq.error(2.0) >= 0.0);
}
}