Skip to main content

nexus_decimal/
ops.rs

1//! Operator trait implementations for `Decimal`.
2//!
3//! Operators always panic on overflow in both debug and release builds.
4//! Use `checked_*` methods for explicit fallibility.
5
6use core::ops::{
7    Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign,
8};
9
10use crate::Decimal;
11
12#[cold]
13#[inline(never)]
14fn panic_overflow(op: &str) -> ! {
15    panic!("decimal {op} overflow")
16}
17
18#[cold]
19#[inline(never)]
20fn panic_div_zero() -> ! {
21    panic!("decimal division overflow or division by zero")
22}
23
24macro_rules! impl_decimal_ops {
25    ($backing:ty) => {
26        impl<const D: u8> Add for Decimal<$backing, D> {
27            type Output = Self;
28
29            #[inline(always)]
30            fn add(self, rhs: Self) -> Self {
31                match self.checked_add(rhs) {
32                    Some(v) => v,
33                    None => panic_overflow("addition"),
34                }
35            }
36        }
37
38        impl<const D: u8> Sub for Decimal<$backing, D> {
39            type Output = Self;
40
41            #[inline(always)]
42            fn sub(self, rhs: Self) -> Self {
43                match self.checked_sub(rhs) {
44                    Some(v) => v,
45                    None => panic_overflow("subtraction"),
46                }
47            }
48        }
49
50        impl<const D: u8> Neg for Decimal<$backing, D> {
51            type Output = Self;
52
53            #[inline(always)]
54            fn neg(self) -> Self {
55                match self.checked_neg() {
56                    Some(v) => v,
57                    None => panic_overflow("negation"),
58                }
59            }
60        }
61
62        impl<const D: u8> AddAssign for Decimal<$backing, D> {
63            #[inline(always)]
64            fn add_assign(&mut self, rhs: Self) {
65                *self = *self + rhs;
66            }
67        }
68
69        impl<const D: u8> SubAssign for Decimal<$backing, D> {
70            #[inline(always)]
71            fn sub_assign(&mut self, rhs: Self) {
72                *self = *self - rhs;
73            }
74        }
75    };
76}
77
78impl_decimal_ops!(i32);
79impl_decimal_ops!(i64);
80impl_decimal_ops!(i128);
81
82macro_rules! impl_decimal_mul_div_ops {
83    ($backing:ty) => {
84        impl<const D: u8> Mul for Decimal<$backing, D> {
85            type Output = Self;
86
87            #[inline(always)]
88            fn mul(self, rhs: Self) -> Self {
89                match self.checked_mul(rhs) {
90                    Some(v) => v,
91                    None => panic_overflow("multiplication"),
92                }
93            }
94        }
95
96        impl<const D: u8> Div for Decimal<$backing, D> {
97            type Output = Self;
98
99            #[inline(always)]
100            fn div(self, rhs: Self) -> Self {
101                match self.checked_div(rhs) {
102                    Some(v) => v,
103                    None => panic_div_zero(),
104                }
105            }
106        }
107
108        impl<const D: u8> MulAssign for Decimal<$backing, D> {
109            #[inline(always)]
110            fn mul_assign(&mut self, rhs: Self) {
111                *self = *self * rhs;
112            }
113        }
114
115        impl<const D: u8> DivAssign for Decimal<$backing, D> {
116            #[inline(always)]
117            fn div_assign(&mut self, rhs: Self) {
118                *self = *self / rhs;
119            }
120        }
121
122        impl<const D: u8> Rem for Decimal<$backing, D> {
123            type Output = Self;
124
125            /// Remainder on the raw scaled values (`self.value % rhs.value`).
126            ///
127            /// # Panics
128            ///
129            /// Panics if `rhs` is zero.
130            #[inline(always)]
131            fn rem(self, rhs: Self) -> Self {
132                if rhs.value == 0 {
133                    panic_div_zero()
134                }
135                Self {
136                    value: self.value % rhs.value,
137                }
138            }
139        }
140
141        impl<const D: u8> RemAssign for Decimal<$backing, D> {
142            #[inline(always)]
143            fn rem_assign(&mut self, rhs: Self) {
144                *self = *self % rhs;
145            }
146        }
147    };
148}
149
150impl_decimal_mul_div_ops!(i32);
151impl_decimal_mul_div_ops!(i64);
152impl_decimal_mul_div_ops!(i128);
153
154// ============================================================================
155// Sum and Product iterator traits
156// ============================================================================
157
158macro_rules! impl_decimal_iter_traits {
159    ($backing:ty) => {
160        impl<const D: u8> core::iter::Sum for Decimal<$backing, D> {
161            fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
162                iter.fold(Self::ZERO, |acc, x| acc + x)
163            }
164        }
165
166        impl<'a, const D: u8> core::iter::Sum<&'a Self> for Decimal<$backing, D> {
167            fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
168                iter.fold(Self::ZERO, |acc, &x| acc + x)
169            }
170        }
171
172        impl<const D: u8> core::iter::Product for Decimal<$backing, D> {
173            fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
174                iter.fold(Self::ONE, |acc, x| acc * x)
175            }
176        }
177
178        impl<'a, const D: u8> core::iter::Product<&'a Self> for Decimal<$backing, D> {
179            fn product<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
180                iter.fold(Self::ONE, |acc, &x| acc * x)
181            }
182        }
183    };
184}
185
186impl_decimal_iter_traits!(i32);
187impl_decimal_iter_traits!(i64);
188impl_decimal_iter_traits!(i128);
189
190// ============================================================================
191// From / TryFrom conversions
192// ============================================================================
193
194macro_rules! impl_decimal_from_traits {
195    ($backing:ty) => {
196        // `TryFrom<i64>` and `TryFrom<u64>` are emitted per-(backing, D) by
197        // `from_int.rs` — sound combinations get `From<IntType>` instead, which
198        // auto-implies `TryFrom` via the std blanket. See `src/from_int.rs`.
199
200        #[cfg(feature = "std")]
201        impl<const D: u8> TryFrom<f64> for Decimal<$backing, D> {
202            type Error = crate::error::ConvertError;
203
204            fn try_from(value: f64) -> Result<Self, Self::Error> {
205                Self::from_f64(value)
206            }
207        }
208
209        #[cfg(feature = "std")]
210        impl<const D: u8> TryFrom<f32> for Decimal<$backing, D> {
211            type Error = crate::error::ConvertError;
212
213            fn try_from(value: f32) -> Result<Self, Self::Error> {
214                Self::from_f32(value)
215            }
216        }
217    };
218}
219
220impl_decimal_from_traits!(i32);
221impl_decimal_from_traits!(i64);
222impl_decimal_from_traits!(i128);