Skip to main content

fixed/
log10.rs

1// Copyright © 2018–2026 Trevor Spiteri
2
3// This library is free software: you can redistribute it and/or
4// modify it under the terms of either
5//
6//   * the Apache License, Version 2.0 or
7//   * the MIT License
8//
9// at your option.
10//
11// You should have recieved copies of the Apache License and the MIT
12// License along with the library. If not, see
13// <https://www.apache.org/licenses/LICENSE-2.0> and
14// <https://opensource.org/licenses/MIT>.
15
16// the value must be positive for all public functions
17
18pub mod frac_part {
19    use core::num::NonZero;
20
21    // MAX / 1000 (0) < val <= MAX (255)
22    // -3 <= log <= -1
23    #[inline]
24    pub const fn u8(val: NonZero<u8>) -> i32 {
25        let val = val.get();
26        if val > 25 {
27            -1
28        } else if val > 2 {
29            -2
30        } else {
31            -3
32        }
33    }
34
35    // MAX / 100_000 (0) < val <= MAX (65_535)
36    // -5 <= log <= -1
37    #[inline]
38    pub const fn u16(val: NonZero<u16>) -> i32 {
39        let val = val.get();
40        if val > 6553 {
41            -1
42        } else if val > 655 {
43            -2
44        } else if val > 65 {
45            -3
46        } else if val > 6 {
47            -4
48        } else {
49            -5
50        }
51    }
52
53    // 0 < val <= MAX
54    // -10 <= log <= -1
55    pub const fn u32(val: NonZero<u32>) -> i32 {
56        const MAX: u32 = u32::MAX;
57        let mut val = val.get();
58        if val <= MAX / 100_000_000 {
59            val *= 100_000_000;
60            // At this point, we have shifted out 8 digits, and we can only shift out 2 more.
61            // We can only check up to -2 more because -10 <= log <= -8.
62            if val > MAX / 10 {
63                -9
64            } else {
65                debug_assert!(val > MAX / 100);
66                -10
67            }
68        } else {
69            greater_equal_m8_u32(val)
70        }
71    }
72
73    // 0 < val <= MAX
74    // -20 <= log <= -1
75    pub const fn u64(val: NonZero<u64>) -> i32 {
76        const MAX: u64 = u64::MAX;
77        let mut val = val.get();
78        let mut log = 0;
79        if val <= MAX / 10_000_000_000_000_000 {
80            // After this, we can only check up to -4 more because -20 <= log <= -16.
81            // That is, we can skip the checks against MAX / 100_000_000 and MAX / 10_000.
82            val *= 10_000_000_000_000_000;
83            log += -16;
84        } else {
85            if val <= MAX / 100_000_000 {
86                val *= 100_000_000;
87                log += -8;
88            }
89            if val <= MAX / 10_000 {
90                val *= 10_000;
91                log += -4;
92            }
93        }
94        log + if val > MAX / 10 {
95            -1
96        } else if val > MAX / 100 {
97            -2
98        } else if val > MAX / 1000 {
99            -3
100        } else {
101            debug_assert!(val > MAX / 10_000);
102            -4
103        }
104    }
105
106    // 0 < val <= MAX
107    // -39 <= log <= -1
108    pub const fn u128(val: NonZero<u128>) -> i32 {
109        const MAX: u128 = u128::MAX;
110        let mut val = val.get();
111        let mut log = 0;
112        if val <= MAX / 100_000_000_000_000_000_000_000_000_000_000 {
113            val *= 100_000_000_000_000_000_000_000_000_000_000;
114            // At this point we have shifted out 32 digits, and we can only shift out 7 more.
115            // We can
116            //   * use val >> 96 because we have shifted out 32 decimal digits (106 bits)
117            //   * only check up to -8 more because -39 <= log <= -32
118            return -32 + greater_equal_m8_u32((val >> 96) as u32);
119        }
120        if val <= MAX / 10_000_000_000_000_000 {
121            val *= 10_000_000_000_000_000;
122            log += -16;
123        }
124        if val <= MAX / 100_000_000 {
125            val *= 100_000_000;
126            log += -8;
127        }
128        if log == -24 {
129            // At this point we have shifted out 24 digits, and we can only shift out 15 more.
130            // We can
131            //   * use val >> 64 because we have shifted out 24 decimal digits (79 bits)
132            //   * only check up to -8 more because -32 <= log <= -24
133            return -24 + greater_equal_m8_u64((val >> 64) as u64);
134        }
135        // We have *not* shifted out enough decimal digits, so we must *not* convert to u32 or u64.
136        if val <= MAX / 10_000 {
137            val *= 10_000;
138            log += -4;
139        }
140        log + if val > MAX / 10 {
141            -1
142        } else if val > MAX / 100 {
143            -2
144        } else if val > MAX / 1000 {
145            -3
146        } else {
147            debug_assert!(val > MAX / 10_000);
148            -4
149        }
150    }
151
152    // MAX / 100_000_000 < val <= MAX
153    // -8 <= log <= -1
154    const fn greater_equal_m8_u32(mut val: u32) -> i32 {
155        const MAX: u32 = u32::MAX;
156        debug_assert!(val > MAX / 100_000_000);
157        let mut log = 0;
158        if val <= MAX / 10_000 {
159            val *= 10_000;
160            log += -4;
161        }
162        log + if val > MAX / 10 {
163            -1
164        } else if val > MAX / 100 {
165            -2
166        } else if val > MAX / 1000 {
167            -3
168        } else {
169            debug_assert!(val > MAX / 10_000);
170            -4
171        }
172    }
173
174    // MAX / 100_000_000 < val <= MAX
175    // -8 <= log <= 1
176    const fn greater_equal_m8_u64(mut val: u64) -> i32 {
177        const MAX: u64 = u64::MAX;
178        debug_assert!(val > MAX / 100_000_000);
179        let mut log = 0;
180        if val <= MAX / 10_000 {
181            val *= 10_000;
182            log += -4;
183        }
184        log + if val > MAX / 10 {
185            -1
186        } else if val > MAX / 100 {
187            -2
188        } else if val > MAX / 1000 {
189            -3
190        } else {
191            debug_assert!(val > MAX / 10_000);
192            -4
193        }
194    }
195}
196
197// check log10() and log(10) in tandem
198#[cfg(test)]
199mod tests {
200    use crate::log;
201    use crate::log::Base;
202    use crate::log10;
203    use core::num::NonZero;
204
205    const DEC: Base = match Base::new(10) {
206        Some(s) => s,
207        None => unreachable!(),
208    };
209
210    macro_rules! check_loop {
211        ($T:ident) => {
212            for i in 0..=$T::MAX.ilog10() as i32 {
213                let p = (10 as $T).pow(i as u32);
214                let nz = NonZero::<$T>::new(p).unwrap();
215                if i > 0 {
216                    let nz_m1 = NonZero::<$T>::new(p - 1).unwrap();
217                    assert_eq!((p - 1).ilog10() as i32, i - 1);
218                    assert_eq!(log::int_part::$T(nz_m1, DEC), i - 1);
219                }
220                assert_eq!(p.ilog10() as i32, i);
221                assert_eq!(log::int_part::$T(nz, DEC), i);
222                let nz_p1 = NonZero::<$T>::new(p + 1).unwrap();
223                assert_eq!((p + 1).ilog10() as i32, i);
224                assert_eq!(log::int_part::$T(nz_p1, DEC), i);
225            }
226
227            for i in 0..-log10::frac_part::$T(NonZero::<$T>::new(1).unwrap()) {
228                let p = <$T>::MAX / (10 as $T).pow(i as u32);
229                let nz = NonZero::<$T>::new(p).unwrap();
230                if p > 1 {
231                    let nz_m1 = NonZero::<$T>::new(p - 1).unwrap();
232                    assert_eq!(log10::frac_part::$T(nz_m1), -1 - i);
233                    assert_eq!(log::frac_part::$T(nz_m1, DEC), -1 - i);
234                }
235                assert_eq!(log10::frac_part::$T(nz), -1 - i);
236                assert_eq!(log::frac_part::$T(nz, DEC), -1 - i);
237                if i > 0 {
238                    let nz_p1 = NonZero::<$T>::new(p + 1).unwrap();
239                    assert_eq!(log10::frac_part::$T(nz_p1), -i);
240                    assert_eq!(log::frac_part::$T(nz_p1, DEC), -i);
241                }
242            }
243        };
244    }
245
246    #[test]
247    fn log10_u8() {
248        let one = NonZero::<u8>::new(1).unwrap();
249
250        assert_eq!(1u8.ilog10(), 0);
251        assert_eq!(log::int_part::u8(one, DEC), 0);
252        assert_eq!(u8::MAX.ilog10(), 2);
253        assert_eq!(log::int_part::u8(NonZero::<u8>::MAX, DEC), 2);
254        assert_eq!(log10::frac_part::u8(one), -3);
255        assert_eq!(log::frac_part::u8(one, DEC), -3);
256        assert_eq!(log10::frac_part::u8(NonZero::<u8>::MAX), -1);
257        assert_eq!(log::frac_part::u8(NonZero::<u8>::MAX, DEC), -1);
258
259        check_loop! { u8 }
260    }
261
262    #[test]
263    fn log10_u16() {
264        let one = NonZero::<u16>::new(1).unwrap();
265
266        assert_eq!(1u16.ilog10(), 0);
267        assert_eq!(log::int_part::u16(one, DEC), 0);
268        assert_eq!(u16::MAX.ilog10(), 4);
269        assert_eq!(log::int_part::u16(NonZero::<u16>::MAX, DEC), 4);
270        assert_eq!(log10::frac_part::u16(one), -5);
271        assert_eq!(log::frac_part::u16(one, DEC), -5);
272        assert_eq!(log10::frac_part::u16(NonZero::<u16>::MAX), -1);
273        assert_eq!(log::frac_part::u16(NonZero::<u16>::MAX, DEC), -1);
274
275        check_loop! { u16 }
276    }
277
278    #[test]
279    fn log10_u32() {
280        let one = NonZero::<u32>::new(1).unwrap();
281
282        assert_eq!(1u32.ilog10(), 0);
283        assert_eq!(log::int_part::u32(NonZero::<u32>::new(1).unwrap(), DEC), 0);
284        assert_eq!(u32::MAX.ilog10(), 9);
285        assert_eq!(log::int_part::u32(NonZero::<u32>::MAX, DEC), 9);
286        assert_eq!(log10::frac_part::u32(one), -10);
287        assert_eq!(log::frac_part::u32(one, DEC), -10);
288        assert_eq!(log10::frac_part::u32(NonZero::<u32>::MAX), -1);
289        assert_eq!(log::frac_part::u32(NonZero::<u32>::MAX, DEC), -1);
290
291        check_loop! { u32 }
292    }
293
294    #[test]
295    fn log10_u64() {
296        let one = NonZero::<u64>::new(1).unwrap();
297
298        assert_eq!(1u64.ilog10(), 0);
299        assert_eq!(log::int_part::u64(one, DEC), 0);
300        assert_eq!(u64::MAX.ilog10(), 19);
301        assert_eq!(log::int_part::u64(NonZero::<u64>::MAX, DEC), 19);
302        assert_eq!(log10::frac_part::u64(one), -20);
303        assert_eq!(log::frac_part::u64(one, DEC), -20);
304        assert_eq!(log10::frac_part::u64(NonZero::<u64>::MAX), -1);
305        assert_eq!(log::frac_part::u64(NonZero::<u64>::MAX, DEC), -1);
306
307        check_loop! { u64 }
308    }
309
310    #[test]
311    fn log10_u128() {
312        let one = NonZero::<u128>::new(1).unwrap();
313
314        assert_eq!(1u128.ilog10(), 0);
315        assert_eq!(log::int_part::u128(one, DEC), 0);
316        assert_eq!(u128::MAX.ilog10(), 38);
317        assert_eq!(log::int_part::u128(NonZero::<u128>::MAX, DEC), 38);
318        assert_eq!(log10::frac_part::u128(one), -39);
319        assert_eq!(log::frac_part::u128(one, DEC), -39);
320        assert_eq!(log10::frac_part::u128(NonZero::<u128>::MAX), -1);
321        assert_eq!(log::frac_part::u128(NonZero::<u128>::MAX, DEC), -1);
322
323        check_loop! { u128 }
324    }
325}