Skip to main content

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