reifydb_value/value/number/safe/convert/
decimal.rs1use super::*;
5
6macro_rules! impl_safe_convert_decimal_to_int {
7 ($($dst:ty),*) => {
8 $(
9 impl SafeConvert<$dst> for Decimal {
10 fn checked_convert(self) -> Option<$dst> {
11 if let Some(int_part) = self.inner().to_bigint() {
12 <$dst>::try_from(int_part).ok()
13 } else {
14 None
15 }
16 }
17
18 fn saturating_convert(self) -> $dst {
19 if let Some(int_part) = self.inner().to_bigint() {
20 if let Ok(val) = <$dst>::try_from(&int_part) {
21 val
22 } else if int_part < BigInt::from(0) {
23 <$dst>::MIN
24 } else {
25 <$dst>::MAX
26 }
27 } else {
28 0
29 }
30 }
31
32 fn wrapping_convert(self) -> $dst {
33 if let Some(int_part) = self.inner().to_bigint() {
34 if let Ok(val) = <$dst>::try_from(&int_part) {
35 val
36 } else {
37 self.saturating_convert()
38 }
39 } else {
40 0
41 }
42 }
43 }
44 )*
45 };
46}
47
48fn decimal_to_f64(decimal: &Decimal) -> f64 {
49 format!("{:e}", decimal.inner())
50 .parse::<f64>()
51 .expect("BigDecimal LowerExp always emits parseable f64 scientific notation")
52}
53
54macro_rules! impl_safe_convert_decimal_to_float {
55 ($($dst:ty),*) => {
56 $(
57 impl SafeConvert<$dst> for Decimal {
58 fn checked_convert(self) -> Option<$dst> {
59 let f = decimal_to_f64(&self);
60 if !f.is_finite() {
61 return None;
62 }
63 if f < <$dst>::MIN as f64 || f > <$dst>::MAX as f64 {
64 return None;
65 }
66 Some(f as $dst)
67 }
68
69 fn saturating_convert(self) -> $dst {
70 let f = decimal_to_f64(&self);
71 if !f.is_finite() {
72 return if f.is_sign_negative() { <$dst>::MIN } else { <$dst>::MAX };
73 }
74 if f < <$dst>::MIN as f64 {
75 return <$dst>::MIN;
76 }
77 if f > <$dst>::MAX as f64 {
78 return <$dst>::MAX;
79 }
80 f as $dst
81 }
82
83 fn wrapping_convert(self) -> $dst {
84 self.saturating_convert()
85 }
86 }
87 )*
88 };
89}
90
91impl_safe_convert_decimal_to_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
92impl_safe_convert_decimal_to_float!(f32, f64);
93
94impl SafeConvert<Int> for Decimal {
95 fn checked_convert(self) -> Option<Int> {
96 self.inner().to_bigint().map(Int)
97 }
98
99 fn saturating_convert(self) -> Int {
100 self.checked_convert().unwrap_or(Int::zero())
101 }
102
103 fn wrapping_convert(self) -> Int {
104 self.saturating_convert()
105 }
106}
107
108impl SafeConvert<Uint> for Decimal {
109 fn checked_convert(self) -> Option<Uint> {
110 if let Some(big_int) = self.inner().to_bigint() {
111 if big_int >= BigInt::from(0) {
112 Some(Uint(big_int))
113 } else {
114 None
115 }
116 } else {
117 None
118 }
119 }
120
121 fn saturating_convert(self) -> Uint {
122 if let Some(big_int) = self.inner().to_bigint() {
123 if big_int >= BigInt::from(0) {
124 Uint(big_int)
125 } else {
126 Uint::zero()
127 }
128 } else {
129 Uint::zero()
130 }
131 }
132
133 fn wrapping_convert(self) -> Uint {
134 if let Some(big_int) = self.inner().to_bigint() {
135 Uint(big_int.abs())
136 } else {
137 Uint::zero()
138 }
139 }
140}
141
142#[cfg(test)]
143pub mod tests {
144 mod i8 {
145 use super::*;
146 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
147
148 #[test]
149 fn test_checked_convert() {
150 let x = Decimal::from(127i64);
151 let y: Option<i8> = x.checked_convert();
152 assert_eq!(y, Some(127i8));
153 }
154
155 #[test]
156 fn test_checked_convert_overflow() {
157 let x = Decimal::from(128i64);
158 let y: Option<i8> = x.checked_convert();
159 assert_eq!(y, None);
160 }
161
162 #[test]
163 fn test_saturating_convert() {
164 let x = Decimal::from(200i64);
165 let y: i8 = x.saturating_convert();
166 assert_eq!(y, i8::MAX);
167 }
168
169 #[test]
170 fn test_wrapping_convert() {
171 let x = Decimal::from(-129i64);
172 let y: i8 = x.wrapping_convert();
173 assert_eq!(y, i8::MIN);
174 }
175 }
176
177 mod i32 {
178 use super::*;
179 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
180
181 #[test]
182 fn test_checked_convert() {
183 let x = Decimal::from(2147483647i64);
184 let y: Option<i32> = x.checked_convert();
185 assert_eq!(y, Some(2147483647i32));
186 }
187
188 #[test]
189 fn test_saturating_convert() {
190 let x = Decimal::from(-2147483648i64);
191 let y: i32 = x.saturating_convert();
192 assert_eq!(y, -2147483648i32);
193 }
194 }
195
196 mod u8 {
197 use super::*;
198 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
199
200 #[test]
201 fn test_checked_convert() {
202 let x = Decimal::from(255i64);
203 let y: Option<u8> = x.checked_convert();
204 assert_eq!(y, Some(255u8));
205 }
206
207 #[test]
208 fn test_checked_convert_overflow() {
209 let x = Decimal::from(256i64);
210 let y: Option<u8> = x.checked_convert();
211 assert_eq!(y, None);
212 }
213
214 #[test]
215 fn test_checked_convert_negative() {
216 let x = Decimal::from(-1i64);
217 let y: Option<u8> = x.checked_convert();
218 assert_eq!(y, None);
219 }
220
221 #[test]
222 fn test_saturating_convert() {
223 let x = Decimal::from(1000i64);
224 let y: u8 = x.saturating_convert();
225 assert_eq!(y, u8::MAX);
226 }
227 }
228
229 mod u32 {
230 use super::*;
231 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
232
233 #[test]
234 fn test_checked_convert() {
235 let x = Decimal::from(4294967295i64);
236 let y: Option<u32> = x.checked_convert();
237 assert_eq!(y, Some(4294967295u32));
238 }
239
240 #[test]
241 fn test_saturating_convert() {
242 let x = Decimal::from(-100i64);
243 let y: u32 = x.saturating_convert();
244 assert_eq!(y, 0u32);
245 }
246 }
247
248 mod f32 {
249 use std::str::FromStr;
250
251 use bigdecimal::BigDecimal;
252
253 use super::*;
254 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
255
256 #[test]
257 fn test_checked_convert() {
258 let x = Decimal::from(42i64);
259 let y: Option<f32> = x.checked_convert();
260 assert_eq!(y, Some(42.0f32));
261 }
262
263 #[test]
264 fn test_saturating_convert() {
265 let x = Decimal::from(-1000i64);
266 let y: f32 = x.saturating_convert();
267 assert_eq!(y, -1000.0f32);
268 }
269
270 #[test]
271 fn checked_convert_f32_max_exact_literal_roundtrips() {
272 let bd = BigDecimal::from_str("3.4028234663852886e38").unwrap();
275 let dec = Decimal::new(bd);
276 let out: Option<f32> = dec.checked_convert();
277 assert_eq!(out, Some(f32::MAX));
278 }
279
280 #[test]
281 fn checked_convert_neg_f32_max_exact_literal_roundtrips() {
282 let bd = BigDecimal::from_str("-3.4028234663852886e38").unwrap();
283 let dec = Decimal::new(bd);
284 let out: Option<f32> = dec.checked_convert();
285 assert_eq!(out, Some(f32::MIN));
286 }
287
288 #[test]
289 fn checked_convert_rejects_value_just_above_f32_max() {
290 let bd = BigDecimal::from_str("3.4028235e38").unwrap();
296 let dec = Decimal::new(bd);
297 let out: Option<f32> = dec.checked_convert();
298 assert_eq!(out, None);
299 }
300
301 #[test]
302 fn checked_convert_rejects_value_above_f32_max() {
303 let bd = BigDecimal::from_str("1e40").unwrap();
305 let dec = Decimal::new(bd);
306 let out: Option<f32> = dec.checked_convert();
307 assert_eq!(out, None);
308 }
309
310 #[test]
311 fn saturating_convert_above_f32_max_returns_max() {
312 let bd = BigDecimal::from_str("1e40").unwrap();
313 let dec = Decimal::new(bd);
314 let out: f32 = dec.saturating_convert();
315 assert_eq!(out, f32::MAX);
316 }
317
318 #[test]
319 fn saturating_convert_below_neg_f32_max_returns_min() {
320 let bd = BigDecimal::from_str("-1e40").unwrap();
321 let dec = Decimal::new(bd);
322 let out: f32 = dec.saturating_convert();
323 assert_eq!(out, f32::MIN);
324 }
325
326 #[test]
327 fn checked_convert_f32_min_positive_roundtrips() {
328 let bd = BigDecimal::from_str("1.17549435e-38").unwrap();
330 let dec = Decimal::new(bd);
331 let out: Option<f32> = dec.checked_convert();
332 assert_eq!(out, Some(f32::MIN_POSITIVE));
333 }
334 }
335
336 mod f64 {
337 use std::str::FromStr;
338
339 use bigdecimal::BigDecimal;
340
341 use super::*;
342 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
343
344 #[test]
345 fn test_checked_convert() {
346 let x = Decimal::from(42i64);
347 let y: Option<f64> = x.checked_convert();
348 assert_eq!(y, Some(42.0f64));
349 }
350
351 #[test]
352 fn test_saturating_convert() {
353 let x = Decimal::from(-1000i64);
354 let y: f64 = x.saturating_convert();
355 assert_eq!(y, -1000.0f64);
356 }
357
358 #[test]
359 fn checked_convert_f64_max_literal_roundtrips() {
360 let bd = BigDecimal::from_str("1.7976931348623157e308").unwrap();
365 let dec = Decimal::new(bd);
366 let out: Option<f64> = dec.checked_convert();
367 assert_eq!(out, Some(f64::MAX));
368 }
369
370 #[test]
371 fn checked_convert_neg_f64_max_literal_roundtrips() {
372 let bd = BigDecimal::from_str("-1.7976931348623157e308").unwrap();
373 let dec = Decimal::new(bd);
374 let out: Option<f64> = dec.checked_convert();
375 assert_eq!(out, Some(f64::MIN));
376 }
377
378 #[test]
379 fn checked_convert_rejects_value_above_f64_max() {
380 let bd = BigDecimal::from_str("1e400").unwrap();
382 let dec = Decimal::new(bd);
383 let out: Option<f64> = dec.checked_convert();
384 assert_eq!(out, None);
385 }
386
387 #[test]
388 fn saturating_convert_above_f64_max_returns_max() {
389 let bd = BigDecimal::from_str("1e400").unwrap();
390 let dec = Decimal::new(bd);
391 let out: f64 = dec.saturating_convert();
392 assert_eq!(out, f64::MAX);
393 }
394
395 #[test]
396 fn saturating_convert_below_neg_f64_max_returns_min() {
397 let bd = BigDecimal::from_str("-1e400").unwrap();
398 let dec = Decimal::new(bd);
399 let out: f64 = dec.saturating_convert();
400 assert_eq!(out, f64::MIN);
401 }
402
403 #[test]
404 fn checked_convert_f64_min_positive_roundtrips() {
405 let bd = BigDecimal::from_str("2.2250738585072014e-308").unwrap();
406 let dec = Decimal::new(bd);
407 let out: Option<f64> = dec.checked_convert();
408 assert_eq!(out, Some(f64::MIN_POSITIVE));
409 }
410 }
411
412 mod int {
413 use crate::value::{decimal::Decimal, int::Int, number::safe::convert::SafeConvert};
414
415 #[test]
416 fn test_checked_convert() {
417 let x = Decimal::from(12345i64);
418 let y: Option<Int> = x.checked_convert();
419 assert!(y.is_some());
420 assert_eq!(y.unwrap().to_string(), "12345");
421 }
422
423 #[test]
424 fn test_saturating_convert() {
425 let x = Decimal::from(-999999i64);
426 let y: Int = x.saturating_convert();
427 assert_eq!(y.to_string(), "-999999");
428 }
429
430 #[test]
431 fn test_wrapping_convert() {
432 let x = Decimal::from(0i64);
433 let y: Int = x.wrapping_convert();
434 assert_eq!(y.to_string(), "0");
435 }
436 }
437
438 mod uint {
439 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert, uint::Uint};
440
441 #[test]
442 fn test_checked_convert_positive() {
443 let x = Decimal::from(42i64);
444 let y: Option<Uint> = x.checked_convert();
445 assert!(y.is_some());
446 assert_eq!(y.unwrap().to_string(), "42");
447 }
448
449 #[test]
450 fn test_checked_convert_negative() {
451 let x = Decimal::from(-1i64);
452 let y: Option<Uint> = x.checked_convert();
453 assert!(y.is_none());
454 }
455
456 #[test]
457 fn test_saturating_convert() {
458 let x = Decimal::from(-100i64);
459 let y: Uint = x.saturating_convert();
460 assert_eq!(y.to_string(), "0");
461 }
462
463 #[test]
464 fn test_wrapping_convert() {
465 let x = Decimal::from(-1i64);
466 let y: Uint = x.wrapping_convert();
467 assert_eq!(y.to_string(), "1");
468 }
469 }
470
471 mod self_conversion {
472 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
473
474 #[test]
475 fn test_checked_convert() {
476 let x = Decimal::from(42i64);
477 let y: Option<Decimal> = x.clone().checked_convert();
478 assert_eq!(y, Some(x));
479 }
480
481 #[test]
482 fn test_saturating_convert() {
483 let x = Decimal::from(-100i64);
484 let y: Decimal = x.clone().saturating_convert();
485 assert_eq!(y, x);
486 }
487
488 #[test]
489 fn test_wrapping_convert() {
490 let x = Decimal::from(999i64);
491 let y: Decimal = x.clone().wrapping_convert();
492 assert_eq!(y, x);
493 }
494 }
495}