#![allow(clippy::arithmetic_side_effects)]
#![allow(clippy::should_implement_trait)]
use std::cmp::Ordering;
use num_rational::Ratio;
use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, One, Signed, Zero};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TaintEvent {
ExactnessLost,
RationalOverflow,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct MagOpResult {
pub mag: Mag,
pub event: Option<TaintEvent>,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Mag {
Exact(Ratio<i128>),
Float(f64),
}
impl Mag {
pub fn exact(r: Ratio<i128>) -> Self {
Self::Exact(r)
}
#[allow(clippy::result_unit_err)]
pub fn float(f: f64) -> Result<Self, ()> {
if f.is_finite() {
Ok(Self::Float(f))
} else {
Err(())
}
}
pub fn is_exact(self) -> bool {
matches!(self, Self::Exact(_))
}
pub fn as_f64(self) -> f64 {
match self {
Self::Exact(r) => {
let n: f64 = num_traits::ToPrimitive::to_f64(r.numer()).unwrap_or(0.0);
let d: f64 = num_traits::ToPrimitive::to_f64(r.denom()).unwrap_or(1.0);
n / d
}
Self::Float(f) => f,
}
}
pub fn exact_ratio(self) -> Option<Ratio<i128>> {
match self {
Self::Exact(r) => Some(r),
Self::Float(_) => None,
}
}
pub fn partial_cmp(self, other: Self) -> Option<Ordering> {
match (self, other) {
(Self::Exact(a), Self::Exact(b)) => Some(a.cmp(&b)),
(Self::Float(a), Self::Float(b)) => a.partial_cmp(&b),
(Self::Exact(a), Self::Float(b)) => Self::as_f64(Self::Exact(a)).partial_cmp(&b),
(Self::Float(a), Self::Exact(b)) => a.partial_cmp(&Self::as_f64(Self::Exact(b))),
}
}
pub fn neg(self) -> Self {
match self {
Self::Exact(r) => Self::Exact(-r),
Self::Float(f) => Self::Float(-f),
}
}
pub fn abs(self) -> Self {
match self {
Self::Exact(r) => Self::Exact(r.abs()),
Self::Float(f) => Self::Float(f.abs()),
}
}
pub fn is_negative(self) -> bool {
match self {
Self::Exact(r) => r.is_negative(),
Self::Float(f) => f.is_sign_negative() && f != 0.0,
}
}
pub fn is_zero(self) -> bool {
match self {
Self::Exact(r) => r.is_zero(),
Self::Float(f) => f == 0.0,
}
}
pub fn add(self, rhs: Self) -> MagOpResult {
match (self, rhs) {
(Self::Exact(a), Self::Exact(b)) => match a.checked_add(&b) {
Some(r) => MagOpResult {
mag: Self::Exact(r),
event: None,
},
None => MagOpResult {
mag: Self::Float(self.as_f64() + rhs.as_f64()),
event: Some(TaintEvent::RationalOverflow),
},
},
_ => MagOpResult {
mag: Self::Float(self.as_f64() + rhs.as_f64()),
event: if self.is_exact() && rhs.is_exact() {
Some(TaintEvent::ExactnessLost)
} else {
None
},
},
}
}
pub fn sub(self, rhs: Self) -> MagOpResult {
match (self, rhs) {
(Self::Exact(a), Self::Exact(b)) => match a.checked_sub(&b) {
Some(r) => MagOpResult {
mag: Self::Exact(r),
event: None,
},
None => MagOpResult {
mag: Self::Float(self.as_f64() - rhs.as_f64()),
event: Some(TaintEvent::RationalOverflow),
},
},
_ => MagOpResult {
mag: Self::Float(self.as_f64() - rhs.as_f64()),
event: if self.is_exact() && rhs.is_exact() {
Some(TaintEvent::ExactnessLost)
} else {
None
},
},
}
}
pub fn mul(self, rhs: Self) -> MagOpResult {
match (self, rhs) {
(Self::Exact(a), Self::Exact(b)) => match a.checked_mul(&b) {
Some(r) => MagOpResult {
mag: Self::Exact(r),
event: None,
},
None => MagOpResult {
mag: Self::Float(self.as_f64() * rhs.as_f64()),
event: Some(TaintEvent::RationalOverflow),
},
},
_ => MagOpResult {
mag: Self::Float(self.as_f64() * rhs.as_f64()),
event: if self.is_exact() && rhs.is_exact() {
Some(TaintEvent::ExactnessLost)
} else {
None
},
},
}
}
#[allow(clippy::result_unit_err)]
pub fn div(self, rhs: Self) -> Result<MagOpResult, ()> {
if rhs.is_zero() {
return Err(());
}
Ok(match (self, rhs) {
(Self::Exact(a), Self::Exact(b)) => match a.checked_div(&b) {
Some(r) => MagOpResult {
mag: Self::Exact(r),
event: None,
},
None => MagOpResult {
mag: Self::Float(self.as_f64() / rhs.as_f64()),
event: Some(TaintEvent::RationalOverflow),
},
},
_ => MagOpResult {
mag: Self::Float(self.as_f64() / rhs.as_f64()),
event: if self.is_exact() && rhs.is_exact() {
Some(TaintEvent::ExactnessLost)
} else {
None
},
},
})
}
pub fn pow_int(self, exp: i32) -> MagOpResult {
if exp == 0 {
return MagOpResult {
mag: Self::Exact(Ratio::one()),
event: None,
};
}
match self {
Self::Exact(r) => {
let mag = if exp > 0 {
r.pow(exp)
} else {
Ratio::one() / r.pow(-exp)
};
MagOpResult {
mag: Self::Exact(mag),
event: None,
}
}
Self::Float(f) => MagOpResult {
mag: Self::Float(f.powi(exp)),
event: None,
},
}
}
}