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 crate::value::decimal::Decimal;
69
70impl SafeDiv for Decimal {
71	fn checked_div(&self, r: &Self) -> Option<Self> {
72		Decimal::checked_div(self, r)
73	}
74
75	fn saturating_div(&self, r: &Self) -> Self {
76		Decimal::saturating_div(self, r)
77	}
78
79	fn wrapping_div(&self, r: &Self) -> Self {
80		if r.is_zero() {
81			return Decimal::zero();
82		}
83		Decimal::saturating_div(self, r)
84	}
85
86	fn is_zero(&self) -> bool {
87		Decimal::is_zero(self)
88	}
89}
90
91impl SafeDiv for f32 {
92	fn checked_div(&self, r: &Self) -> Option<Self> {
93		let result = *self / *r;
94		if result.is_infinite() && self.is_finite() && r.is_finite() {
95			None
96		} else {
97			Some(result)
98		}
99	}
100
101	fn saturating_div(&self, r: &Self) -> Self {
102		let result = *self / *r;
103		if result.is_infinite() {
104			if result.is_sign_positive() {
105				f32::MAX
106			} else {
107				f32::MIN
108			}
109		} else {
110			result
111		}
112	}
113
114	fn wrapping_div(&self, r: &Self) -> Self {
115		let result = *self / *r;
116		if result.is_finite() {
117			result
118		} else {
119			0.0
120		}
121	}
122
123	fn is_zero(&self) -> bool {
124		*self == 0.0
125	}
126}
127
128impl SafeDiv for f64 {
129	fn checked_div(&self, r: &Self) -> Option<Self> {
130		let result = *self / *r;
131		if result.is_infinite() && self.is_finite() && r.is_finite() {
132			None
133		} else {
134			Some(result)
135		}
136	}
137
138	fn saturating_div(&self, r: &Self) -> Self {
139		let result = *self / *r;
140		if result.is_infinite() {
141			if result.is_sign_positive() {
142				f64::MAX
143			} else {
144				f64::MIN
145			}
146		} else {
147			result
148		}
149	}
150
151	fn wrapping_div(&self, r: &Self) -> Self {
152		let result = *self / *r;
153		if result.is_finite() {
154			result
155		} else {
156			0.0
157		}
158	}
159
160	fn is_zero(&self) -> bool {
161		*self == 0.0
162	}
163}
164
165#[cfg(test)]
166pub mod tests {
167	macro_rules! signed {
168        ($($t:ty => $mod:ident),*) => {
169            $(
170                mod $mod {
171                    use super::super::SafeDiv;
172
173                    #[test]
174                    fn checked_div_happy() {
175                        let x: $t = 20;
176                        let y: $t = 2;
177                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(10));
178                    }
179
180                    #[test]
181                    fn checked_div_unhappy() {
182                        let x: $t = 10;
183                        let y: $t = 0;
184                        assert_eq!(SafeDiv::checked_div(&x, &y), None);
185                    }
186
187                    #[test]
188                    fn saturating_div_happy() {
189                        let x: $t = 20;
190                        let y: $t = 2;
191                        assert_eq!(SafeDiv::saturating_div(&x, &y), 10);
192                    }
193
194                    #[test]
195                    fn saturating_div_unhappy() {
196                        let x: $t = 10;
197                        let y: $t = 0;
198                        // Division by zero has no saturation bound to pick, so it yields 0.
199                        let result = SafeDiv::saturating_div(&x, &y);
200                        assert_eq!(result, 0);
201                    }
202
203                    #[test]
204                    fn saturating_div_negative() {
205                        let x: $t = -10;
206                        let y: $t = 0;
207                        // Division by zero has no saturation bound to pick, so it yields 0.
208                        let result = SafeDiv::saturating_div(&x, &y);
209                        assert_eq!(result, 0);
210                    }
211
212                    #[test]
213                    fn wrapping_div_happy() {
214                        let x: $t = 20;
215                        let y: $t = 2;
216                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 10);
217                    }
218
219                    #[test]
220                    fn wrapping_div_unhappy() {
221                        let x: $t = <$t>::MIN;
222                        let y: $t = -1;
223                        // MIN / -1 is the one signed division that overflows; wrapping lands on MIN.
224                        let result = SafeDiv::wrapping_div(&x, &y);
225                        assert_eq!(result, <$t>::MIN);
226                    }
227
228                    #[test]
229                    fn div_small_by_large() {
230                        let x: $t = 5;
231                        let y: $t = <$t>::MAX;
232
233                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(0));
234                        assert_eq!(SafeDiv::saturating_div(&x, &y), 0);
235                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 0);
236                    }
237                }
238            )*
239        };
240    }
241
242	macro_rules! unsigned {
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 wrapping_div_happy() {
280                        let x: $t = 20;
281                        let y: $t = 2;
282                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 10);
283                    }
284
285                    #[test]
286                    fn wrapping_div_unhappy() {
287                        let x: $t = 10;
288                        let y: $t = 0;
289                        let result = SafeDiv::wrapping_div(&x, &y);
290                        assert_eq!(result, 0);
291                    }
292
293                    #[test]
294                    fn div_small_by_large() {
295                        let x: $t = 5;
296                        let y: $t = <$t>::MAX;
297
298                        assert_eq!(SafeDiv::checked_div(&x, &y), Some(0));
299                        assert_eq!(SafeDiv::saturating_div(&x, &y), 0);
300                        assert_eq!(SafeDiv::wrapping_div(&x, &y), 0);
301                    }
302                }
303            )*
304        };
305    }
306
307	signed!(
308	    i8 => i8,
309	    i16 => i16,
310	    i32 => i32,
311	    i64 => i64,
312	    i128 => i128
313	);
314
315	unsigned!(
316	    u8 => u8,
317	    u16 => u16,
318	    u32 => u32,
319	    u64 => u64,
320	    u128 => u128
321	);
322
323	mod f32 {
324		use super::super::SafeDiv;
325
326		#[test]
327		fn checked_div_happy() {
328			let x: f32 = 20.0;
329			let y: f32 = 2.0;
330			assert_eq!(SafeDiv::checked_div(&x, &y), Some(10.0));
331		}
332
333		#[test]
334		fn checked_div_unhappy() {
335			let x: f32 = f32::MAX;
336			let y: f32 = 0.1;
337			assert_eq!(SafeDiv::checked_div(&x, &y), None);
338		}
339
340		#[test]
341		fn saturating_div_happy() {
342			let x: f32 = 20.0;
343			let y: f32 = 2.0;
344			assert_eq!(SafeDiv::saturating_div(&x, &y), 10.0);
345		}
346
347		#[test]
348		fn saturating_div_unhappy() {
349			let x: f32 = f32::MAX;
350			let y: f32 = 0.1;
351			assert_eq!(SafeDiv::saturating_div(&x, &y), f32::MAX);
352		}
353
354		#[test]
355		fn wrapping_div_happy() {
356			let x: f32 = 20.0;
357			let y: f32 = 2.0;
358			assert_eq!(SafeDiv::wrapping_div(&x, &y), 10.0);
359		}
360
361		#[test]
362		fn wrapping_div_unhappy() {
363			let x: f32 = f32::MAX;
364			let y: f32 = 0.1;
365			let result = SafeDiv::wrapping_div(&x, &y);
366			assert!(result.is_finite());
367			assert_eq!(result, 0.0);
368		}
369
370		#[test]
371		fn wrapping_div_negative() {
372			let x: f32 = f32::MAX;
373			let y: f32 = -0.1;
374			let result = SafeDiv::wrapping_div(&x, &y);
375			assert!(result.is_finite());
376			assert_eq!(result, 0.0);
377		}
378	}
379
380	mod f64 {
381		use super::super::SafeDiv;
382
383		#[test]
384		fn checked_div_happy() {
385			let x: f64 = 20.0;
386			let y: f64 = 2.0;
387			assert_eq!(SafeDiv::checked_div(&x, &y), Some(10.0));
388		}
389
390		#[test]
391		fn checked_div_unhappy() {
392			let x: f64 = f64::MAX;
393			let y: f64 = 0.1;
394			assert_eq!(SafeDiv::checked_div(&x, &y), None);
395		}
396
397		#[test]
398		fn saturating_div_happy() {
399			let x: f64 = 20.0;
400			let y: f64 = 2.0;
401			assert_eq!(SafeDiv::saturating_div(&x, &y), 10.0);
402		}
403
404		#[test]
405		fn saturating_div_unhappy() {
406			let x: f64 = f64::MAX;
407			let y: f64 = 0.1;
408			assert_eq!(SafeDiv::saturating_div(&x, &y), f64::MAX);
409		}
410
411		#[test]
412		fn wrapping_div_happy() {
413			let x: f64 = 20.0;
414			let y: f64 = 2.0;
415			assert_eq!(SafeDiv::wrapping_div(&x, &y), 10.0);
416		}
417
418		#[test]
419		fn wrapping_div_unhappy() {
420			let x: f64 = f64::MAX;
421			let y: f64 = 0.1;
422			let result = SafeDiv::wrapping_div(&x, &y);
423			assert!(result.is_finite());
424			assert_eq!(result, 0.0);
425		}
426
427		#[test]
428		fn wrapping_div_negative() {
429			let x: f64 = f64::MAX;
430			let y: f64 = -0.1;
431			let result = SafeDiv::wrapping_div(&x, &y);
432			assert!(result.is_finite());
433			assert_eq!(result, 0.0);
434		}
435	}
436}