fmt_cmp/int/
traits.rs

1// We are using a hack to polyfill the `ilog` methods.
2#![allow(clippy::incompatible_msrv)]
3
4/// A trait for integer types that can be compared with [`cmp_int`](super::cmp_int) function.
5///
6/// This trait is sealed and cannot be implemented outside of `fmt_cmp` crate.
7pub trait Integer: private::Sealed {}
8
9mod private {
10    pub trait Sealed {
11        fn copy(&self) -> Self;
12        fn eq(self, other: Self) -> bool;
13        fn lt(self, other: Self) -> bool;
14        fn checked_ilog(self, base: Self) -> Option<u32>;
15        fn ilog(self, base: u32) -> u32;
16        fn checked_ilog10(self) -> Option<u32>;
17        fn ilog10(self) -> u32;
18        /// Calculates `self / base.pow(exp)`.
19        fn invpow(self, base: u32, exp: u32) -> Self;
20    }
21}
22
23macro_rules! sealed_common {
24    () => {
25        fn copy(&self) -> Self {
26            *self
27        }
28
29        fn eq(self, other: Self) -> bool {
30            self == other
31        }
32
33        fn lt(self, other: Self) -> bool {
34            self < other
35        }
36
37        fn checked_ilog(mut self, base: Self) -> Option<u32> {
38            // Well, the function isn't _checking_ anything in fact, since we are not going to call
39            // it with `base == 0` and defaulting to `0` if `None` is returned.
40            // It is returning `Option<_>` only for consistency with the inherent `checked_ilog`.
41            if base <= 1 {
42                assert!(base > 0);
43                return Some(0);
44            }
45            let mut x = 0;
46            while self >= base {
47                self /= base;
48                x += 1;
49            }
50            Some(x)
51        }
52
53        // `unstable_name_collisions` here are intentional. This will automatically use the inherent
54        // `checked_ilog` if available or uses the fallback impl otherwise.
55        #[allow(unstable_name_collisions)]
56        fn ilog(self, base: u32) -> u32 {
57            if let Some(x) = self.checked_ilog(base as _) {
58                x
59            } else {
60                0
61            }
62        }
63
64        #[allow(unstable_name_collisions)]
65        fn ilog10(self) -> u32 {
66            if let Some(x) = self.checked_ilog10() {
67                x
68            } else {
69                0
70            }
71        }
72
73        fn invpow(mut self, base: u32, mut exp: u32) -> Self {
74            // Based on `{integer}::pow` implementation from `core`.
75            // <https://doc.rust-lang.org/1.57.0/src/core/num/uint_macros.rs.html#1926-1946>
76            // The variant implementation is provided to prevent overflows in cases like
77            // `10_u32.pow(u128::MAX.ilog10() - 0_u128.ilog10())` without resorting to `u128::pow`.
78
79            if exp == 0 {
80                return self;
81            }
82            // The `exp` argument in our use case is `Self.ilog(base) - Self.ilog(base)`,
83            // which would be zero if `base > Self::MAX` so the `as` conversion is lossless.
84            let mut base = base as Self;
85
86            while exp > 1 {
87                if (exp & 1) == 1 {
88                    self /= base;
89                }
90                exp /= 2;
91                base = base * base;
92            }
93
94            self / base
95        }
96    };
97}
98
99// These specialized `ilog10` implementations are based on `core`'s ones.
100// <https://doc.rust-lang.org/1.80.0/src/core/num/int_log10.rs.html#52-92>
101
102impl private::Sealed for u32 {
103    sealed_common!();
104
105    #[allow(unstable_name_collisions)]
106    fn checked_ilog10(mut self) -> Option<u32> {
107        let x = if self >= 100_000 {
108            self /= 100_000;
109            5
110        } else {
111            0
112        };
113
114        // Checking that `self` would be `<= u16::MAX` now even if the argument were `u32::MAX`...
115        assert!((!0_u32) / 100_000 <= (!0_u16) as u32);
116        debug_assert!(self <= (!0_u16) as u32); // ... so that this holds.
117
118        Some((self as u16).ilog(10) + x)
119    }
120}
121
122impl private::Sealed for u64 {
123    sealed_common!();
124
125    #[allow(unstable_name_collisions)]
126    fn checked_ilog10(mut self) -> Option<u32> {
127        let x = if self >= 10_000_000_000 {
128            self /= 10_000_000_000;
129            10
130        } else {
131            0
132        };
133        assert!((!0_u64) / 10_000_000_000 <= (!0_u32) as u64);
134        debug_assert!(self <= (!0_u32) as u64);
135        Some((self as u32).ilog(10) + x)
136    }
137}
138
139impl private::Sealed for u128 {
140    sealed_common!();
141
142    #[allow(unstable_name_collisions)]
143    fn checked_ilog10(mut self) -> Option<u32> {
144        if self >= 100_000_000_000_000_000_000_000_000_000_000 {
145            self /= 100_000_000_000_000_000_000_000_000_000_000;
146            assert!((!0_u128) / 100_000_000_000_000_000_000_000_000_000_000 <= (!0_u32) as u128);
147            debug_assert!(self <= (!0_u32) as u128);
148            return Some((self as u32).ilog(10) + 32);
149        }
150        let x = if self >= 10_000_000_000_000_000 {
151            self /= 10_000_000_000_000_000;
152            16
153        } else {
154            0
155        };
156        assert!(
157            (100_000_000_000_000_000_000_000_000_000_000 - 1) / 10_000_000_000_000_000
158                <= (!0_u64) as u128
159        );
160        debug_assert!(self <= (!0_u64) as u128);
161        Some((self as u64).ilog(10) + x)
162    }
163}
164
165macro_rules! generic_ilog10 {
166    ($($ty:ty)*) => {$(
167        impl private::Sealed for $ty {
168            sealed_common!();
169
170            #[allow(unstable_name_collisions)]
171            fn checked_ilog10(self) -> Option<u32> {
172                self.checked_ilog(10)
173            }
174        }
175    )*};
176}
177
178generic_ilog10! { u8 u16 }
179
180#[cfg(target_pointer_width = "64")]
181impl private::Sealed for usize {
182    sealed_common!();
183    #[allow(unstable_name_collisions)]
184    fn checked_ilog10(self) -> Option<u32> {
185        (self as u64).checked_ilog10()
186    }
187}
188
189#[cfg(target_pointer_width = "32")]
190impl private::Sealed for usize {
191    sealed_common!();
192    #[allow(unstable_name_collisions)]
193    fn checked_ilog10(self) -> Option<u32> {
194        (self as u32).checked_ilog10()
195    }
196}
197
198#[cfg(not(any(target_pointer_width = "64", target_pointer_width = "32")))]
199generic_ilog10! { usize }
200
201impl Integer for u8 {}
202impl Integer for u16 {}
203impl Integer for u32 {}
204impl Integer for u64 {}
205impl Integer for u128 {}
206impl Integer for usize {}