Skip to main content

reifydb_value/value/number/safe/
div.rs

1// SPDX-License-Identifier: MIT
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                        let result = SafeDiv::saturating_div(&x, &y);
274                        // Should saturate to 0 for division by zero
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                        let result = SafeDiv::saturating_div(&x, &y);
283                        // Should saturate to 0 for division by zero
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                        // For signed types, MIN / -1 would overflow, so it wraps
299                        let result = SafeDiv::wrapping_div(&x, &y);
300                        // The exact wrapped value depends on the type, but should wrap to MIN
301                        assert_eq!(result, <$t>::MIN);
302                    }
303
304                    #[test]
305                    fn div_small_by_large() {
306                        let x: $t = 5;
307                        let y: $t = <$t>::MAX;
308
309                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(0));
310                        assert_eq!(SafeDiv::saturating_div(&x, &y), 0);
311                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 0);
312                    }
313                }
314            )*
315        };
316    }
317
318	macro_rules! unsigned {
319        ($($t:ty => $mod:ident),*) => {
320            $(
321                mod $mod {
322                    use super::super::SafeDiv;
323
324                    #[test]
325                    fn checked_div_happy() {
326                        let x: $t = 20;
327                        let y: $t = 2;
328                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(10));
329                    }
330
331                    #[test]
332                    fn checked_div_unhappy() {
333                        let x: $t = 10;
334                        let y: $t = 0;
335                        assert_eq!(SafeDiv::checked_div(&x, &y), None);
336                    }
337
338                    #[test]
339                    fn saturating_div_happy() {
340                        let x: $t = 20;
341                        let y: $t = 2;
342                        assert_eq!(SafeDiv::saturating_div(&x, &y), 10);
343                    }
344
345                    #[test]
346                    fn saturating_div_unhappy() {
347                        let x: $t = 10;
348                        let y: $t = 0;
349                        let result = SafeDiv::saturating_div(&x, &y);
350                        // Should saturate to 0 for division by zero
351                        assert_eq!(result, 0);
352                    }
353
354                    #[test]
355                    fn wrapping_div_happy() {
356                        let x: $t = 20;
357                        let y: $t = 2;
358                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 10);
359                    }
360
361                    #[test]
362                    fn wrapping_div_unhappy() {
363                        let x: $t = 10;
364                        let y: $t = 0;
365                        let result = SafeDiv::wrapping_div(&x, &y);
366                        assert_eq!(result, 0);
367                    }
368
369                    #[test]
370                    fn div_small_by_large() {
371                        let x: $t = 5;
372                        let y: $t = <$t>::MAX;
373
374                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(0));
375                        assert_eq!(SafeDiv::saturating_div(&x, &y), 0);
376                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 0);
377                    }
378                }
379            )*
380        };
381    }
382
383	signed!(
384	    i8 => i8,
385	    i16 => i16,
386	    i32 => i32,
387	    i64 => i64,
388	    i128 => i128
389	);
390
391	unsigned!(
392	    u8 => u8,
393	    u16 => u16,
394	    u32 => u32,
395	    u64 => u64,
396	    u128 => u128
397	);
398
399	mod f32 {
400		use super::super::SafeDiv;
401
402		#[test]
403		fn checked_div_happy() {
404			let x: f32 = 20.0;
405			let y: f32 = 2.0;
406			assert_eq!(SafeDiv::checked_div(&x, &y), Some(10.0));
407		}
408
409		#[test]
410		fn checked_div_unhappy() {
411			let x: f32 = f32::MAX;
412			let y: f32 = 0.1;
413			assert_eq!(SafeDiv::checked_div(&x, &y), None);
414		}
415
416		#[test]
417		fn saturating_div_happy() {
418			let x: f32 = 20.0;
419			let y: f32 = 2.0;
420			assert_eq!(SafeDiv::saturating_div(&x, &y), 10.0);
421		}
422
423		#[test]
424		fn saturating_div_unhappy() {
425			let x: f32 = f32::MAX;
426			let y: f32 = 0.1;
427			assert_eq!(SafeDiv::saturating_div(&x, &y), f32::MAX);
428		}
429
430		#[test]
431		fn wrapping_div_happy() {
432			let x: f32 = 20.0;
433			let y: f32 = 2.0;
434			assert_eq!(SafeDiv::wrapping_div(&x, &y), 10.0);
435		}
436
437		#[test]
438		fn wrapping_div_unhappy() {
439			let x: f32 = f32::MAX;
440			let y: f32 = 0.1;
441			let result = SafeDiv::wrapping_div(&x, &y);
442			assert!(result.is_finite());
443			assert_eq!(result, 0.0);
444		}
445
446		#[test]
447		fn wrapping_div_negative() {
448			let x: f32 = f32::MAX;
449			let y: f32 = -0.1;
450			let result = SafeDiv::wrapping_div(&x, &y);
451			assert!(result.is_finite());
452			assert_eq!(result, 0.0);
453		}
454	}
455
456	mod f64 {
457		use super::super::SafeDiv;
458
459		#[test]
460		fn checked_div_happy() {
461			let x: f64 = 20.0;
462			let y: f64 = 2.0;
463			assert_eq!(SafeDiv::checked_div(&x, &y), Some(10.0));
464		}
465
466		#[test]
467		fn checked_div_unhappy() {
468			let x: f64 = f64::MAX;
469			let y: f64 = 0.1;
470			assert_eq!(SafeDiv::checked_div(&x, &y), None);
471		}
472
473		#[test]
474		fn saturating_div_happy() {
475			let x: f64 = 20.0;
476			let y: f64 = 2.0;
477			assert_eq!(SafeDiv::saturating_div(&x, &y), 10.0);
478		}
479
480		#[test]
481		fn saturating_div_unhappy() {
482			let x: f64 = f64::MAX;
483			let y: f64 = 0.1;
484			assert_eq!(SafeDiv::saturating_div(&x, &y), f64::MAX);
485		}
486
487		#[test]
488		fn wrapping_div_happy() {
489			let x: f64 = 20.0;
490			let y: f64 = 2.0;
491			assert_eq!(SafeDiv::wrapping_div(&x, &y), 10.0);
492		}
493
494		#[test]
495		fn wrapping_div_unhappy() {
496			let x: f64 = f64::MAX;
497			let y: f64 = 0.1;
498			let result = SafeDiv::wrapping_div(&x, &y);
499			assert!(result.is_finite());
500			assert_eq!(result, 0.0);
501		}
502
503		#[test]
504		fn wrapping_div_negative() {
505			let x: f64 = f64::MAX;
506			let y: f64 = -0.1;
507			let result = SafeDiv::wrapping_div(&x, &y);
508			assert!(result.is_finite());
509			assert_eq!(result, 0.0);
510		}
511	}
512}