1use super::*;
2
3#[derive(Debug, Clone, Copy, PartialEq)]
4pub enum PQLNumeric {
5 Count(PQLCardCount),
6 Long(PQLLong),
7 Double(PQLDouble),
8 Frac(PQLFraction),
9}
10
11const fn int_add(
12 lhs: PQLLong,
13 rhs: PQLLong,
14) -> Result<PQLNumeric, RuntimeError> {
15 match lhs.checked_add(rhs) {
16 Some(v) => Ok(PQLNumeric::Long(v)),
17 None => Err(RuntimeError::AddOverflow),
18 }
19}
20
21const fn int_sub(
22 lhs: PQLLong,
23 rhs: PQLLong,
24) -> Result<PQLNumeric, RuntimeError> {
25 match lhs.checked_sub(rhs) {
26 Some(v) => Ok(PQLNumeric::Long(v)),
27 None => Err(RuntimeError::SubOverflow),
28 }
29}
30
31const fn int_mul(
32 lhs: PQLLong,
33 rhs: PQLLong,
34) -> Result<PQLNumeric, RuntimeError> {
35 match lhs.checked_mul(rhs) {
36 Some(v) => Ok(PQLNumeric::Long(v)),
37 None => Err(RuntimeError::MulOverflow),
38 }
39}
40
41#[allow(clippy::unnecessary_wraps)]
42const fn dbl_add(
43 lhs: PQLDouble,
44 rhs: PQLDouble,
45) -> Result<PQLNumeric, RuntimeError> {
46 Ok(PQLNumeric::Double(lhs + rhs))
47}
48
49#[allow(clippy::unnecessary_wraps)]
50const fn dbl_sub(
51 lhs: PQLDouble,
52 rhs: PQLDouble,
53) -> Result<PQLNumeric, RuntimeError> {
54 Ok(PQLNumeric::Double(lhs - rhs))
55}
56
57#[allow(clippy::unnecessary_wraps)]
58const fn dbl_mul(
59 lhs: PQLDouble,
60 rhs: PQLDouble,
61) -> Result<PQLNumeric, RuntimeError> {
62 Ok(PQLNumeric::Double(lhs * rhs))
63}
64
65#[allow(clippy::unnecessary_wraps)]
66const fn dbl_div(
67 lhs: PQLDouble,
68 rhs: PQLDouble,
69) -> Result<PQLNumeric, RuntimeError> {
70 Ok(PQLNumeric::Double(lhs / rhs))
71}
72
73impl PQLNumeric {
74 pub fn try_add(self, other: Self) -> Result<Self, RuntimeError> {
75 if self.is_int() && other.is_int() {
76 int_add(self.to_int(), other.to_int())
77 } else {
78 dbl_add(self.to_dbl(), other.to_dbl())
79 }
80 }
81
82 pub fn try_sub(self, other: Self) -> Result<Self, RuntimeError> {
83 if self.is_int() && other.is_int() {
84 int_sub(self.to_int(), other.to_int())
85 } else {
86 dbl_sub(self.to_dbl(), other.to_dbl())
87 }
88 }
89
90 pub fn try_mul(self, other: Self) -> Result<Self, RuntimeError> {
91 if self.is_int() && other.is_int() {
92 int_mul(self.to_int(), other.to_int())
93 } else {
94 dbl_mul(self.to_dbl(), other.to_dbl())
95 }
96 }
97
98 pub const fn try_div(self, other: Self) -> Result<Self, RuntimeError> {
99 dbl_div(self.to_dbl(), other.to_dbl())
100 }
101
102 pub fn partial_compare(self, other: Self) -> Option<cmp::Ordering> {
103 if self.is_int() && other.is_int() {
104 Some(self.to_int().cmp(&other.to_int()))
105 } else {
106 self.to_dbl().partial_cmp(&other.to_dbl())
107 }
108 }
109
110 const fn is_int(self) -> bool {
111 matches!(self, Self::Count(_) | Self::Long(_))
112 }
113
114 fn to_int(self) -> PQLLong {
115 match self {
116 Self::Count(v) => PQLLong::from(v),
117 Self::Long(v) => v,
118 _ => unreachable!(),
119 }
120 }
121
122 #[allow(clippy::cast_lossless)]
123 #[allow(clippy::cast_precision_loss)]
124 pub const fn to_dbl(self) -> PQLDouble {
125 match self {
126 Self::Count(v) => v as PQLDouble,
127 Self::Long(v) => v as PQLDouble,
128 Self::Double(v) => v,
129 Self::Frac(v) => v.to_double(),
130 }
131 }
132}
133
134#[cfg(test)]
135#[cfg_attr(coverage_nightly, coverage(off))]
136pub mod tests {
137 use super::*;
138 use crate::*;
139
140 fn cnt_(v: PQLCardCount) -> PQLNumeric {
141 PQLNumeric::Count(v)
142 }
143
144 fn long(v: PQLLong) -> PQLNumeric {
145 PQLNumeric::Long(v)
146 }
147
148 fn dbl_(v: i8) -> PQLNumeric {
149 PQLNumeric::Double(PQLDouble::from(v))
150 }
151
152 fn frac(v: FractionInner) -> PQLNumeric {
153 PQLNumeric::Frac(PQLFraction::new(v, 1))
154 }
155
156 #[test]
157 fn test_add() {
158 let op = PQLNumeric::try_add;
159 assert_eq!(op(cnt_(1), cnt_(2)), Ok(long(3)));
160 assert_eq!(op(cnt_(1), long(2)), Ok(long(3)));
161 assert_eq!(op(cnt_(1), frac(2)), Ok(dbl_(3)));
162 assert_eq!(op(cnt_(1), dbl_(2)), Ok(dbl_(3)));
163
164 assert_eq!(op(long(1), long(2)), Ok(long(3)));
165 assert_eq!(op(long(1), frac(2)), Ok(dbl_(3)));
166 assert_eq!(op(long(1), dbl_(2)), Ok(dbl_(3)));
167
168 assert_eq!(op(frac(1), frac(2)), Ok(dbl_(3)));
169 assert_eq!(op(frac(1), dbl_(2)), Ok(dbl_(3)));
170
171 assert_eq!(op(dbl_(1), dbl_(2)), Ok(dbl_(3)));
172 }
173
174 #[test]
175 fn test_sub() {
176 let op = PQLNumeric::try_sub;
177 assert_eq!(op(cnt_(1), cnt_(2)), Ok(long(-1)));
178 assert_eq!(op(cnt_(1), long(2)), Ok(long(-1)));
179 assert_eq!(op(cnt_(1), frac(2)), Ok(dbl_(-1)));
180 assert_eq!(op(cnt_(1), dbl_(2)), Ok(dbl_(-1)));
181
182 assert_eq!(op(long(1), long(2)), Ok(long(-1)));
183 assert_eq!(op(long(1), frac(2)), Ok(dbl_(-1)));
184 assert_eq!(op(long(1), dbl_(2)), Ok(dbl_(-1)));
185
186 assert_eq!(op(frac(1), frac(2)), Ok(dbl_(-1)));
187 assert_eq!(op(frac(1), dbl_(2)), Ok(dbl_(-1)));
188
189 assert_eq!(op(dbl_(1), dbl_(2)), Ok(dbl_(-1)));
190 }
191
192 #[test]
193 fn test_mul() {
194 let op = PQLNumeric::try_mul;
195 assert_eq!(op(cnt_(1), cnt_(2)), Ok(long(2)));
196 assert_eq!(op(cnt_(1), long(2)), Ok(long(2)));
197 assert_eq!(op(cnt_(1), frac(2)), Ok(dbl_(2)));
198 assert_eq!(op(cnt_(1), dbl_(2)), Ok(dbl_(2)));
199
200 assert_eq!(op(long(1), long(2)), Ok(long(2)));
201 assert_eq!(op(long(1), frac(2)), Ok(dbl_(2)));
202 assert_eq!(op(long(1), dbl_(2)), Ok(dbl_(2)));
203
204 assert_eq!(op(frac(1), frac(2)), Ok(dbl_(2)));
205 assert_eq!(op(frac(1), dbl_(2)), Ok(dbl_(2)));
206
207 assert_eq!(op(dbl_(1), dbl_(2)), Ok(dbl_(2)));
208 }
209
210 #[test]
211 fn test_div() {
212 let op = PQLNumeric::try_div;
213 let half = PQLNumeric::Double(0.5);
214 assert_eq!(op(cnt_(1), cnt_(2)), Ok(half));
215 assert_eq!(op(cnt_(1), long(2)), Ok(half));
216 assert_eq!(op(cnt_(1), frac(2)), Ok(half));
217 assert_eq!(op(cnt_(1), dbl_(2)), Ok(half));
218
219 assert_eq!(op(long(1), long(2)), Ok(half));
220 assert_eq!(op(long(1), frac(2)), Ok(half));
221 assert_eq!(op(long(1), dbl_(2)), Ok(half));
222
223 assert_eq!(op(frac(1), frac(2)), Ok(half));
224 assert_eq!(op(frac(1), dbl_(2)), Ok(half));
225
226 assert_eq!(op(dbl_(1), dbl_(2)), Ok(half));
227 }
228
229 #[test]
230 fn test_err() {
231 assert_eq!(
232 long(PQLLong::MAX).try_add(cnt_(1)),
233 Err(RuntimeError::AddOverflow)
234 );
235
236 assert_eq!(
237 long(PQLLong::MIN).try_sub(cnt_(1)),
238 Err(RuntimeError::SubOverflow)
239 );
240
241 assert_eq!(
242 long(PQLLong::MIN).try_mul(cnt_(2)),
243 Err(RuntimeError::MulOverflow)
244 );
245 }
246
247 #[test]
248 #[should_panic(expected = "")]
249 fn test_internal() {
250 dbl_(1).to_int();
251 }
252}