use std::ops::{Add, Div, Mul, Rem, Sub};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Num {
Int(i64),
Float(f64),
}
impl Num {
pub fn as_f64(self) -> f64 {
match self {
Num::Int(n) => n as f64,
Num::Float(n) => n,
}
}
pub fn is_int(self) -> bool {
matches!(self, Num::Int(_))
}
fn combine(
self,
other: Num,
int_op: impl Fn(i64, i64) -> Option<i64>,
float_op: impl Fn(f64, f64) -> Option<f64>,
) -> Option<Num> {
match (self, other) {
(Num::Int(a), Num::Int(b)) => int_op(a, b).map(Num::Int),
_ => float_op(self.as_f64(), other.as_f64()).map(Num::Float),
}
}
pub fn max(self, other: Num) -> Num {
self.combine(other, |a, b| Some(a.max(b)), |a, b| Some(a.max(b)))
.expect("max is total")
}
pub fn min(self, other: Num) -> Num {
self.combine(other, |a, b| Some(a.min(b)), |a, b| Some(a.min(b)))
.expect("min is total")
}
pub fn abs(self) -> Num {
match self {
Num::Int(n) => Num::Int(n.abs()),
Num::Float(n) => Num::Float(n.abs()),
}
}
pub fn checked_div(self, other: Num) -> Option<Num> {
self.combine(
other,
|a, b| (b != 0).then(|| a / b),
|a, b| (b != 0.0).then(|| a / b),
)
}
pub fn checked_rem(self, other: Num) -> Option<Num> {
self.combine(
other,
|a, b| (b != 0).then(|| a % b),
|a, b| (b != 0.0).then(|| a % b),
)
}
}
impl Add for Num {
type Output = Num;
fn add(self, other: Num) -> Num {
self.combine(other, |a, b| Some(a + b), |a, b| Some(a + b))
.expect("addition is total")
}
}
impl Sub for Num {
type Output = Num;
fn sub(self, other: Num) -> Num {
self.combine(other, |a, b| Some(a - b), |a, b| Some(a - b))
.expect("subtraction is total")
}
}
impl Mul for Num {
type Output = Num;
fn mul(self, other: Num) -> Num {
self.combine(other, |a, b| Some(a * b), |a, b| Some(a * b))
.expect("multiplication is total")
}
}
impl Div for Num {
type Output = Num;
fn div(self, other: Num) -> Num {
self.checked_div(other).unwrap_or(Num::Float(f64::NAN))
}
}
impl Rem for Num {
type Output = Num;
fn rem(self, other: Num) -> Num {
self.checked_rem(other).unwrap_or(Num::Float(f64::NAN))
}
}
impl From<i64> for Num {
fn from(n: i64) -> Self {
Num::Int(n)
}
}
impl From<f64> for Num {
fn from(n: f64) -> Self {
Num::Float(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn two_ints_stay_an_int() {
assert_eq!(Num::Int(3) + Num::Int(4), Num::Int(7));
assert_eq!(Num::Int(3) * Num::Int(4), Num::Int(12));
assert_eq!(Num::Int(15).checked_div(Num::Int(2)), Some(Num::Int(7)));
}
#[test]
fn a_float_operand_widens_the_result() {
assert_eq!(
Num::Int(15).checked_div(Num::Float(2.0)),
Some(Num::Float(7.5))
);
assert_eq!(
Num::Float(15.0).checked_div(Num::Int(2)),
Some(Num::Float(7.5))
);
assert_eq!(Num::Int(3) + Num::Float(0.5), Num::Float(3.5));
}
#[test]
fn a_zero_divisor_has_no_result() {
assert_eq!(Num::Int(1).checked_div(Num::Int(0)), None);
assert_eq!(Num::Float(1.0).checked_div(Num::Float(0.0)), None);
assert_eq!(Num::Int(1).checked_rem(Num::Int(0)), None);
}
#[test]
fn max_and_min_keep_the_type() {
assert_eq!(Num::Int(3).max(Num::Int(4)), Num::Int(4));
assert_eq!(Num::Int(3).max(Num::Float(4.0)), Num::Float(4.0));
assert_eq!(Num::Int(3).min(Num::Int(4)), Num::Int(3));
assert_eq!(Num::Int(-3).abs(), Num::Int(3));
}
}