lexical_util/digit.rs
1//! Utilities to process digits.
2//!
3//! This both contains routines to convert to and from digits,
4//! as well as iterate over digits while skipping digit separators.
5
6// CONST FNS
7// ---------
8
9// These are optimized functions for when the radix is known at compile-time,
10// which is **most** of our cases. There are cases where for code generation,
11// using a runtime algorithm is preferable.
12
13/// Unchecked, highly optimized algorithm to convert a char to a digit.
14/// This only works if the input character is known to be a valid digit.
15#[inline(always)]
16pub const fn char_to_valid_digit_const(c: u8, radix: u32) -> u32 {
17 if radix <= 10 {
18 // Optimize for small radixes.
19 (c.wrapping_sub(b'0')) as u32
20 } else {
21 // Fallback, still decently fast.
22 let digit = match c {
23 b'0'..=b'9' => c - b'0',
24 b'A'..=b'Z' => c - b'A' + 10,
25 b'a'..=b'z' => c - b'a' + 10,
26 _ => 0xFF,
27 };
28 digit as u32
29 }
30}
31
32/// Convert a character to a digit with a radix known at compile time.
33///
34/// This optimizes for cases where radix is <= 10, and uses a decent,
35/// match-based fallback algorithm.
36#[inline(always)]
37pub const fn char_to_digit_const(c: u8, radix: u32) -> Option<u32> {
38 let digit = char_to_valid_digit_const(c, radix);
39 if digit < radix {
40 Some(digit)
41 } else {
42 None
43 }
44}
45
46/// Determine if a character is a digit with a radix known at compile time.
47#[inline(always)]
48pub const fn char_is_digit_const(c: u8, radix: u32) -> bool {
49 char_to_digit_const(c, radix).is_some()
50}
51
52/// Convert a digit to a character with a radix known at compile time.
53///
54/// This optimizes for cases where radix is <= 10, and uses a decent,
55/// match-based fallback algorithm.
56#[inline(always)]
57#[cfg(any(feature = "write", feature = "floats"))]
58pub const fn digit_to_char_const(digit: u32, radix: u32) -> u8 {
59 if radix <= 10 || digit < 10 {
60 // Can short-circuit if we know the radix is small at compile time.
61 digit as u8 + b'0'
62 } else {
63 digit as u8 + b'A' - 10
64 }
65}
66
67// NON-CONST
68// ---------
69
70// These are less optimized functions for when the radix is not known at
71// compile-time, which is a few (but important) cases. These generally have
72// improved compiler optimization passes when generics are used more sparingly.
73
74/// Convert a character to a digit.
75#[inline(always)]
76#[cfg(feature = "parse")]
77pub const fn char_to_digit(c: u8, radix: u32) -> Option<u32> {
78 // Fallback, still decently fast.
79 let digit = match c {
80 b'0'..=b'9' => c - b'0',
81 b'A'..=b'Z' => c - b'A' + 10,
82 b'a'..=b'z' => c - b'a' + 10,
83 _ => 0xFF,
84 } as u32;
85 if digit < radix {
86 Some(digit)
87 } else {
88 None
89 }
90}
91
92/// Determine if a character is a digit.
93#[inline(always)]
94#[cfg(feature = "parse")]
95pub const fn char_is_digit(c: u8, radix: u32) -> bool {
96 char_to_digit(c, radix).is_some()
97}
98
99/// Convert a digit to a character. This uses a pre-computed table to avoid
100/// branching.
101///
102/// # Panics
103///
104/// Panics if `digit >= 36`.
105#[inline(always)]
106#[cfg(feature = "write")]
107pub fn digit_to_char(digit: u32) -> u8 {
108 const TABLE: [u8; 36] = [
109 b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'A', b'B', b'C', b'D', b'E',
110 b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T',
111 b'U', b'V', b'W', b'X', b'Y', b'Z',
112 ];
113 debug_assert!(digit < 36, "digit_to_char() invalid character.");
114 TABLE[digit as usize]
115}