1use std::ops::{Add, Div, Mul, Rem, Sub};
14
15#[derive(Debug, Clone, Copy, PartialEq)]
17pub enum Num {
18 Int(i64),
19 Float(f64),
20}
21
22impl Num {
23 pub fn as_f64(self) -> f64 {
25 match self {
26 Num::Int(n) => n as f64,
27 Num::Float(n) => n,
28 }
29 }
30
31 pub fn is_int(self) -> bool {
32 matches!(self, Num::Int(_))
33 }
34
35 fn combine(
39 self,
40 other: Num,
41 int_op: impl Fn(i64, i64) -> Option<i64>,
42 float_op: impl Fn(f64, f64) -> Option<f64>,
43 ) -> Option<Num> {
44 match (self, other) {
45 (Num::Int(a), Num::Int(b)) => int_op(a, b).map(Num::Int),
46 _ => float_op(self.as_f64(), other.as_f64()).map(Num::Float),
47 }
48 }
49
50 pub fn max(self, other: Num) -> Num {
52 self.combine(other, |a, b| Some(a.max(b)), |a, b| Some(a.max(b)))
53 .expect("max is total")
54 }
55
56 pub fn min(self, other: Num) -> Num {
58 self.combine(other, |a, b| Some(a.min(b)), |a, b| Some(a.min(b)))
59 .expect("min is total")
60 }
61
62 pub fn abs(self) -> Num {
64 match self {
65 Num::Int(n) => Num::Int(n.abs()),
66 Num::Float(n) => Num::Float(n.abs()),
67 }
68 }
69
70 pub fn checked_div(self, other: Num) -> Option<Num> {
73 self.combine(
74 other,
75 |a, b| (b != 0).then(|| a / b),
76 |a, b| (b != 0.0).then(|| a / b),
77 )
78 }
79
80 pub fn checked_rem(self, other: Num) -> Option<Num> {
82 self.combine(
83 other,
84 |a, b| (b != 0).then(|| a % b),
85 |a, b| (b != 0.0).then(|| a % b),
86 )
87 }
88}
89
90impl Add for Num {
91 type Output = Num;
92 fn add(self, other: Num) -> Num {
93 self.combine(other, |a, b| Some(a + b), |a, b| Some(a + b))
94 .expect("addition is total")
95 }
96}
97
98impl Sub for Num {
99 type Output = Num;
100 fn sub(self, other: Num) -> Num {
101 self.combine(other, |a, b| Some(a - b), |a, b| Some(a - b))
102 .expect("subtraction is total")
103 }
104}
105
106impl Mul for Num {
107 type Output = Num;
108 fn mul(self, other: Num) -> Num {
109 self.combine(other, |a, b| Some(a * b), |a, b| Some(a * b))
110 .expect("multiplication is total")
111 }
112}
113
114impl Div for Num {
115 type Output = Num;
116 fn div(self, other: Num) -> Num {
118 self.checked_div(other).unwrap_or(Num::Float(f64::NAN))
119 }
120}
121
122impl Rem for Num {
123 type Output = Num;
124 fn rem(self, other: Num) -> Num {
126 self.checked_rem(other).unwrap_or(Num::Float(f64::NAN))
127 }
128}
129
130impl From<i64> for Num {
131 fn from(n: i64) -> Self {
132 Num::Int(n)
133 }
134}
135
136impl From<f64> for Num {
137 fn from(n: f64) -> Self {
138 Num::Float(n)
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn two_ints_stay_an_int() {
148 assert_eq!(Num::Int(3) + Num::Int(4), Num::Int(7));
149 assert_eq!(Num::Int(3) * Num::Int(4), Num::Int(12));
150 assert_eq!(Num::Int(15).checked_div(Num::Int(2)), Some(Num::Int(7)));
152 }
153
154 #[test]
155 fn a_float_operand_widens_the_result() {
156 assert_eq!(
157 Num::Int(15).checked_div(Num::Float(2.0)),
158 Some(Num::Float(7.5))
159 );
160 assert_eq!(
161 Num::Float(15.0).checked_div(Num::Int(2)),
162 Some(Num::Float(7.5))
163 );
164 assert_eq!(Num::Int(3) + Num::Float(0.5), Num::Float(3.5));
165 }
166
167 #[test]
168 fn a_zero_divisor_has_no_result() {
169 assert_eq!(Num::Int(1).checked_div(Num::Int(0)), None);
170 assert_eq!(Num::Float(1.0).checked_div(Num::Float(0.0)), None);
171 assert_eq!(Num::Int(1).checked_rem(Num::Int(0)), None);
172 }
173
174 #[test]
175 fn max_and_min_keep_the_type() {
176 assert_eq!(Num::Int(3).max(Num::Int(4)), Num::Int(4));
177 assert_eq!(Num::Int(3).max(Num::Float(4.0)), Num::Float(4.0));
178 assert_eq!(Num::Int(3).min(Num::Int(4)), Num::Int(3));
179 assert_eq!(Num::Int(-3).abs(), Num::Int(3));
180 }
181}