num_width/
lib.rs

1//! Traits to determine the width of a number.
2
3/// Determine the width needed to display a number.
4pub trait NumberWidth {
5    /// Digit count in the given base.
6    ///
7    /// This is expected to be zero for the number zero, and negative for negative numbers.
8    fn signed_digit_count(&self, base: u64) -> i64;
9
10    /// Width including leading minus sign if the number is negative.
11    ///
12    /// # Example
13    ///
14    /// ```rust
15    /// use num_width::NumberWidth;
16    /// assert_eq!(0u8.signed_width(), 1);
17    /// assert_eq!(15u8.signed_width(), 2);
18    /// assert_eq!((-33i8).signed_width(), 3);
19    /// ```
20    fn signed_width(&self) -> u64 {
21        self.signed_width_base(10)
22    }
23
24    /// Width of the number in the given base, including leading minus sign for negative numbers.
25    ///
26    /// # Example
27    ///
28    /// ```rust
29    /// use num_width::NumberWidth;
30    /// assert_eq!(0xAu8.signed_width_base(16), 1);
31    /// assert_eq!(0xFFu8.signed_width_base(16), 2);
32    /// assert_eq!((-0xAAi16).signed_width_base(16), 3);
33    /// ```
34    fn signed_width_base(&self, base: u64) -> u64 {
35        let count = match self.signed_digit_count(base) {
36            count if count < 0 => (-count + 1) as u64,
37            count => count as u64,
38        };
39
40        count.max(1)
41    }
42
43    /// Width needed to represent number.
44    ///
45    /// This does not include the width needed for a leading minus sign in case the number is
46    /// negative. If that is what you need, consider using the [signed_width()][] method.
47    ///
48    /// # Example
49    ///
50    /// ```rust
51    /// use num_width::NumberWidth;
52    /// assert_eq!(0u8.width(), 1);
53    /// assert_eq!(15u8.width(), 2);
54    /// assert_eq!((-33i8).width(), 2);
55    /// ```
56    fn width(&self) -> u64 {
57        self.width_base(10)
58    }
59
60    /// Width needed to represent number in the given base.
61    ///
62    /// This does not include the width needed for a leading minus sign in case the number is
63    /// negative. If that is what you need, consider using the [signed_width_base()][] method.
64    fn width_base(&self, base: u64) -> u64 {
65        (self.signed_digit_count(base).abs() as u64).max(1)
66    }
67}
68
69fn digit_count(num: u64, base: u64) -> u64 {
70    let mut width = 0;
71    let mut cur = num;
72    while cur > 0 {
73        cur /= base;
74        width += 1;
75    }
76    width
77}
78
79#[test]
80fn test_digit_count() {
81    for base in 2..255 {
82        // zero always has count of zero
83        assert_eq!(digit_count(0, base), 0);
84        for num in 1..base {
85            assert_eq!(digit_count(num, base), 1);
86        }
87
88        // test positive digit counts
89        let mut num: u64 = 1;
90        for i in 1.. {
91            num = match num.checked_mul(base) {
92                Some(num) => num,
93                None => break,
94            };
95            assert_eq!(digit_count(num - 1, base), i);
96            assert_eq!(digit_count(num, base), i + 1);
97        }
98    }
99}
100
101fn signed_digit_count(num: i64, base: u64) -> i64 {
102    let sign = num.signum();
103    // using just abs() here will panic when num is i64::MIN.
104    let num = num.checked_abs().map(|num| num as u64).unwrap_or(i64::MAX as u64 + 1);
105    let digit_count = digit_count(num, base);
106    digit_count as i64 * sign
107}
108
109#[test]
110fn test_signed_digit_count() {
111    for base in 2..255 {
112        // zero always has count of zero
113        assert_eq!(signed_digit_count(0, base), 0);
114        for num in 1..base {
115            assert_eq!(signed_digit_count(num as i64, base), 1);
116        }
117
118        // test positive digit counts
119        let mut num: i64 = 1;
120        for i in 1.. {
121            num = match num.checked_mul(base as i64) {
122                Some(num) => num,
123                None => break,
124            };
125
126            assert_eq!(signed_digit_count(num - 1, base), i);
127            assert_eq!(signed_digit_count(num, base), i + 1);
128        }
129
130        // test negative digit counts
131        let mut num: i64 = -1;
132        for i in 1.. {
133            num = match num.checked_mul(base as i64) {
134                Some(num) => num,
135                None => break,
136            };
137
138            assert_eq!(signed_digit_count(num + 1, base), -i);
139            assert_eq!(signed_digit_count(num, base), -i - 1);
140        }
141    }
142}
143
144#[test]
145fn can_determine_width_u8() {
146    for num in 0..u8::MAX {
147        assert_eq!(num.width() as usize, num.to_string().len());
148        assert_eq!(num.signed_width() as usize, num.to_string().len());
149    }
150}
151
152#[test]
153fn can_determine_width_u16() {
154    for num in 0..u16::MAX {
155        assert_eq!(num.width() as usize, num.to_string().len());
156        assert_eq!(num.signed_width() as usize, num.to_string().len());
157    }
158}
159
160#[test]
161fn can_determine_width_u32() {
162    assert_eq!(0u32.width(), 1);
163    assert_eq!(10u32.width(), 2);
164    assert_eq!(100u32.width(), 3);
165
166    assert_eq!(0x0u32.width_base(16), 1);
167    assert_eq!(0xFu32.width_base(16), 1);
168    assert_eq!(0xFABu32.width_base(16), 3);
169    assert_eq!(0xCAFEu32.width_base(16), 4);
170}
171
172#[test]
173fn can_determine_width_u64() {
174    assert_eq!(0u64.width(), 1);
175    assert_eq!(10u64.width(), 2);
176    assert_eq!(100u64.width(), 3);
177
178    assert_eq!(0x0u64.width_base(16), 1);
179    assert_eq!(0xFu64.width_base(16), 1);
180    assert_eq!(0xFABu64.width_base(16), 3);
181    assert_eq!(0xDEADBEEFu64.width_base(16), 8);
182}
183
184#[test]
185fn can_determine_width_i8() {
186    for num in i8::MIN..i8::MAX {
187        assert_eq!(num.signed_width() as usize, num.to_string().len());
188        if num >= 0 {
189            assert_eq!(num.width() as usize, num.to_string().len());
190        }
191    }
192}
193
194#[test]
195fn can_determine_width_i16() {
196    for num in i16::MIN..i16::MAX {
197        assert_eq!(num.signed_width() as usize, num.to_string().len());
198        if num >= 0 {
199            assert_eq!(num.width() as usize, num.to_string().len());
200        }
201    }
202}
203
204#[test]
205fn can_determine_width_i32() {
206    assert_eq!(0i32.width(), 1);
207    assert_eq!(10i32.width(), 2);
208    assert_eq!(100i32.width(), 3);
209    assert_eq!((-233i32).width(), 3);
210    assert_eq!((-233i32).signed_width(), 4);
211
212    assert_eq!(0x0i32.width_base(16), 1);
213    assert_eq!(0xFi32.width_base(16), 1);
214    assert_eq!(0xFABi32.width_base(16), 3);
215    assert_eq!(0xCAFEi32.width_base(16), 4);
216    assert_eq!((-0xCAFEi32).signed_width_base(16), 5);
217}
218
219#[test]
220fn can_determine_width_i64() {
221    assert_eq!(0i64.width(), 1);
222    assert_eq!(10i64.width(), 2);
223    assert_eq!(100i64.width(), 3);
224    assert_eq!((-233i64).width(), 3);
225    assert_eq!((-233i64).signed_width(), 4);
226
227    assert_eq!(0x0i64.width_base(16), 1);
228    assert_eq!(0xFi64.width_base(16), 1);
229    assert_eq!(0xFABi64.width_base(16), 3);
230    assert_eq!(0xCAFEi64.width_base(16), 4);
231    assert_eq!((-0xCAFEi64).signed_width_base(16), 5);
232}
233
234impl NumberWidth for u8 {
235    fn signed_digit_count(&self, base: u64) -> i64 {
236        digit_count((*self).into(), base) as i64
237    }
238}
239
240impl NumberWidth for u16 {
241    fn signed_digit_count(&self, base: u64) -> i64 {
242        digit_count((*self).into(), base) as i64
243    }
244}
245
246impl NumberWidth for u32 {
247    fn signed_digit_count(&self, base: u64) -> i64 {
248        digit_count((*self).into(), base) as i64
249    }
250}
251
252impl NumberWidth for u64 {
253    fn signed_digit_count(&self, base: u64) -> i64 {
254        digit_count(*self, base) as i64
255    }
256}
257
258impl NumberWidth for i8 {
259    fn signed_digit_count(&self, base: u64) -> i64 {
260        signed_digit_count((*self).into(), base)
261    }
262}
263
264impl NumberWidth for i16 {
265    fn signed_digit_count(&self, base: u64) -> i64 {
266        signed_digit_count((*self).into(), base)
267    }
268}
269
270impl NumberWidth for i32 {
271    fn signed_digit_count(&self, base: u64) -> i64 {
272        signed_digit_count((*self).into(), base)
273    }
274}
275
276impl NumberWidth for i64 {
277    fn signed_digit_count(&self, base: u64) -> i64 {
278        signed_digit_count(*self, base)
279    }
280}