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 narrow_i256(self.trunc())
12 }
13
14 fn saturating_convert(self) -> $dst {
15 if let Some(value) = narrow_i256(self.trunc()) {
16 value
17 } else if self.is_negative() {
18 <$dst>::MIN
19 } else {
20 <$dst>::MAX
21 }
22 }
23
24 fn wrapping_convert(self) -> $dst {
25 self.saturating_convert()
26 }
27 }
28 )*
29 };
30}
31
32macro_rules! impl_safe_convert_decimal_to_float {
33 ($($dst:ty => $to:ident),*) => {
34 $(
35 impl SafeConvert<$dst> for Decimal {
36 fn checked_convert(self) -> Option<$dst> {
37 let value = self.$to();
38 value.is_finite().then_some(value)
39 }
40
41 fn saturating_convert(self) -> $dst {
42 let value = self.$to();
43 if value.is_finite() {
44 value
45 } else if value.is_sign_negative() {
46 <$dst>::MIN
47 } else {
48 <$dst>::MAX
49 }
50 }
51
52 fn wrapping_convert(self) -> $dst {
53 self.saturating_convert()
54 }
55 }
56 )*
57 };
58}
59
60impl_safe_convert_decimal_to_int!(i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
61impl_safe_convert_decimal_to_float!(f32 => to_f32, f64 => to_f64);
62
63#[cfg(test)]
64pub mod tests {
65 mod i8 {
66 use super::*;
67 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
68
69 #[test]
70 fn test_checked_convert() {
71 let x = Decimal::from(127i64);
72 let y: Option<i8> = x.checked_convert();
73 assert_eq!(y, Some(127i8));
74 }
75
76 #[test]
77 fn test_checked_convert_overflow() {
78 let x = Decimal::from(128i64);
79 let y: Option<i8> = x.checked_convert();
80 assert_eq!(y, None);
81 }
82
83 #[test]
84 fn test_saturating_convert() {
85 let x = Decimal::from(200i64);
86 let y: i8 = x.saturating_convert();
87 assert_eq!(y, i8::MAX);
88 }
89
90 #[test]
91 fn test_wrapping_convert() {
92 let x = Decimal::from(-129i64);
93 let y: i8 = x.wrapping_convert();
94 assert_eq!(y, i8::MIN);
95 }
96 }
97
98 mod i32 {
99 use super::*;
100 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
101
102 #[test]
103 fn test_checked_convert() {
104 let x = Decimal::from(2147483647i64);
105 let y: Option<i32> = x.checked_convert();
106 assert_eq!(y, Some(2147483647i32));
107 }
108
109 #[test]
110 fn test_saturating_convert() {
111 let x = Decimal::from(-2147483648i64);
112 let y: i32 = x.saturating_convert();
113 assert_eq!(y, -2147483648i32);
114 }
115 }
116
117 mod u8 {
118 use super::*;
119 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
120
121 #[test]
122 fn test_checked_convert() {
123 let x = Decimal::from(255i64);
124 let y: Option<u8> = x.checked_convert();
125 assert_eq!(y, Some(255u8));
126 }
127
128 #[test]
129 fn test_checked_convert_overflow() {
130 let x = Decimal::from(256i64);
131 let y: Option<u8> = x.checked_convert();
132 assert_eq!(y, None);
133 }
134
135 #[test]
136 fn test_checked_convert_negative() {
137 let x = Decimal::from(-1i64);
138 let y: Option<u8> = x.checked_convert();
139 assert_eq!(y, None);
140 }
141
142 #[test]
143 fn test_saturating_convert() {
144 let x = Decimal::from(1000i64);
145 let y: u8 = x.saturating_convert();
146 assert_eq!(y, u8::MAX);
147 }
148 }
149
150 mod u32 {
151 use super::*;
152 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
153
154 #[test]
155 fn test_checked_convert() {
156 let x = Decimal::from(4294967295i64);
157 let y: Option<u32> = x.checked_convert();
158 assert_eq!(y, Some(4294967295u32));
159 }
160
161 #[test]
162 fn test_saturating_convert() {
163 let x = Decimal::from(-100i64);
164 let y: u32 = x.saturating_convert();
165 assert_eq!(y, 0u32);
166 }
167 }
168
169 mod f32 {
170 use std::str::FromStr;
171
172 use super::*;
173 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
174
175 #[test]
176 fn test_checked_convert() {
177 let x = Decimal::from(42i64);
178 let y: Option<f32> = x.checked_convert();
179 assert_eq!(y, Some(42.0f32));
180 }
181
182 #[test]
183 fn test_saturating_convert() {
184 let x = Decimal::from(-1000i64);
185 let y: f32 = x.saturating_convert();
186 assert_eq!(y, -1000.0f32);
187 }
188
189 #[test]
190 fn checked_convert_f32_max_exact_literal_roundtrips() {
191 let dec = Decimal::from_str("3.4028234663852886e38").unwrap();
193 let out: Option<f32> = dec.checked_convert();
194 assert_eq!(out, Some(f32::MAX));
195 }
196
197 #[test]
198 fn checked_convert_neg_f32_max_exact_literal_roundtrips() {
199 let dec = Decimal::from_str("-3.4028234663852886e38").unwrap();
200 let out: Option<f32> = dec.checked_convert();
201 assert_eq!(out, Some(f32::MIN));
202 }
203
204 #[test]
205 fn checked_convert_rounds_value_just_above_f32_max_to_max() {
206 let dec = Decimal::from_str("3.4028235e38").unwrap();
210 let out: Option<f32> = dec.checked_convert();
211 assert_eq!(out, Some(f32::MAX));
212 }
213
214 #[test]
215 fn checked_convert_rejects_value_above_f32_max() {
216 let dec = Decimal::from_str("1e40").unwrap();
217 let out: Option<f32> = dec.checked_convert();
218 assert_eq!(out, None);
219 }
220
221 #[test]
222 fn saturating_convert_above_f32_max_returns_max() {
223 let dec = Decimal::from_str("1e40").unwrap();
224 let out: f32 = dec.saturating_convert();
225 assert_eq!(out, f32::MAX);
226 }
227
228 #[test]
229 fn saturating_convert_below_neg_f32_max_returns_min() {
230 let dec = Decimal::from_str("-1e40").unwrap();
231 let out: f32 = dec.saturating_convert();
232 assert_eq!(out, f32::MIN);
233 }
234
235 #[test]
236 fn checked_convert_f32_min_positive_roundtrips() {
237 let dec = Decimal::from_str("1.17549435e-38").unwrap();
239 let out: Option<f32> = dec.checked_convert();
240 assert_eq!(out, Some(f32::MIN_POSITIVE));
241 }
242 }
243
244 mod f64 {
245 use std::str::FromStr;
246
247 use super::*;
248 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
249
250 #[test]
251 fn test_checked_convert() {
252 let x = Decimal::from(42i64);
253 let y: Option<f64> = x.checked_convert();
254 assert_eq!(y, Some(42.0f64));
255 }
256
257 #[test]
258 fn test_saturating_convert() {
259 let x = Decimal::from(-1000i64);
260 let y: f64 = x.saturating_convert();
261 assert_eq!(y, -1000.0f64);
262 }
263
264 #[test]
265 fn checked_convert_widest_decimal_literal_roundtrips() {
266 let dec = Decimal::from_str(&"9".repeat(76)).unwrap();
269 let out: Option<f64> = dec.checked_convert();
270 assert_eq!(out, Some(1e76));
271 }
272
273 #[test]
274 fn checked_convert_neg_widest_decimal_literal_roundtrips() {
275 let dec = Decimal::from_str(&format!("-{}", "9".repeat(76))).unwrap();
276 let out: Option<f64> = dec.checked_convert();
277 assert_eq!(out, Some(-1e76));
278 }
279
280 #[test]
281 fn saturating_convert_of_the_widest_decimals_stays_finite() {
282 let max = Decimal::from_str(&"9".repeat(76)).unwrap();
284 let out: f64 = max.clone().saturating_convert();
285 assert_eq!(out, 1e76);
286 let out: f64 = max.negate().saturating_convert();
287 assert_eq!(out, -1e76);
288 }
289
290 #[test]
291 fn checked_convert_smallest_positive_decimal_roundtrips() {
292 let dec = Decimal::from_str(&format!("0.{}1", "0".repeat(75))).unwrap();
294 let out: Option<f64> = dec.checked_convert();
295 assert_eq!(out, Some(1e-76));
296 }
297
298 #[test]
299 fn checked_convert_rounds_at_seventeen_digits_like_the_literal() {
300 let text = "0.12345678901234567890123456789";
302 let dec = Decimal::from_str(text).unwrap();
303 let out: Option<f64> = dec.checked_convert();
304 assert_eq!(out, Some(text.parse::<f64>().unwrap()));
305 }
306 }
307
308 mod self_conversion {
309 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
310
311 #[test]
312 fn test_checked_convert() {
313 let x = Decimal::from(42i64);
314 let y: Option<Decimal> = x.clone().checked_convert();
315 assert_eq!(y, Some(x));
316 }
317
318 #[test]
319 fn test_saturating_convert() {
320 let x = Decimal::from(-100i64);
321 let y: Decimal = x.clone().saturating_convert();
322 assert_eq!(y, x);
323 }
324
325 #[test]
326 fn test_wrapping_convert() {
327 let x = Decimal::from(999i64);
328 let y: Decimal = x.clone().wrapping_convert();
329 assert_eq!(y, x);
330 }
331 }
332
333 mod range {
334 use std::str::FromStr;
335
336 use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
337
338 #[test]
339 fn integer_targets_truncate_toward_zero() {
340 let dec = Decimal::from_str("-1.9").unwrap();
342 assert_eq!(SafeConvert::<i8>::checked_convert(dec.clone()), Some(-1));
343 }
344
345 #[test]
346 fn wide_decimals_refuse_narrow_targets_and_clamp_with_their_sign() {
347 let max = Decimal::from_str(&"9".repeat(76)).unwrap();
349 assert_eq!(SafeConvert::<i128>::checked_convert(max.clone()), None);
350 assert_eq!(SafeConvert::<u128>::checked_convert(max.clone()), None);
351 assert_eq!(SafeConvert::<u128>::saturating_convert(max.clone()), u128::MAX);
352 assert_eq!(SafeConvert::<i64>::saturating_convert(max.negate()), i64::MIN);
353 let u128_max = Decimal::from_str(&u128::MAX.to_string()).unwrap();
354 assert_eq!(SafeConvert::<u128>::checked_convert(u128_max), Some(u128::MAX));
355 }
356 }
357}