Skip to main content

reifydb_value/value/number/safe/
div.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4pub trait SafeDiv: Sized {
5	fn checked_div(&self, r: &Self) -> Option<Self>;
6	fn saturating_div(&self, r: &Self) -> Self;
7	fn wrapping_div(&self, r: &Self) -> Self;
8	fn is_zero(&self) -> bool;
9}
10
11macro_rules! impl_safe_div_signed {
12    ($($t:ty),*) => {
13        $(
14            impl SafeDiv for $t {
15                fn checked_div(&self, r: &Self) -> Option<Self> {
16                    <$t>::checked_div(*self, *r)
17                }
18                fn saturating_div(&self, r: &Self) -> Self {
19                    match <$t>::checked_div(*self, *r) {
20                        Some(result) => result,
21                        None => {
22                            if *r == 0 {
23                                0
24                            } else {
25                                <$t>::MAX
26                            }
27                        }
28                    }
29                }
30                fn wrapping_div(&self, r: &Self) -> Self {
31                    if *r == 0 { 0 } else { <$t>::wrapping_div(*self, *r) }
32                }
33                fn is_zero(&self) -> bool {
34                    *self == 0
35                }
36            }
37        )*
38    };
39}
40
41macro_rules! impl_safe_div_unsigned {
42    ($($t:ty),*) => {
43        $(
44            impl SafeDiv for $t {
45                fn checked_div(&self, r: &Self) -> Option<Self> {
46                    <$t>::checked_div(*self, *r)
47                }
48                fn saturating_div(&self, r: &Self) -> Self {
49                    match <$t>::checked_div(*self, *r) {
50                        Some(result) => result,
51                        None => 0
52                    }
53                }
54                fn wrapping_div(&self, r: &Self) -> Self {
55                   if *r == 0 { 0 } else { <$t>::wrapping_div(*self, *r) }
56                }
57                fn is_zero(&self) -> bool {
58                    *self == 0
59                }
60            }
61        )*
62    };
63}
64
65impl_safe_div_signed!(i8, i16, i32, i64, i128);
66impl_safe_div_unsigned!(u8, u16, u32, u64, u128);
67
68use bigdecimal::{BigDecimal, Zero};
69use num_bigint::BigInt;
70
71use crate::value::{decimal::Decimal, int::Int, uint::Uint};
72
73impl SafeDiv for Int {
74	fn checked_div(&self, r: &Self) -> Option<Self> {
75		if r.0 == BigInt::from(0) {
76			None
77		} else {
78			Some(Int::from(&self.0 / &r.0))
79		}
80	}
81
82	fn saturating_div(&self, r: &Self) -> Self {
83		if r.0 == BigInt::from(0) {
84			self.clone()
85		} else {
86			Int::from(&self.0 / &r.0)
87		}
88	}
89
90	fn wrapping_div(&self, r: &Self) -> Self {
91		if r.0 == BigInt::from(0) {
92			Int::from(0)
93		} else {
94			Int::from(&self.0 / &r.0)
95		}
96	}
97
98	fn is_zero(&self) -> bool {
99		self.0 == BigInt::from(0)
100	}
101}
102
103impl SafeDiv for Uint {
104	fn checked_div(&self, r: &Self) -> Option<Self> {
105		if r.0 == BigInt::from(0) {
106			None
107		} else {
108			Some(Uint::from(&self.0 / &r.0))
109		}
110	}
111
112	fn saturating_div(&self, r: &Self) -> Self {
113		if r.0 == BigInt::from(0) {
114			self.clone()
115		} else {
116			Uint::from(&self.0 / &r.0)
117		}
118	}
119
120	fn wrapping_div(&self, r: &Self) -> Self {
121		if r.0 == BigInt::from(0) {
122			Uint::from(0u64)
123		} else {
124			Uint::from(&self.0 / &r.0)
125		}
126	}
127
128	fn is_zero(&self) -> bool {
129		self.0 == BigInt::from(0)
130	}
131}
132
133impl SafeDiv for Decimal {
134	fn checked_div(&self, r: &Self) -> Option<Self> {
135		if r.inner().is_zero() {
136			None
137		} else {
138			let result = self.inner() / r.inner();
139			Some(Decimal::from(result))
140		}
141	}
142
143	fn saturating_div(&self, r: &Self) -> Self {
144		if r.inner().is_zero() {
145			self.clone()
146		} else {
147			let result = self.inner() / r.inner();
148			Decimal::from(result)
149		}
150	}
151
152	fn wrapping_div(&self, r: &Self) -> Self {
153		if r.inner().is_zero() {
154			Decimal::from(BigDecimal::from(0))
155		} else {
156			let result = self.inner() / r.inner();
157			Decimal::from(result)
158		}
159	}
160
161	fn is_zero(&self) -> bool {
162		self.inner().is_zero()
163	}
164}
165
166impl SafeDiv for f32 {
167	fn checked_div(&self, r: &Self) -> Option<Self> {
168		let result = *self / *r;
169		if result.is_finite() {
170			Some(result)
171		} else {
172			None
173		}
174	}
175
176	fn saturating_div(&self, r: &Self) -> Self {
177		let result = *self / *r;
178		if result.is_infinite() {
179			if result.is_sign_positive() {
180				f32::MAX
181			} else {
182				f32::MIN
183			}
184		} else {
185			result
186		}
187	}
188
189	fn wrapping_div(&self, r: &Self) -> Self {
190		let result = *self / *r;
191		if result.is_finite() {
192			result
193		} else {
194			0.0
195		}
196	}
197
198	fn is_zero(&self) -> bool {
199		*self == 0.0
200	}
201}
202
203impl SafeDiv for f64 {
204	fn checked_div(&self, r: &Self) -> Option<Self> {
205		let result = *self / *r;
206		if result.is_finite() {
207			Some(result)
208		} else {
209			None
210		}
211	}
212
213	fn saturating_div(&self, r: &Self) -> Self {
214		let result = *self / *r;
215		if result.is_infinite() {
216			if result.is_sign_positive() {
217				f64::MAX
218			} else {
219				f64::MIN
220			}
221		} else {
222			result
223		}
224	}
225
226	fn wrapping_div(&self, r: &Self) -> Self {
227		let result = *self / *r;
228		if result.is_finite() {
229			result
230		} else {
231			0.0
232		}
233	}
234
235	fn is_zero(&self) -> bool {
236		*self == 0.0
237	}
238}
239
240#[cfg(test)]
241pub mod tests {
242	macro_rules! signed {
243        ($($t:ty => $mod:ident),*) => {
244            $(
245                mod $mod {
246                    use super::super::SafeDiv;
247
248                    #[test]
249                    fn checked_div_happy() {
250                        let x: $t = 20;
251                        let y: $t = 2;
252                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(10));
253                    }
254
255                    #[test]
256                    fn checked_div_unhappy() {
257                        let x: $t = 10;
258                        let y: $t = 0;
259                        assert_eq!(SafeDiv::checked_div(&x, &y), None);
260                    }
261
262                    #[test]
263                    fn saturating_div_happy() {
264                        let x: $t = 20;
265                        let y: $t = 2;
266                        assert_eq!(SafeDiv::saturating_div(&x, &y), 10);
267                    }
268
269                    #[test]
270                    fn saturating_div_unhappy() {
271                        let x: $t = 10;
272                        let y: $t = 0;
273                        // Division by zero has no saturation bound to pick, so it yields 0.
274                        let result = SafeDiv::saturating_div(&x, &y);
275                        assert_eq!(result, 0);
276                    }
277
278                    #[test]
279                    fn saturating_div_negative() {
280                        let x: $t = -10;
281                        let y: $t = 0;
282                        // Division by zero has no saturation bound to pick, so it yields 0.
283                        let result = SafeDiv::saturating_div(&x, &y);
284                        assert_eq!(result, 0);
285                    }
286
287                    #[test]
288                    fn wrapping_div_happy() {
289                        let x: $t = 20;
290                        let y: $t = 2;
291                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 10);
292                    }
293
294                    #[test]
295                    fn wrapping_div_unhappy() {
296                        let x: $t = <$t>::MIN;
297                        let y: $t = -1;
298                        // MIN / -1 is the one signed division that overflows; wrapping lands on MIN.
299                        let result = SafeDiv::wrapping_div(&x, &y);
300                        assert_eq!(result, <$t>::MIN);
301                    }
302
303                    #[test]
304                    fn div_small_by_large() {
305                        let x: $t = 5;
306                        let y: $t = <$t>::MAX;
307
308                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(0));
309                        assert_eq!(SafeDiv::saturating_div(&x, &y), 0);
310                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 0);
311                    }
312                }
313            )*
314        };
315    }
316
317	macro_rules! unsigned {
318        ($($t:ty => $mod:ident),*) => {
319            $(
320                mod $mod {
321                    use super::super::SafeDiv;
322
323                    #[test]
324                    fn checked_div_happy() {
325                        let x: $t = 20;
326                        let y: $t = 2;
327                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(10));
328                    }
329
330                    #[test]
331                    fn checked_div_unhappy() {
332                        let x: $t = 10;
333                        let y: $t = 0;
334                        assert_eq!(SafeDiv::checked_div(&x, &y), None);
335                    }
336
337                    #[test]
338                    fn saturating_div_happy() {
339                        let x: $t = 20;
340                        let y: $t = 2;
341                        assert_eq!(SafeDiv::saturating_div(&x, &y), 10);
342                    }
343
344                    #[test]
345                    fn saturating_div_unhappy() {
346                        let x: $t = 10;
347                        let y: $t = 0;
348                        // Division by zero has no saturation bound to pick, so it yields 0.
349                        let result = SafeDiv::saturating_div(&x, &y);
350                        assert_eq!(result, 0);
351                    }
352
353                    #[test]
354                    fn wrapping_div_happy() {
355                        let x: $t = 20;
356                        let y: $t = 2;
357                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 10);
358                    }
359
360                    #[test]
361                    fn wrapping_div_unhappy() {
362                        let x: $t = 10;
363                        let y: $t = 0;
364                        let result = SafeDiv::wrapping_div(&x, &y);
365                        assert_eq!(result, 0);
366                    }
367
368                    #[test]
369                    fn div_small_by_large() {
370                        let x: $t = 5;
371                        let y: $t = <$t>::MAX;
372
373                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(0));
374                        assert_eq!(SafeDiv::saturating_div(&x, &y), 0);
375                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 0);
376                    }
377                }
378            )*
379        };
380    }
381
382	signed!(
383	    i8 => i8,
384	    i16 => i16,
385	    i32 => i32,
386	    i64 => i64,
387	    i128 => i128
388	);
389
390	unsigned!(
391	    u8 => u8,
392	    u16 => u16,
393	    u32 => u32,
394	    u64 => u64,
395	    u128 => u128
396	);
397
398	mod f32 {
399		use super::super::SafeDiv;
400
401		#[test]
402		fn checked_div_happy() {
403			let x: f32 = 20.0;
404			let y: f32 = 2.0;
405			assert_eq!(SafeDiv::checked_div(&x, &y), Some(10.0));
406		}
407
408		#[test]
409		fn checked_div_unhappy() {
410			let x: f32 = f32::MAX;
411			let y: f32 = 0.1;
412			assert_eq!(SafeDiv::checked_div(&x, &y), None);
413		}
414
415		#[test]
416		fn saturating_div_happy() {
417			let x: f32 = 20.0;
418			let y: f32 = 2.0;
419			assert_eq!(SafeDiv::saturating_div(&x, &y), 10.0);
420		}
421
422		#[test]
423		fn saturating_div_unhappy() {
424			let x: f32 = f32::MAX;
425			let y: f32 = 0.1;
426			assert_eq!(SafeDiv::saturating_div(&x, &y), f32::MAX);
427		}
428
429		#[test]
430		fn wrapping_div_happy() {
431			let x: f32 = 20.0;
432			let y: f32 = 2.0;
433			assert_eq!(SafeDiv::wrapping_div(&x, &y), 10.0);
434		}
435
436		#[test]
437		fn wrapping_div_unhappy() {
438			let x: f32 = f32::MAX;
439			let y: f32 = 0.1;
440			let result = SafeDiv::wrapping_div(&x, &y);
441			assert!(result.is_finite());
442			assert_eq!(result, 0.0);
443		}
444
445		#[test]
446		fn wrapping_div_negative() {
447			let x: f32 = f32::MAX;
448			let y: f32 = -0.1;
449			let result = SafeDiv::wrapping_div(&x, &y);
450			assert!(result.is_finite());
451			assert_eq!(result, 0.0);
452		}
453	}
454
455	mod f64 {
456		use super::super::SafeDiv;
457
458		#[test]
459		fn checked_div_happy() {
460			let x: f64 = 20.0;
461			let y: f64 = 2.0;
462			assert_eq!(SafeDiv::checked_div(&x, &y), Some(10.0));
463		}
464
465		#[test]
466		fn checked_div_unhappy() {
467			let x: f64 = f64::MAX;
468			let y: f64 = 0.1;
469			assert_eq!(SafeDiv::checked_div(&x, &y), None);
470		}
471
472		#[test]
473		fn saturating_div_happy() {
474			let x: f64 = 20.0;
475			let y: f64 = 2.0;
476			assert_eq!(SafeDiv::saturating_div(&x, &y), 10.0);
477		}
478
479		#[test]
480		fn saturating_div_unhappy() {
481			let x: f64 = f64::MAX;
482			let y: f64 = 0.1;
483			assert_eq!(SafeDiv::saturating_div(&x, &y), f64::MAX);
484		}
485
486		#[test]
487		fn wrapping_div_happy() {
488			let x: f64 = 20.0;
489			let y: f64 = 2.0;
490			assert_eq!(SafeDiv::wrapping_div(&x, &y), 10.0);
491		}
492
493		#[test]
494		fn wrapping_div_unhappy() {
495			let x: f64 = f64::MAX;
496			let y: f64 = 0.1;
497			let result = SafeDiv::wrapping_div(&x, &y);
498			assert!(result.is_finite());
499			assert_eq!(result, 0.0);
500		}
501
502		#[test]
503		fn wrapping_div_negative() {
504			let x: f64 = f64::MAX;
505			let y: f64 = -0.1;
506			let result = SafeDiv::wrapping_div(&x, &y);
507			assert!(result.is_finite());
508			assert_eq!(result, 0.0);
509		}
510	}
511}