ext_ops/
try_ops.rs

1/*
2 * Copyright (c) 2023 Martin Mills <daggerbot@gmail.com>
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7 */
8
9use crate::error::{ArithmeticError, Overflow, RangeError, Undefined, Underflow};
10
11/// Checked addition operator which returns a [Result] to indicate success or failure.
12pub trait TryAdd<Rhs = Self> {
13    type Output;
14    type Error;
15
16    fn try_add(self, rhs: Rhs) -> Result<Self::Output, Self::Error>;
17}
18
19/// Checked division operator which returns a [Result] to indicate success or failure.
20pub trait TryDiv<Rhs = Self> {
21    type Output;
22    type Error;
23
24    fn try_div(self, rhs: Rhs) -> Result<Self::Output, Self::Error>;
25}
26
27/// Checked multiplication operator which returns a [Result] to indicate success or failure.
28pub trait TryMul<Rhs = Self> {
29    type Output;
30    type Error;
31
32    fn try_mul(self, rhs: Rhs) -> Result<Self::Output, Self::Error>;
33}
34
35/// Checked negation operator which returns a [Result] to indicate success or failure.
36pub trait TryNeg {
37    type Output;
38    type Error;
39
40    fn try_neg(self) -> Result<Self::Output, Self::Error>;
41}
42
43/// Checked remainder operator which returns a [Result] to indicate success or failure.
44pub trait TryRem<Rhs = Self> {
45    type Output;
46    type Error;
47
48    fn try_rem(self, rhs: Rhs) -> Result<Self::Output, Self::Error>;
49}
50
51/// Checked subtraction operator which returns a [Result] to indicate success or failure.
52pub trait TrySub<Rhs = Self> {
53    type Output;
54    type Error;
55
56    fn try_sub(self, rhs: Rhs) -> Result<Self::Output, Self::Error>;
57}
58
59//--------------------------------------------------------------------------------------------------
60
61/// Implements unary operators for reference types.
62macro_rules! impl_unary_ref_ops {
63    { $(impl $trait:ident::$fn:ident for $ty:ident;)* } => { $(
64        impl<'a> $trait for &'a $ty {
65            type Output = $ty;
66            type Error = <$ty as $trait>::Error;
67
68            fn $fn(self) -> Result<$ty, Self::Error> {
69                $trait::$fn(*self)
70            }
71        }
72    )* };
73}
74
75/// Implements binary operators for reference types.
76macro_rules! impl_binary_ref_ops {
77    { $(impl $trait:ident::$fn:ident for $ty:ident;)* } => { $(
78        impl<'a> $trait<$ty> for &'a $ty {
79            type Output = $ty;
80            type Error = <$ty as $trait>::Error;
81
82            fn $fn(self, rhs: $ty) -> Result<$ty, Self::Error> {
83                $trait::$fn(*self, rhs)
84            }
85        }
86
87        impl<'r> $trait<&'r $ty> for $ty {
88            type Output = $ty;
89            type Error = <$ty as $trait>::Error;
90
91            fn $fn(self, rhs: &'r $ty) -> Result<$ty, Self::Error> {
92                $trait::$fn(self, *rhs)
93            }
94        }
95
96        impl<'a, 'r> $trait<&'r $ty> for &'a $ty {
97            type Output = $ty;
98            type Error = <$ty as $trait>::Error;
99
100            fn $fn(self, rhs: &'r $ty) -> Result<$ty, Self::Error> {
101                $trait::$fn(*self, *rhs)
102            }
103        }
104    )* };
105}
106
107/// Implements checked operators for signed integer types.
108macro_rules! impl_int_ops {
109    ($($ty:ident),*) => { $(
110        impl TryAdd for $ty {
111            type Output = $ty;
112            type Error = RangeError;
113
114            fn try_add(self, rhs: $ty) -> Result<$ty, RangeError> {
115                match self.checked_add(rhs) {
116                    None => Err(if self >= 0 {
117                        RangeError::Overflow
118                    } else {
119                        RangeError::Underflow
120                    }),
121                    Some(n) => Ok(n),
122                }
123            }
124        }
125
126        impl TryDiv for $ty {
127            type Output = $ty;
128            type Error = ArithmeticError;
129
130            fn try_div(self, rhs: $ty) -> Result<$ty, ArithmeticError> {
131                match self.checked_div(rhs) {
132                    None => Err(if rhs == 0 {
133                        ArithmeticError::Undefined
134                    } else {
135                        // Only reachable if self == $ty::MIN && rhs == -1.
136                        ArithmeticError::Overflow
137                    }),
138                    Some(n) => Ok(n),
139                }
140            }
141        }
142
143        impl TryMul for $ty {
144            type Output = $ty;
145            type Error = RangeError;
146
147            fn try_mul(self, rhs: $ty) -> Result<$ty, RangeError> {
148                match self.checked_mul(rhs) {
149                    None => Err(if (self >= 0) == (rhs >= 0) {
150                        RangeError::Overflow
151                    } else {
152                        RangeError::Underflow
153                    }),
154                    Some(n) => Ok(n),
155                }
156            }
157        }
158
159        impl TryNeg for $ty {
160            type Output = $ty;
161            type Error = Overflow;
162
163            fn try_neg(self) -> Result<$ty, Overflow> {
164                match self.checked_neg() {
165                    None => Err(Overflow),
166                    Some(n) => Ok(n),
167                }
168            }
169        }
170
171        impl TryRem for $ty {
172            type Output = $ty;
173            type Error = Undefined;
174
175            fn try_rem(self, rhs: $ty) -> Result<$ty, Undefined> {
176                match self.checked_rem(rhs) {
177                    None => if rhs == 0 {
178                        Err(Undefined)
179                    } else {
180                        // Only reachable if self == $ty::MIN && rhs == -1. Accepted because we know
181                        // what the result would be if division did not result in an overflow.
182                        Ok(0)
183                    },
184                    Some(n) => Ok(n),
185                }
186            }
187        }
188
189        impl TrySub for $ty {
190            type Output = $ty;
191            type Error = RangeError;
192
193            fn try_sub(self, rhs: $ty) -> Result<$ty, RangeError> {
194                match self.checked_sub(rhs) {
195                    None => Err(if self >= 0 {
196                        RangeError::Overflow
197                    } else {
198                        RangeError::Underflow
199                    }),
200                    Some(n) => Ok(n),
201                }
202            }
203        }
204
205        impl_unary_ref_ops! {
206            impl TryNeg::try_neg for $ty;
207        }
208
209        impl_binary_ref_ops! {
210            impl TryAdd::try_add for $ty;
211            impl TryDiv::try_div for $ty;
212            impl TryMul::try_mul for $ty;
213            impl TryRem::try_rem for $ty;
214            impl TrySub::try_sub for $ty;
215        }
216    )* };
217}
218
219impl_int_ops!(i8, i16, i32, i64, i128, isize);
220
221/// Implements checked operators for unsigned integer types.
222macro_rules! impl_uint_ops {
223    ($($ty:ident),*) => { $(
224        impl TryAdd for $ty {
225            type Output = $ty;
226            type Error = Overflow;
227
228            fn try_add(self, rhs: $ty) -> Result<$ty, Overflow> {
229                match self.checked_add(rhs) {
230                    None => Err(Overflow),
231                    Some(n) => Ok(n),
232                }
233            }
234        }
235
236        impl TryDiv for $ty {
237            type Output = $ty;
238            type Error = Undefined;
239
240            fn try_div(self, rhs: $ty) -> Result<$ty, Undefined> {
241                match self.checked_div(rhs) {
242                    None => Err(Undefined),
243                    Some(n) => Ok(n),
244                }
245            }
246        }
247
248        impl TryMul for $ty {
249            type Output = $ty;
250            type Error = Overflow;
251
252            fn try_mul(self, rhs: $ty) -> Result<$ty, Overflow> {
253                match self.checked_mul(rhs) {
254                    None => Err(Overflow),
255                    Some(n) => Ok(n),
256                }
257            }
258        }
259
260        impl TryNeg for $ty {
261            type Output = $ty;
262            type Error = Underflow;
263
264            fn try_neg(self) -> Result<$ty, Underflow> {
265                match self.checked_neg() {
266                    None => Err(Underflow),
267                    Some(n) => Ok(n),
268                }
269            }
270        }
271
272        impl TryRem for $ty {
273            type Output = $ty;
274            type Error = Undefined;
275
276            fn try_rem(self, rhs: $ty) -> Result<$ty, Undefined> {
277                match self.checked_rem(rhs) {
278                    None => Err(Undefined),
279                    Some(n) => Ok(n),
280                }
281            }
282        }
283
284        impl TrySub for $ty {
285            type Output = $ty;
286            type Error = Underflow;
287
288            fn try_sub(self, rhs: $ty) -> Result<$ty, Underflow> {
289                match self.checked_sub(rhs) {
290                    None => Err(Underflow),
291                    Some(n) => Ok(n),
292                }
293            }
294        }
295
296        impl_unary_ref_ops! {
297            impl TryNeg::try_neg for $ty;
298        }
299
300        impl_binary_ref_ops! {
301            impl TryAdd::try_add for $ty;
302            impl TryDiv::try_div for $ty;
303            impl TryMul::try_mul for $ty;
304            impl TryRem::try_rem for $ty;
305            impl TrySub::try_sub for $ty;
306        }
307    )* };
308}
309
310impl_uint_ops!(u8, u16, u32, u64, u128, usize);
311
312//--------------------------------------------------------------------------------------------------
313
314#[test]
315fn test_try_add() {
316    assert_eq!(i8::try_add(100, 27), Ok(127));
317    assert_eq!(i8::try_add(100, 28), Err(RangeError::Overflow));
318    assert_eq!(i8::try_add(-100, -28), Ok(-128));
319    assert_eq!(i8::try_add(-100, -29), Err(RangeError::Underflow));
320    assert_eq!(u8::try_add(200, 55), Ok(255));
321    assert_eq!(u8::try_add(200, 56), Err(Overflow));
322}
323
324#[test]
325fn test_try_div() {
326    assert_eq!(i8::try_div(100, 10), Ok(10));
327    assert_eq!(i8::try_div(100, 0), Err(ArithmeticError::Undefined));
328    assert_eq!(i8::try_div(-128, -1), Err(ArithmeticError::Overflow));
329    assert_eq!(u8::try_div(100, 10), Ok(10));
330    assert_eq!(u8::try_div(100, 0), Err(Undefined));
331}
332
333#[test]
334fn test_try_mul() {
335    assert_eq!(i8::try_mul(15, 8), Ok(120));
336    assert_eq!(i8::try_mul(16, 8), Err(RangeError::Overflow));
337    assert_eq!(i8::try_mul(16, -8), Ok(-128));
338    assert_eq!(i8::try_mul(43, -3), Err(RangeError::Underflow));
339    assert_eq!(i8::try_mul(-127, -1), Ok(127));
340    assert_eq!(i8::try_mul(-128, -1), Err(RangeError::Overflow));
341    assert_eq!(u8::try_mul(85, 3), Ok(255));
342    assert_eq!(u8::try_mul(16, 16), Err(Overflow));
343}
344
345#[test]
346fn test_try_neg() {
347    assert_eq!(i8::try_neg(127), Ok(-127));
348    assert_eq!(i8::try_neg(-128), Err(Overflow));
349    assert_eq!(u8::try_neg(0), Ok(0));
350    assert_eq!(u8::try_neg(1), Err(Underflow));
351}
352
353#[test]
354fn test_try_rem() {
355    assert_eq!(i8::try_rem(99, 10), Ok(9));
356    assert_eq!(i8::try_rem(99, -10), Ok(9));
357    assert_eq!(i8::try_rem(-99, 10), Ok(-9));
358    assert_eq!(i8::try_rem(-99, -10), Ok(-9));
359    assert_eq!(i8::try_rem(-128, -1), Ok(0)); // Division would overflow.
360    assert_eq!(i8::try_rem(99, 0), Err(Undefined));
361    assert_eq!(u8::try_rem(99, 10), Ok(9));
362    assert_eq!(u8::try_rem(99, 0), Err(Undefined));
363}
364
365#[test]
366fn test_try_sub() {
367    assert_eq!(i8::try_sub(0, -127), Ok(127));
368    assert_eq!(i8::try_sub(0, -128), Err(RangeError::Overflow));
369    assert_eq!(i8::try_sub(-1, 127), Ok(-128));
370    assert_eq!(i8::try_sub(-2, 127), Err(RangeError::Underflow));
371    assert_eq!(u8::try_sub(100, 100), Ok(0));
372    assert_eq!(u8::try_sub(0, 1), Err(Underflow));
373}