opql/types/
pql_numeric.rs

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}