Skip to main content

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::conversions::try_into_int::{ConversionError, FloatToInt};
75    use crate::{test, CanBeNA};
76
77    type ConversionResult<T, E> = std::result::Result<T, E>;
78
79    #[test]
80    fn test_exact_zero() {
81        let value = 0.0;
82        let int_value: ConversionResult<i32, _> = value.try_into_int();
83        assert_eq!(int_value, Ok(0));
84    }
85
86    #[test]
87    fn test_exact_negative_zero() {
88        let value = -0.0;
89        let int_value: ConversionResult<i32, _> = value.try_into_int();
90        assert_eq!(int_value, Ok(0));
91    }
92
93    #[test]
94    fn large_value_overflow() {
95        let value: f64 = 1.000000020000001e200;
96        let int_value: ConversionResult<i32, _> = value.try_into_int();
97        assert_eq!(int_value, Err(ConversionError::Overflow))
98    }
99
100    #[test]
101    fn large_negative_value_underflow() {
102        let value: f64 = -1.000000020000001e200;
103        let int_value: ConversionResult<i32, _> = value.try_into_int();
104        assert_eq!(int_value, Err(ConversionError::Underflow))
105    }
106
107    #[test]
108    fn na_not_integerish() {
109        // NA-checks are unavailable unless R is set up
110        test! {
111            let value: f64 = f64::na();
112            let int_value: ConversionResult<i32, _> = value.try_into_int();
113            assert_eq!(int_value, Err(ConversionError::NotIntegerish));
114        }
115    }
116
117    #[test]
118    fn fractional_not_integerish() {
119        let value: f64 = 1.5;
120        let int_value: ConversionResult<i32, _> = value.try_into_int();
121        assert_eq!(int_value, Err(ConversionError::NotIntegerish))
122    }
123
124    #[test]
125    fn negative_fractional_not_integerish() {
126        let value: f64 = -1.5;
127        let int_value: ConversionResult<i32, _> = value.try_into_int();
128        assert_eq!(int_value, Err(ConversionError::NotIntegerish))
129    }
130
131    #[test]
132    fn small_integerish_negative_to_unsigned_underflow() {
133        let value: f64 = -1.0;
134        let int_value: ConversionResult<u32, _> = value.try_into_int();
135        assert_eq!(int_value, Err(ConversionError::Underflow))
136    }
137
138    #[test]
139    fn integerish_converts_successfully() {
140        let value: f64 = 42.0;
141
142        assert_eq!(FloatToInt::<i128>::try_into_int(&value), Ok(42));
143        assert_eq!(FloatToInt::<i64>::try_into_int(&value), Ok(42));
144        assert_eq!(FloatToInt::<i32>::try_into_int(&value), Ok(42));
145        assert_eq!(FloatToInt::<i16>::try_into_int(&value), Ok(42));
146        assert_eq!(FloatToInt::<i8>::try_into_int(&value), Ok(42));
147
148        assert_eq!(FloatToInt::<u128>::try_into_int(&value), Ok(42));
149        assert_eq!(FloatToInt::<u64>::try_into_int(&value), Ok(42));
150        assert_eq!(FloatToInt::<u32>::try_into_int(&value), Ok(42));
151        assert_eq!(FloatToInt::<u16>::try_into_int(&value), Ok(42));
152        assert_eq!(FloatToInt::<u8>::try_into_int(&value), Ok(42));
153    }
154}