digitize/
lib.rs

1//! Traits for iterating over the digits of any primitive integer or float
2
3use tinyvec::ArrayVec;
4
5pub trait Digits {
6    const MAX_NUMBER_OF_DIGITS: usize;
7    type R;
8
9    fn digits(self) -> Self::R;
10}
11
12pub trait FloatDigits {
13    /// Digits left of the decimal point
14    fn digits_left_of_dot(&self) -> Box<[u8]>;
15
16    /// Digits right of the decimal point
17    fn digits_right_of_dot(&self) -> Box<[u8]>;
18
19    /// Digits left of the decimal point, and right of the decimal point in that order.
20    fn digits_left_then_right_of_dot(&self) -> [Box<[u8]>; 2] {
21        [self.digits_left_of_dot(), self.digits_right_of_dot()]
22    }
23}
24
25macro_rules! first_power_of_two_gte {
26    ($x: expr) => {
27        if $x.is_power_of_two() {
28            $x
29        } else {
30            $x.next_power_of_two()
31        }
32    };
33}
34
35#[macro_export]
36macro_rules! impl_digits {
37    ($t: ty, $max: expr, $digits_of_min: expr, $is_signed_type: expr) => {
38        impl Digits for $t {
39            const MAX_NUMBER_OF_DIGITS: usize = $max.ilog10() as usize + 1;
40
41            type R = ArrayVec<[u8; first_power_of_two_gte!(Self::MAX_NUMBER_OF_DIGITS)]>;
42
43            #[allow(
44                clippy::cast_possible_truncation,
45                clippy::cast_sign_loss,
46                unused_comparisons
47            )]
48            fn digits(mut self) -> Self::R {
49                let mut v = ArrayVec::new();
50
51                if $is_signed_type {
52                    if self == Self::MIN {
53                        v.extend($digits_of_min.into_iter());
54                        return v;
55                    }
56                    if self < 0 {
57                        0 - self
58                    } else {
59                        self
60                    };
61                }
62
63                while self > 10 {
64                    v.push((self % 10) as u8);
65                    self /= 10;
66                }
67                v.push((self % 10) as u8);
68                v.reverse();
69                v
70            }
71        }
72    };
73}
74
75impl_digits!(u8, u8::MAX, [0], false);
76impl_digits!(u16, u16::MAX, [0], false);
77impl_digits!(u32, u32::MAX, [0], false);
78impl_digits!(u64, u64::MAX, [0], false);
79impl_digits!(u128, u128::MAX, [0], false);
80impl_digits!(usize, usize::MAX, [0], false);
81
82impl_digits!(i8, i8::MAX, [1, 2, 8], true);
83impl_digits!(i16, i16::MAX, [3, 2, 7, 6, 8], true);
84impl_digits!(i32, i32::MAX, [2, 1, 4, 7, 4, 8, 3, 6, 4, 8], true);
85impl_digits!(
86    i64,
87    i64::MAX,
88    [9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8],
89    true
90);
91impl_digits!(
92    i128,
93    i128::MAX,
94    [
95        1, 7, 0, 1, 4, 1, 1, 8, 3, 4, 6, 0, 4, 6, 9, 2, 3, 1, 7, 3, 1, 6, 8, 7, 3, 0, 3, 7, 1, 5,
96        8, 8, 4, 1, 0, 5, 7, 2, 8
97    ],
98    true
99);
100impl_digits!(
101    isize,
102    isize::MAX,
103    [9, 2, 2, 3, 3, 7, 2, 0, 3, 6, 8, 5, 4, 7, 7, 5, 8, 0, 8],
104    true
105);
106
107/// Unsafely casts the characters in `x`'s string representation to u32's.
108/// # Safety
109/// Only use if `x`'s String representation only contains digits in base 10
110#[inline(always)]
111unsafe fn stringable_to_digits<T: ToString>(x: T) -> Box<[u8]> {
112    x.to_string()
113        .chars()
114        .map(|c| match c {
115            '0' => 0,
116            '1' => 1,
117            '2' => 2,
118            '3' => 3,
119            '4' => 4,
120            '5' => 5,
121            '6' => 6,
122            '7' => 7,
123            '8' => 8,
124            _ => 9,
125        })
126        .collect()
127}
128
129impl FloatDigits for f64 {
130    fn digits_left_of_dot(&self) -> Box<[u8]> {
131        if self.is_finite() {
132            unsafe { stringable_to_digits(self.trunc()) }
133        } else {
134            Box::new([]) // Infinity & NaN have no digits
135        }
136    }
137
138    fn digits_right_of_dot(&self) -> Box<[u8]> {
139        if self.is_finite() {
140            unsafe { stringable_to_digits(self.fract()) }
141        } else {
142            Box::new([]) // Infinity & NaN have no digits
143        }
144    }
145}