Skip to main content

reifydb_value/value/number/safe/convert/
f64.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use super::*;
5
6impl_safe_convert_float_demote!(f64 => f32);
7
8impl_safe_convert_float_to_signed!(f64 => i8, i16, i32, i64, i128);
9
10impl_safe_convert_float_to_unsigned!(f64 => u8, u16, u32, u64, u128);
11
12impl_safe_convert_to_decimal_from_float!(f64 => from_f64);
13
14#[cfg(test)]
15pub mod tests {
16
17	mod f32 {
18		use super::*;
19		use crate::value::number::safe::convert::SafeConvert;
20
21		#[test]
22		fn test_checked_convert_happy() {
23			let x: f64 = 123.0;
24			let y: Option<f32> = x.checked_convert();
25			assert_eq!(y, Some(123.0f32));
26		}
27
28		#[test]
29		fn test_checked_convert_unhappy_due_to_infinity() {
30			let x: f64 = f64::MAX;
31			let y: Option<f32> = x.checked_convert();
32			assert_eq!(y, None);
33		}
34
35		#[test]
36		fn test_checked_convert_unhappy_due_to_negative_infinity() {
37			let x: f64 = f64::MIN;
38			let y: Option<f32> = x.checked_convert();
39			assert_eq!(y, None);
40		}
41
42		#[test]
43		fn test_saturating_convert_within_range() {
44			let x: f64 = 456.789;
45			let y: f32 = x.saturating_convert();
46			assert_eq!(y, 456.789f32);
47		}
48
49		#[test]
50		fn test_saturating_convert_too_large() {
51			let x: f64 = f64::MAX;
52			let y: f32 = x.saturating_convert();
53			assert_eq!(y, f32::MAX);
54		}
55
56		#[test]
57		fn test_saturating_convert_too_small() {
58			let x: f64 = f64::MIN;
59			let y: f32 = x.saturating_convert();
60			assert_eq!(y, f32::MIN);
61		}
62
63		#[test]
64		fn test_saturating_convert_nan() {
65			let x: f64 = f64::NAN;
66			let y: f32 = x.saturating_convert();
67			assert!(y.is_nan());
68		}
69
70		#[test]
71		fn test_wrapping_convert_regular() {
72			let x: f64 = 789.123;
73			let y: f32 = x.wrapping_convert();
74			assert_eq!(y, 789.123f32);
75		}
76
77		#[test]
78		fn test_wrapping_convert_nan() {
79			let x: f64 = f64::NAN;
80			let y: f32 = x.wrapping_convert();
81			assert!(y.is_nan());
82		}
83
84		#[test]
85		fn test_wrapping_convert_infinity() {
86			let x: f64 = f64::INFINITY;
87			let y: f32 = x.wrapping_convert();
88			assert!(y.is_infinite() && y.is_sign_positive());
89		}
90	}
91
92	mod i8 {
93		use crate::value::number::safe::convert::SafeConvert;
94
95		#[test]
96		fn test_checked_convert_happy() {
97			let x: f64 = 42.0;
98			let y: Option<i8> = x.checked_convert();
99			assert_eq!(y, Some(42i8));
100		}
101
102		#[test]
103		fn test_checked_convert_unhappy() {
104			let x: f64 = 300.0;
105			let y: Option<i8> = x.checked_convert();
106			assert_eq!(y, None);
107		}
108
109		#[test]
110		fn test_checked_convert_negative() {
111			let x: f64 = -42.0;
112			let y: Option<i8> = x.checked_convert();
113			assert_eq!(y, Some(-42i8));
114		}
115
116		#[test]
117		fn test_checked_convert_nan() {
118			let x: f64 = f64::NAN;
119			let y: Option<i8> = x.checked_convert();
120			assert_eq!(y, None);
121		}
122
123		#[test]
124		fn test_checked_convert_infinity() {
125			let x: f64 = f64::INFINITY;
126			let y: Option<i8> = x.checked_convert();
127			assert_eq!(y, None);
128		}
129
130		#[test]
131		fn test_saturating_convert_overflow() {
132			let x: f64 = 300.0;
133			let y: i8 = x.saturating_convert();
134			assert_eq!(y, i8::MAX);
135		}
136
137		#[test]
138		fn test_saturating_convert_underflow() {
139			let x: f64 = -300.0;
140			let y: i8 = x.saturating_convert();
141			assert_eq!(y, i8::MIN);
142		}
143
144		#[test]
145		fn test_saturating_convert_nan() {
146			let x: f64 = f64::NAN;
147			let y: i8 = x.saturating_convert();
148			assert_eq!(y, 0);
149		}
150
151		#[test]
152		fn test_saturating_convert_infinity() {
153			let x: f64 = f64::INFINITY;
154			let y: i8 = x.saturating_convert();
155			assert_eq!(y, i8::MAX);
156		}
157
158		#[test]
159		fn test_saturating_convert_neg_infinity() {
160			let x: f64 = f64::NEG_INFINITY;
161			let y: i8 = x.saturating_convert();
162			assert_eq!(y, i8::MIN);
163		}
164
165		#[test]
166		fn test_wrapping_convert() {
167			let x: f64 = 42.0;
168			let y: i8 = x.wrapping_convert();
169			assert_eq!(y, 42i8);
170		}
171
172		#[test]
173		fn test_wrapping_convert_nan() {
174			let x: f64 = f64::NAN;
175			let y: i8 = x.wrapping_convert();
176			assert_eq!(y, 0);
177		}
178	}
179
180	mod i16 {
181		use crate::value::number::safe::convert::SafeConvert;
182
183		#[test]
184		fn test_checked_convert_happy() {
185			let x: f64 = 42.0;
186			let y: Option<i16> = x.checked_convert();
187			assert_eq!(y, Some(42i16));
188		}
189
190		#[test]
191		fn test_checked_convert_unhappy() {
192			let x: f64 = 40000.0;
193			let y: Option<i16> = x.checked_convert();
194			assert_eq!(y, None);
195		}
196
197		#[test]
198		fn test_checked_convert_negative() {
199			let x: f64 = -42.0;
200			let y: Option<i16> = x.checked_convert();
201			assert_eq!(y, Some(-42i16));
202		}
203
204		#[test]
205		fn test_checked_convert_nan() {
206			let x: f64 = f64::NAN;
207			let y: Option<i16> = x.checked_convert();
208			assert_eq!(y, None);
209		}
210
211		#[test]
212		fn test_saturating_convert_overflow() {
213			let x: f64 = 40000.0;
214			let y: i16 = x.saturating_convert();
215			assert_eq!(y, i16::MAX);
216		}
217
218		#[test]
219		fn test_saturating_convert_underflow() {
220			let x: f64 = -40000.0;
221			let y: i16 = x.saturating_convert();
222			assert_eq!(y, i16::MIN);
223		}
224
225		#[test]
226		fn test_wrapping_convert() {
227			let x: f64 = 42.0;
228			let y: i16 = x.wrapping_convert();
229			assert_eq!(y, 42i16);
230		}
231	}
232
233	mod i32 {
234		use crate::value::number::safe::convert::SafeConvert;
235
236		#[test]
237		fn test_checked_convert_happy() {
238			let x: f64 = 42.0;
239			let y: Option<i32> = x.checked_convert();
240			assert_eq!(y, Some(42i32));
241		}
242
243		#[test]
244		fn test_checked_convert_unhappy() {
245			let x: f64 = 3e38;
246			let y: Option<i32> = x.checked_convert();
247			assert_eq!(y, None);
248		}
249
250		#[test]
251		fn test_checked_convert_negative() {
252			let x: f64 = -42.0;
253			let y: Option<i32> = x.checked_convert();
254			assert_eq!(y, Some(-42i32));
255		}
256
257		#[test]
258		fn test_saturating_convert_overflow() {
259			let x: f64 = 3e38;
260			let y: i32 = x.saturating_convert();
261			assert_eq!(y, i32::MAX);
262		}
263
264		#[test]
265		fn test_saturating_convert_underflow() {
266			let x: f64 = -3e38;
267			let y: i32 = x.saturating_convert();
268			assert_eq!(y, i32::MIN);
269		}
270
271		#[test]
272		fn test_wrapping_convert() {
273			let x: f64 = 42.0;
274			let y: i32 = x.wrapping_convert();
275			assert_eq!(y, 42i32);
276		}
277	}
278
279	mod i64 {
280		use crate::value::number::safe::convert::SafeConvert;
281
282		#[test]
283		fn test_checked_convert_happy() {
284			let x: f64 = 42.0;
285			let y: Option<i64> = x.checked_convert();
286			assert_eq!(y, Some(42i64));
287		}
288
289		#[test]
290		fn test_checked_convert_unhappy() {
291			let x: f64 = 1e300;
292			let y: Option<i64> = x.checked_convert();
293			assert_eq!(y, None);
294		}
295
296		#[test]
297		fn test_checked_convert_negative() {
298			let x: f64 = -42.0;
299			let y: Option<i64> = x.checked_convert();
300			assert_eq!(y, Some(-42i64));
301		}
302
303		#[test]
304		fn test_saturating_convert_overflow() {
305			let x: f64 = 1e300;
306			let y: i64 = x.saturating_convert();
307			assert_eq!(y, i64::MAX);
308		}
309
310		#[test]
311		fn test_saturating_convert_underflow() {
312			let x: f64 = -1e300;
313			let y: i64 = x.saturating_convert();
314			assert_eq!(y, i64::MIN);
315		}
316
317		#[test]
318		fn test_wrapping_convert() {
319			let x: f64 = 42.0;
320			let y: i64 = x.wrapping_convert();
321			assert_eq!(y, 42i64);
322		}
323	}
324
325	mod i128 {
326		use crate::value::number::safe::convert::SafeConvert;
327
328		#[test]
329		fn test_checked_convert_happy() {
330			let x: f64 = 42.0;
331			let y: Option<i128> = x.checked_convert();
332			assert_eq!(y, Some(42i128));
333		}
334
335		#[test]
336		fn test_checked_convert_unhappy() {
337			let x: f64 = 1e300;
338			let y: Option<i128> = x.checked_convert();
339			assert_eq!(y, None);
340		}
341
342		#[test]
343		fn test_checked_convert_negative() {
344			let x: f64 = -42.0;
345			let y: Option<i128> = x.checked_convert();
346			assert_eq!(y, Some(-42i128));
347		}
348
349		#[test]
350		fn test_saturating_convert_overflow() {
351			let x: f64 = 1e300;
352			let y: i128 = x.saturating_convert();
353			assert_eq!(y, i128::MAX);
354		}
355
356		#[test]
357		fn test_saturating_convert_underflow() {
358			let x: f64 = -1e300;
359			let y: i128 = x.saturating_convert();
360			assert_eq!(y, i128::MIN);
361		}
362
363		#[test]
364		fn test_wrapping_convert() {
365			let x: f64 = 42.0;
366			let y: i128 = x.wrapping_convert();
367			assert_eq!(y, 42i128);
368		}
369	}
370
371	mod u8 {
372		use crate::value::number::safe::convert::SafeConvert;
373
374		#[test]
375		fn test_checked_convert_happy() {
376			let x: f64 = 42.0;
377			let y: Option<u8> = x.checked_convert();
378			assert_eq!(y, Some(42u8));
379		}
380
381		#[test]
382		fn test_checked_convert_unhappy() {
383			let x: f64 = 300.0;
384			let y: Option<u8> = x.checked_convert();
385			assert_eq!(y, None);
386		}
387
388		#[test]
389		fn test_checked_convert_negative() {
390			let x: f64 = -42.0;
391			let y: Option<u8> = x.checked_convert();
392			assert_eq!(y, None);
393		}
394
395		#[test]
396		fn test_checked_convert_nan() {
397			let x: f64 = f64::NAN;
398			let y: Option<u8> = x.checked_convert();
399			assert_eq!(y, None);
400		}
401
402		#[test]
403		fn test_checked_convert_infinity() {
404			let x: f64 = f64::INFINITY;
405			let y: Option<u8> = x.checked_convert();
406			assert_eq!(y, None);
407		}
408
409		#[test]
410		fn test_saturating_convert_overflow() {
411			let x: f64 = 300.0;
412			let y: u8 = x.saturating_convert();
413			assert_eq!(y, u8::MAX);
414		}
415
416		#[test]
417		fn test_saturating_convert_underflow() {
418			let x: f64 = -42.0;
419			let y: u8 = x.saturating_convert();
420			assert_eq!(y, 0);
421		}
422
423		#[test]
424		fn test_saturating_convert_nan() {
425			let x: f64 = f64::NAN;
426			let y: u8 = x.saturating_convert();
427			assert_eq!(y, 0);
428		}
429
430		#[test]
431		fn test_saturating_convert_infinity() {
432			let x: f64 = f64::INFINITY;
433			let y: u8 = x.saturating_convert();
434			assert_eq!(y, u8::MAX);
435		}
436
437		#[test]
438		fn test_wrapping_convert() {
439			let x: f64 = 42.0;
440			let y: u8 = x.wrapping_convert();
441			assert_eq!(y, 42u8);
442		}
443
444		#[test]
445		fn test_wrapping_convert_negative() {
446			let x: f64 = -42.0;
447			let y: u8 = x.wrapping_convert();
448			assert_eq!(y, 0);
449		}
450	}
451
452	mod u16 {
453		use crate::value::number::safe::convert::SafeConvert;
454
455		#[test]
456		fn test_checked_convert_happy() {
457			let x: f64 = 42.0;
458			let y: Option<u16> = x.checked_convert();
459			assert_eq!(y, Some(42u16));
460		}
461
462		#[test]
463		fn test_checked_convert_unhappy() {
464			let x: f64 = 70000.0;
465			let y: Option<u16> = x.checked_convert();
466			assert_eq!(y, None);
467		}
468
469		#[test]
470		fn test_checked_convert_negative() {
471			let x: f64 = -42.0;
472			let y: Option<u16> = x.checked_convert();
473			assert_eq!(y, None);
474		}
475
476		#[test]
477		fn test_saturating_convert_overflow() {
478			let x: f64 = 70000.0;
479			let y: u16 = x.saturating_convert();
480			assert_eq!(y, u16::MAX);
481		}
482
483		#[test]
484		fn test_saturating_convert_underflow() {
485			let x: f64 = -42.0;
486			let y: u16 = x.saturating_convert();
487			assert_eq!(y, 0);
488		}
489
490		#[test]
491		fn test_wrapping_convert() {
492			let x: f64 = 42.0;
493			let y: u16 = x.wrapping_convert();
494			assert_eq!(y, 42u16);
495		}
496	}
497
498	mod u32 {
499		use crate::value::number::safe::convert::SafeConvert;
500
501		#[test]
502		fn test_checked_convert_happy() {
503			let x: f64 = 42.0;
504			let y: Option<u32> = x.checked_convert();
505			assert_eq!(y, Some(42u32));
506		}
507
508		#[test]
509		fn test_checked_convert_unhappy() {
510			let x: f64 = 1e300;
511			let y: Option<u32> = x.checked_convert();
512			assert_eq!(y, None);
513		}
514
515		#[test]
516		fn test_checked_convert_negative() {
517			let x: f64 = -42.0;
518			let y: Option<u32> = x.checked_convert();
519			assert_eq!(y, None);
520		}
521
522		#[test]
523		fn test_saturating_convert_overflow() {
524			let x: f64 = 1e300;
525			let y: u32 = x.saturating_convert();
526			assert_eq!(y, u32::MAX);
527		}
528
529		#[test]
530		fn test_saturating_convert_underflow() {
531			let x: f64 = -42.0;
532			let y: u32 = x.saturating_convert();
533			assert_eq!(y, 0);
534		}
535
536		#[test]
537		fn test_wrapping_convert() {
538			let x: f64 = 42.0;
539			let y: u32 = x.wrapping_convert();
540			assert_eq!(y, 42u32);
541		}
542	}
543
544	mod u64 {
545		use crate::value::number::safe::convert::SafeConvert;
546
547		#[test]
548		fn test_checked_convert_happy() {
549			let x: f64 = 42.0;
550			let y: Option<u64> = x.checked_convert();
551			assert_eq!(y, Some(42u64));
552		}
553
554		#[test]
555		fn test_checked_convert_unhappy() {
556			let x: f64 = 1e300;
557			let y: Option<u64> = x.checked_convert();
558			assert_eq!(y, None);
559		}
560
561		#[test]
562		fn test_checked_convert_negative() {
563			let x: f64 = -42.0;
564			let y: Option<u64> = x.checked_convert();
565			assert_eq!(y, None);
566		}
567
568		#[test]
569		fn test_saturating_convert_overflow() {
570			let x: f64 = 1e300;
571			let y: u64 = x.saturating_convert();
572			assert_eq!(y, u64::MAX);
573		}
574
575		#[test]
576		fn test_saturating_convert_underflow() {
577			let x: f64 = -42.0;
578			let y: u64 = x.saturating_convert();
579			assert_eq!(y, 0);
580		}
581
582		#[test]
583		fn test_wrapping_convert() {
584			let x: f64 = 42.0;
585			let y: u64 = x.wrapping_convert();
586			assert_eq!(y, 42u64);
587		}
588	}
589
590	mod u128 {
591		use crate::value::number::safe::convert::SafeConvert;
592
593		#[test]
594		fn test_checked_convert_happy() {
595			let x: f64 = 42.0;
596			let y: Option<u128> = x.checked_convert();
597			assert_eq!(y, Some(42u128));
598		}
599
600		#[test]
601		fn test_checked_convert_unhappy() {
602			let x: f64 = 1e300;
603			let y: Option<u128> = x.checked_convert();
604			assert_eq!(y, None);
605		}
606
607		#[test]
608		fn test_checked_convert_negative() {
609			let x: f64 = -42.0;
610			let y: Option<u128> = x.checked_convert();
611			assert_eq!(y, None);
612		}
613
614		#[test]
615		fn test_saturating_convert_overflow() {
616			let x: f64 = 1e300;
617			let y: u128 = x.saturating_convert();
618			assert_eq!(y, u128::MAX);
619		}
620
621		#[test]
622		fn test_saturating_convert_underflow() {
623			let x: f64 = -42.0;
624			let y: u128 = x.saturating_convert();
625			assert_eq!(y, 0);
626		}
627
628		#[test]
629		fn test_wrapping_convert() {
630			let x: f64 = 42.0;
631			let y: u128 = x.wrapping_convert();
632			assert_eq!(y, 42u128);
633		}
634	}
635
636	mod decimal {
637		use std::f64::consts::PI;
638
639		use crate::value::{decimal::Decimal, number::safe::convert::SafeConvert};
640
641		#[test]
642		fn test_checked_convert() {
643			let x: f64 = 42.5;
644			let y: Option<Decimal> = x.checked_convert();
645			assert!(y.is_some());
646			let decimal = y.unwrap();
647			assert_eq!(decimal.to_string(), "42.5");
648		}
649
650		#[test]
651		fn test_checked_convert_high_precision() {
652			let x: f64 = 123.456789;
653			let y: Option<Decimal> = x.checked_convert();
654			assert!(y.is_some());
655			let decimal = y.unwrap();
656			// The f64 carries binary rounding noise past the printed digits, so the
657			// assertion is on the prefix rather than the whole rendering.
658			assert!(decimal.to_string().starts_with("123.456789"));
659		}
660
661		#[test]
662		fn test_checked_convert_integer() {
663			let x: f64 = 1000.0;
664			let y: Option<Decimal> = x.checked_convert();
665			assert!(y.is_some());
666			let decimal = y.unwrap();
667			assert_eq!(decimal.to_string(), "1000");
668		}
669
670		#[test]
671		fn test_checked_convert_small_decimal() {
672			let x: f64 = 0.0000125;
673			let y: Option<Decimal> = x.checked_convert();
674			assert!(y.is_some());
675			let decimal = y.unwrap();
676			assert!(decimal.to_string().starts_with("0.0000125"));
677		}
678
679		#[test]
680		fn test_checked_convert_negative() {
681			let x: f64 = -9876.543210;
682			let y: Option<Decimal> = x.checked_convert();
683			assert!(y.is_some());
684			let decimal = y.unwrap();
685			assert!(decimal.to_string().starts_with("-9876.5432"));
686		}
687
688		#[test]
689		fn test_checked_convert_zero() {
690			let x: f64 = 0.0;
691			let y: Option<Decimal> = x.checked_convert();
692			assert!(y.is_some());
693			let decimal = y.unwrap();
694			assert_eq!(decimal.to_string(), "0");
695		}
696
697		#[test]
698		fn test_checked_convert_negative_zero() {
699			let x: f64 = -0.0;
700			let y: Option<Decimal> = x.checked_convert();
701			assert!(y.is_some());
702			let decimal = y.unwrap();
703			// -0.0 should convert to 0
704			assert_eq!(decimal.to_string(), "0");
705		}
706
707		#[test]
708		fn test_checked_convert_nan() {
709			let x: f64 = f64::NAN;
710			let y: Option<Decimal> = x.checked_convert();
711			assert!(y.is_none());
712		}
713
714		#[test]
715		fn test_checked_convert_infinity() {
716			let x: f64 = f64::INFINITY;
717			let y: Option<Decimal> = x.checked_convert();
718			assert!(y.is_none());
719		}
720
721		#[test]
722		fn test_checked_convert_neg_infinity() {
723			let x: f64 = f64::NEG_INFINITY;
724			let y: Option<Decimal> = x.checked_convert();
725			assert!(y.is_none());
726		}
727
728		#[test]
729		fn test_saturating_convert() {
730			let x: f64 = 999999.999999;
731			let y: Decimal = x.saturating_convert();
732			// The literal is not exactly representable, so either neighbouring rounding is
733			// acceptable here.
734			let str_repr = y.to_string();
735			assert!(str_repr.starts_with("999999") || str_repr.starts_with("1000000"));
736		}
737
738		#[test]
739		fn test_saturating_convert_nan() {
740			let x: f64 = f64::NAN;
741			let y: Decimal = x.saturating_convert();
742			assert_eq!(y.to_string(), "0");
743		}
744
745		#[test]
746		fn test_saturating_convert_infinity() {
747			let x: f64 = f64::INFINITY;
748			let y: Decimal = x.saturating_convert();
749			assert_eq!(y.to_string(), "0");
750		}
751
752		#[test]
753		fn test_wrapping_convert() {
754			let x: f64 = 42.0;
755			let y: Decimal = x.wrapping_convert();
756			assert_eq!(y.to_string(), "42");
757		}
758
759		#[test]
760		fn test_wrapping_convert_with_decimal() {
761			let x: f64 = PI;
762			let y: Decimal = x.wrapping_convert();
763			let str_repr = y.to_string();
764			assert!(str_repr.starts_with("3.141"), "actual: {}", str_repr);
765		}
766
767		#[test]
768		fn checked_convert_refuses_floats_past_seventy_six_digits() {
769			// 1e80 has no decimal form, so a checked cast must fail and a saturating one must clamp with
770			// its sign.
771			let y: Option<Decimal> = 1e80f64.checked_convert();
772			assert_eq!(y, None);
773			let y: Decimal = 1e80f64.saturating_convert();
774			assert_eq!(y.to_string(), "9".repeat(76));
775			let y: Decimal = (-1e80f64).saturating_convert();
776			assert_eq!(y.to_string(), format!("-{}", "9".repeat(76)));
777			let y: Option<Decimal> = 1e-300f64.checked_convert();
778			assert_eq!(y, Some(Decimal::zero()));
779		}
780	}
781}