Skip to main content

reifydb_value/value/number/safe/
remainder.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4pub trait SafeRemainder: Sized {
5	fn checked_rem(&self, r: &Self) -> Option<Self>;
6	fn saturating_rem(&self, r: &Self) -> Self;
7	fn wrapping_rem(&self, r: &Self) -> Self;
8	fn is_zero(&self) -> bool;
9}
10
11macro_rules! impl_safe_rem_signed {
12    ($($t:ty),*) => {
13        $(
14            impl SafeRemainder for $t {
15                fn checked_rem(&self, r: &Self) -> Option<Self> {
16                    if *r == 0 || (*self == <$t>::MIN && *r == -1) {
17                        None
18                    } else {
19                        Some(*self % *r)
20                    }
21                }
22                fn saturating_rem(&self, r: &Self) -> Self {
23                    if *r == 0 {
24                        0
25                    } else if *self == <$t>::MIN && *r == -1 {
26                        0
27                    } else {
28                        *self % *r
29                    }
30                }
31                fn wrapping_rem(&self, r: &Self) -> Self {
32                    if *r == 0 {
33                        0
34                    } else {
35                        (*self).wrapping_rem(*r)
36                    }
37                }
38                fn is_zero(&self) -> bool {
39                    *self == 0
40                }
41            }
42        )*
43    };
44}
45
46macro_rules! impl_safe_rem_unsigned {
47    ($($t:ty),*) => {
48        $(
49            impl SafeRemainder for $t {
50                fn checked_rem(&self, r: &Self) -> Option<Self> {
51                    if *r == 0 {
52                        None
53                    } else {
54                        Some(*self % *r)
55                    }
56                }
57                fn saturating_rem(&self, r: &Self) -> Self {
58                    if *r == 0 {
59                        0
60                    } else {
61                        *self % *r
62                    }
63                }
64                fn wrapping_rem(&self, r: &Self) -> Self {
65                    if *r == 0 {
66                        0
67                    } else {
68                        *self % *r
69                    }
70                }
71                fn is_zero(&self) -> bool {
72                    *self == 0
73                }
74            }
75        )*
76    };
77}
78
79impl_safe_rem_signed!(i8, i16, i32, i64, i128);
80impl_safe_rem_unsigned!(u8, u16, u32, u64, u128);
81
82use crate::value::decimal::Decimal;
83
84impl SafeRemainder for Decimal {
85	fn checked_rem(&self, r: &Self) -> Option<Self> {
86		Decimal::checked_rem(self, r)
87	}
88
89	fn saturating_rem(&self, r: &Self) -> Self {
90		Decimal::saturating_rem(self, r)
91	}
92
93	fn wrapping_rem(&self, r: &Self) -> Self {
94		Decimal::saturating_rem(self, r)
95	}
96
97	fn is_zero(&self) -> bool {
98		Decimal::is_zero(self)
99	}
100}
101
102impl SafeRemainder for f32 {
103	fn checked_rem(&self, r: &Self) -> Option<Self> {
104		if *r == 0.0 {
105			None
106		} else {
107			Some(*self % *r)
108		}
109	}
110
111	fn saturating_rem(&self, r: &Self) -> Self {
112		if *r == 0.0 || r.is_nan() || self.is_nan() {
113			0.0
114		} else {
115			let result = *self % *r;
116			if result.is_finite() {
117				result
118			} else {
119				0.0
120			}
121		}
122	}
123
124	fn wrapping_rem(&self, r: &Self) -> Self {
125		if *r == 0.0 {
126			0.0
127		} else {
128			let result = *self % *r;
129			if result.is_infinite() || result.is_nan() {
130				0.0
131			} else {
132				result
133			}
134		}
135	}
136
137	fn is_zero(&self) -> bool {
138		*self == 0.0
139	}
140}
141
142impl SafeRemainder for f64 {
143	fn checked_rem(&self, r: &Self) -> Option<Self> {
144		if *r == 0.0 {
145			None
146		} else {
147			Some(*self % *r)
148		}
149	}
150
151	fn saturating_rem(&self, r: &Self) -> Self {
152		if *r == 0.0 || r.is_nan() || self.is_nan() {
153			0.0
154		} else {
155			let result = *self % *r;
156			if result.is_finite() {
157				result
158			} else {
159				0.0
160			}
161		}
162	}
163
164	fn wrapping_rem(&self, r: &Self) -> Self {
165		if *r == 0.0 {
166			0.0
167		} else {
168			let result = *self % *r;
169			if result.is_infinite() || result.is_nan() {
170				0.0
171			} else {
172				result
173			}
174		}
175	}
176
177	fn is_zero(&self) -> bool {
178		*self == 0.0
179	}
180}
181
182#[cfg(test)]
183pub mod tests {
184	macro_rules! signed_unsigned {
185        ($($t:ty => $mod:ident),*) => {
186            $(
187                mod $mod {
188                    use super::super::SafeRemainder;
189
190                    #[test]
191                    fn checked_rem_happy() {
192                        let x: $t = 10;
193                        let y: $t = 3;
194                        assert_eq!(SafeRemainder::checked_rem(&x, &y), Some(1));
195                    }
196
197                    #[test]
198                    fn checked_rem_zero() {
199                        let x: $t = 10;
200                        let y: $t = 0;
201                        assert_eq!(SafeRemainder::checked_rem(&x, &y), None);
202                    }
203
204                    #[test]
205                    fn saturating_rem_happy() {
206                        let x: $t = 10;
207                        let y: $t = 3;
208                        assert_eq!(SafeRemainder::saturating_rem(&x, &y), 1);
209                    }
210
211                    #[test]
212                    fn saturating_rem_zero() {
213                        let x: $t = 10;
214                        let y: $t = 0;
215                        assert_eq!(SafeRemainder::saturating_rem(&x, &y), 0);
216                    }
217
218                    #[test]
219                    fn wrapping_rem_happy() {
220                        let x: $t = 10;
221                        let y: $t = 3;
222                        assert_eq!(SafeRemainder::wrapping_rem(&x, &y), 1);
223                    }
224
225                    #[test]
226                    fn wrapping_rem_zero() {
227                        let x: $t = 10;
228                        let y: $t = 0;
229                        assert_eq!(SafeRemainder::wrapping_rem(&x, &y), 0);
230                    }
231                }
232            )*
233        };
234    }
235
236	signed_unsigned!(
237	    i8 => i8,
238	    i16 => i16,
239	    i32 => i32,
240	    i64 => i64,
241	    i128 => i128,
242	    u8 => u8,
243	    u16 => u16,
244	    u32 => u32,
245	    u64 => u64,
246	    u128 => u128
247	);
248
249	mod signed_overflow {
250		use crate::value::number::safe::remainder::SafeRemainder;
251
252		#[test]
253		fn checked_rem_min_negative_one() {
254			assert_eq!(SafeRemainder::checked_rem(&i8::MIN, &-1), None);
255			assert_eq!(SafeRemainder::checked_rem(&i16::MIN, &-1), None);
256			assert_eq!(SafeRemainder::checked_rem(&i32::MIN, &-1), None);
257			assert_eq!(SafeRemainder::checked_rem(&i64::MIN, &-1), None);
258			assert_eq!(SafeRemainder::checked_rem(&i128::MIN, &-1), None);
259		}
260
261		#[test]
262		fn saturating_rem_min_negative_one() {
263			assert_eq!(SafeRemainder::saturating_rem(&i8::MIN, &-1), 0);
264			assert_eq!(SafeRemainder::saturating_rem(&i16::MIN, &-1), 0);
265			assert_eq!(SafeRemainder::saturating_rem(&i32::MIN, &-1), 0);
266			assert_eq!(SafeRemainder::saturating_rem(&i64::MIN, &-1), 0);
267			assert_eq!(SafeRemainder::saturating_rem(&i128::MIN, &-1), 0);
268		}
269
270		#[test]
271		fn wrapping_rem_min_negative_one() {
272			assert_eq!(SafeRemainder::wrapping_rem(&i8::MIN, &-1), 0);
273			assert_eq!(SafeRemainder::wrapping_rem(&i16::MIN, &-1), 0);
274			assert_eq!(SafeRemainder::wrapping_rem(&i32::MIN, &-1), 0);
275			assert_eq!(SafeRemainder::wrapping_rem(&i64::MIN, &-1), 0);
276			assert_eq!(SafeRemainder::wrapping_rem(&i128::MIN, &-1), 0);
277		}
278	}
279
280	mod f32 {
281		use crate::value::number::safe::remainder::SafeRemainder;
282
283		#[test]
284		fn checked_rem_happy() {
285			let x: f32 = 10.5;
286			let y: f32 = 3.0;
287			assert_eq!(SafeRemainder::checked_rem(&x, &y), Some(1.5));
288		}
289
290		#[test]
291		fn checked_rem_zero() {
292			let x: f32 = 10.0;
293			let y: f32 = 0.0;
294			assert_eq!(SafeRemainder::checked_rem(&x, &y), None);
295		}
296
297		#[test]
298		fn checked_rem_nan() {
299			// postgres treats NaN as an ordinary float8 value, so a NaN operand propagates rather than
300			// erroring.
301			let x: f32 = f32::NAN;
302			let y: f32 = 3.0;
303			assert!(SafeRemainder::checked_rem(&x, &y).unwrap().is_nan());
304
305			let x: f32 = 10.0;
306			let y: f32 = f32::NAN;
307			assert!(SafeRemainder::checked_rem(&x, &y).unwrap().is_nan());
308		}
309
310		#[test]
311		fn saturating_rem_happy() {
312			let x: f32 = 10.5;
313			let y: f32 = 3.0;
314			assert_eq!(SafeRemainder::saturating_rem(&x, &y), 1.5);
315		}
316
317		#[test]
318		fn saturating_rem_zero() {
319			let x: f32 = 10.0;
320			let y: f32 = 0.0;
321			assert_eq!(SafeRemainder::saturating_rem(&x, &y), 0.0);
322		}
323
324		#[test]
325		fn saturating_rem_nan() {
326			let x: f32 = f32::NAN;
327			let y: f32 = 3.0;
328			assert_eq!(SafeRemainder::saturating_rem(&x, &y), 0.0);
329		}
330
331		#[test]
332		fn wrapping_rem_happy() {
333			let x: f32 = 10.5;
334			let y: f32 = 3.0;
335			assert_eq!(SafeRemainder::wrapping_rem(&x, &y), 1.5);
336		}
337
338		#[test]
339		fn wrapping_rem_zero() {
340			let x: f32 = 10.0;
341			let y: f32 = 0.0;
342			assert_eq!(SafeRemainder::wrapping_rem(&x, &y), 0.0);
343		}
344
345		#[test]
346		fn wrapping_rem_infinity() {
347			let x: f32 = f32::INFINITY;
348			let y: f32 = 3.0;
349			let result = SafeRemainder::wrapping_rem(&x, &y);
350			assert_eq!(result, 0.0);
351		}
352	}
353
354	mod f64 {
355		use crate::value::number::safe::remainder::SafeRemainder;
356
357		#[test]
358		fn checked_rem_happy() {
359			let x: f64 = 10.5;
360			let y: f64 = 3.0;
361			assert_eq!(SafeRemainder::checked_rem(&x, &y), Some(1.5));
362		}
363
364		#[test]
365		fn checked_rem_zero() {
366			let x: f64 = 10.0;
367			let y: f64 = 0.0;
368			assert_eq!(SafeRemainder::checked_rem(&x, &y), None);
369		}
370
371		#[test]
372		fn checked_rem_nan() {
373			// postgres treats NaN as an ordinary float8 value, so a NaN operand propagates rather than
374			// erroring.
375			let x: f64 = f64::NAN;
376			let y: f64 = 3.0;
377			assert!(SafeRemainder::checked_rem(&x, &y).unwrap().is_nan());
378
379			let x: f64 = 10.0;
380			let y: f64 = f64::NAN;
381			assert!(SafeRemainder::checked_rem(&x, &y).unwrap().is_nan());
382		}
383
384		#[test]
385		fn saturating_rem_happy() {
386			let x: f64 = 10.5;
387			let y: f64 = 3.0;
388			assert_eq!(SafeRemainder::saturating_rem(&x, &y), 1.5);
389		}
390
391		#[test]
392		fn saturating_rem_zero() {
393			let x: f64 = 10.0;
394			let y: f64 = 0.0;
395			assert_eq!(SafeRemainder::saturating_rem(&x, &y), 0.0);
396		}
397
398		#[test]
399		fn saturating_rem_nan() {
400			let x: f64 = f64::NAN;
401			let y: f64 = 3.0;
402			assert_eq!(SafeRemainder::saturating_rem(&x, &y), 0.0);
403		}
404
405		#[test]
406		fn wrapping_rem_happy() {
407			let x: f64 = 10.5;
408			let y: f64 = 3.0;
409			assert_eq!(SafeRemainder::wrapping_rem(&x, &y), 1.5);
410		}
411
412		#[test]
413		fn wrapping_rem_zero() {
414			let x: f64 = 10.0;
415			let y: f64 = 0.0;
416			assert_eq!(SafeRemainder::wrapping_rem(&x, &y), 0.0);
417		}
418
419		#[test]
420		fn wrapping_rem_infinity() {
421			let x: f64 = f64::INFINITY;
422			let y: f64 = 3.0;
423			let result = SafeRemainder::wrapping_rem(&x, &y);
424			assert_eq!(result, 0.0);
425		}
426	}
427}