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        // NA-checks are unavailable unless R is set up
111        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}