extendr_api/conversions/
try_into_int.rs
1use std::fmt::{Debug, Display, Formatter};
2use std::num::FpCategory;
3
4#[derive(Debug, Copy, Clone, Eq, PartialEq)]
5pub enum ConversionError {
6 Underflow,
7 Overflow,
8 NotIntegerish,
9}
10
11impl Display for ConversionError {
12 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
13 match self {
14 ConversionError::Underflow => write!(f, "underflow"),
15 ConversionError::Overflow => write!(f, "overflow"),
16 ConversionError::NotIntegerish => write!(f, "not a whole number"),
17 }
18 }
19}
20
21pub(crate) trait FloatToInt<T: Sized> {
22 fn try_into_int(&self) -> Result<T, ConversionError>
23 where
24 Self: Sized;
25}
26
27macro_rules! impl_into_integerish {
28 ($float_type:ty, $int_type:ty) => {
29 impl FloatToInt<$int_type> for $float_type {
30 fn try_into_int(&self) -> Result<$int_type, ConversionError> {
31 match self.classify() {
32 FpCategory::Nan | FpCategory::Subnormal => Err(ConversionError::NotIntegerish),
33 FpCategory::Zero => Ok(<$int_type>::default()),
34 FpCategory::Infinite if self.is_sign_positive() => {
35 Err(ConversionError::Overflow)
36 }
37 FpCategory::Infinite => Err(ConversionError::Underflow),
38 FpCategory::Normal => {
39 let truncated_value = self.trunc();
40 const MIN_VALUE: $float_type = <$int_type>::MIN as $float_type;
41 if truncated_value < MIN_VALUE {
42 return Err(ConversionError::Underflow);
43 }
44 const MAX_VALUE: $float_type = <$int_type>::MAX as $float_type;
45 if truncated_value > MAX_VALUE {
46 return Err(ConversionError::Overflow);
47 }
48 if !truncated_value.eq(self) {
49 return Err(ConversionError::NotIntegerish);
50 }
51 return Ok(truncated_value as $int_type);
52 }
53 }
54 }
55 }
56 };
57}
58
59impl_into_integerish!(f64, isize);
60impl_into_integerish!(f64, usize);
61impl_into_integerish!(f64, i128);
62impl_into_integerish!(f64, u128);
63impl_into_integerish!(f64, i64);
64impl_into_integerish!(f64, u64);
65impl_into_integerish!(f64, i32);
66impl_into_integerish!(f64, u32);
67impl_into_integerish!(f64, i16);
68impl_into_integerish!(f64, u16);
69impl_into_integerish!(f64, i8);
70impl_into_integerish!(f64, u8);
71
72#[cfg(test)]
73mod try_into_int_tests {
74 use crate as extendr_api;
75 use crate::conversions::try_into_int::{ConversionError, FloatToInt};
76 use crate::{test, CanBeNA};
77
78 type ConversionResult<T, E> = std::result::Result<T, E>;
79
80 #[test]
81 fn test_exact_zero() {
82 let value = 0.0;
83 let int_value: ConversionResult<i32, _> = value.try_into_int();
84 assert_eq!(int_value, Ok(0));
85 }
86
87 #[test]
88 fn test_exact_negative_zero() {
89 let value = -0.0;
90 let int_value: ConversionResult<i32, _> = value.try_into_int();
91 assert_eq!(int_value, Ok(0));
92 }
93
94 #[test]
95 fn large_value_overflow() {
96 let value: f64 = 1.000000020000001e200;
97 let int_value: ConversionResult<i32, _> = value.try_into_int();
98 assert_eq!(int_value, Err(ConversionError::Overflow))
99 }
100
101 #[test]
102 fn large_negative_value_underflow() {
103 let value: f64 = -1.000000020000001e200;
104 let int_value: ConversionResult<i32, _> = value.try_into_int();
105 assert_eq!(int_value, Err(ConversionError::Underflow))
106 }
107
108 #[test]
109 fn na_not_integerish() {
110 test! {
112 let value: f64 = f64::na();
113 let int_value: ConversionResult<i32, _> = value.try_into_int();
114 assert_eq!(int_value, Err(ConversionError::NotIntegerish));
115 }
116 }
117
118 #[test]
119 fn fractional_not_integerish() {
120 let value: f64 = 1.5;
121 let int_value: ConversionResult<i32, _> = value.try_into_int();
122 assert_eq!(int_value, Err(ConversionError::NotIntegerish))
123 }
124
125 #[test]
126 fn negative_fractional_not_integerish() {
127 let value: f64 = -1.5;
128 let int_value: ConversionResult<i32, _> = value.try_into_int();
129 assert_eq!(int_value, Err(ConversionError::NotIntegerish))
130 }
131
132 #[test]
133 fn small_integerish_negative_to_unsigned_underflow() {
134 let value: f64 = -1.0;
135 let int_value: ConversionResult<u32, _> = value.try_into_int();
136 assert_eq!(int_value, Err(ConversionError::Underflow))
137 }
138
139 #[test]
140 fn integerish_converts_successfully() {
141 let value: f64 = 42.0;
142
143 assert_eq!(FloatToInt::<i128>::try_into_int(&value), Ok(42));
144 assert_eq!(FloatToInt::<i64>::try_into_int(&value), Ok(42));
145 assert_eq!(FloatToInt::<i32>::try_into_int(&value), Ok(42));
146 assert_eq!(FloatToInt::<i16>::try_into_int(&value), Ok(42));
147 assert_eq!(FloatToInt::<i8>::try_into_int(&value), Ok(42));
148
149 assert_eq!(FloatToInt::<u128>::try_into_int(&value), Ok(42));
150 assert_eq!(FloatToInt::<u64>::try_into_int(&value), Ok(42));
151 assert_eq!(FloatToInt::<u32>::try_into_int(&value), Ok(42));
152 assert_eq!(FloatToInt::<u16>::try_into_int(&value), Ok(42));
153 assert_eq!(FloatToInt::<u8>::try_into_int(&value), Ok(42));
154 }
155}