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