lexical_write_integer/algorithm.rs
1//! Radix-generic, optimized, integer-to-string conversion routines.
2//!
3//! These routines are highly optimized: they unroll 4 loops at a time,
4//! using pre-computed base^2 tables.
5//!
6//! See [Algorithm.md](/docs/Algorithm.md) for a more detailed description of
7//! the algorithm choice here. See [Benchmarks.md](/docs/Benchmarks.md) for
8//! recent benchmark data.
9
10#![cfg(not(feature = "compact"))]
11#![doc(hidden)]
12
13use lexical_util::assert::debug_assert_radix;
14use lexical_util::digit::digit_to_char;
15use lexical_util::div128::u128_divrem;
16use lexical_util::format::{radix_from_flags, NumberFormat};
17use lexical_util::num::{AsCast, UnsignedInteger};
18use lexical_util::step::u64_step;
19
20/// Write 2 digits to buffer.
21///
22/// # Safety
23///
24/// Safe if `bytes` is large enough to hold 2 characters, `index >= 2`,
25/// and if the 2 * remainder, or `r`, has it so `r + 1 < table.len()`.
26macro_rules! write_digits {
27 ($bytes:ident, $index:ident, $table:ident, $r:ident) => {{
28 debug_assert!($index >= 2);
29 debug_assert!($bytes.len() >= 2);
30 debug_assert!($r + 1 < $table.len());
31 $index -= 1;
32 unsafe { index_unchecked_mut!($bytes[$index] = $table[$r + 1]) };
33 $index -= 1;
34 unsafe { index_unchecked_mut!($bytes[$index] = $table[$r]) };
35 }};
36}
37
38/// Write 1 digit to buffer.
39///
40/// # Safety
41///
42/// Safe if `bytes` is large enough to hold 1 characters, and `r < 36`.
43macro_rules! write_digit {
44 ($bytes:ident, $index:ident, $r:ident) => {{
45 debug_assert!($index >= 1);
46 debug_assert!($bytes.len() >= 1);
47 debug_assert!($r < 36);
48 $index -= 1;
49 unsafe { index_unchecked_mut!($bytes[$index]) = digit_to_char($r) };
50 }};
51}
52
53// NOTE: Don't use too many generics:
54// We don't need generics for most of the internal algorithms,
55// and doing so kills performance. Why? I don't know, but assuming
56// it messed with the compiler's code generation.
57
58/// Write integral digits to buffer.
59///
60/// This algorithm first writes 4, then 2 digits at a time, finally
61/// the last 1 or 2 digits, using power reduction to speed up the
62/// algorithm a lot.
63///
64/// # Safety
65///
66/// This is safe as long as the buffer is large enough to hold `T::MAX`
67/// digits in radix `N`.
68unsafe fn write_digits<T: UnsignedInteger>(
69 mut value: T,
70 radix: u32,
71 table: &[u8],
72 buffer: &mut [u8],
73 mut index: usize,
74) -> usize {
75 debug_assert_radix(radix);
76
77 // Pre-compute our powers of radix.
78 let radix = T::from_u32(radix);
79 let radix2 = radix * radix;
80 let radix4 = radix2 * radix2;
81
82 // SAFETY: All of these are safe for the buffer writes as long as
83 // the buffer is large enough to hold `T::MAX` digits in radix `N`.
84
85 // Decode 4 digits at a time.
86 while value >= radix4 {
87 let r = value % radix4;
88 value /= radix4;
89 let r1 = usize::as_cast(T::TWO * (r / radix2));
90 let r2 = usize::as_cast(T::TWO * (r % radix2));
91
92 // SAFETY: This is always safe, since the table is 2*radix^2, and
93 // r1 and r2 must be in the range [0, 2*radix^2-1), since the maximum
94 // value of r is `radix4-1`, which must have a div and r
95 // in the range [0, radix^2-1).
96 write_digits!(buffer, index, table, r2);
97 write_digits!(buffer, index, table, r1);
98 }
99
100 // Decode 2 digits at a time.
101 while value >= radix2 {
102 let r = usize::as_cast(T::TWO * (value % radix2));
103 value /= radix2;
104
105 // SAFETY: this is always safe, since the table is 2*radix^2, and
106 // r must be in the range [0, 2*radix^2-1).
107 write_digits!(buffer, index, table, r);
108 }
109
110 // Decode last 2 digits.
111 if value < radix {
112 // SAFETY: this is always safe, since value < radix, so it must be < 36.
113 let r = u32::as_cast(value);
114 write_digit!(buffer, index, r);
115 } else {
116 let r = usize::as_cast(T::TWO * value);
117 // SAFETY: this is always safe, since the table is 2*radix^2, and
118 // the value must <= radix^2, so rem must be in the range
119 // [0, 2*radix^2-1).
120 write_digits!(buffer, index, table, r);
121 }
122
123 index
124}
125
126/// Specialized digits writer for u128, since it writes at least step digits.
127///
128/// # Safety
129///
130/// This is safe as long as the buffer is large enough to hold `T::MAX`
131/// digits in radix `N`.
132unsafe fn write_step_digits<T: UnsignedInteger>(
133 value: T,
134 radix: u32,
135 table: &[u8],
136 buffer: &mut [u8],
137 index: usize,
138 step: usize,
139) -> usize {
140 debug_assert_radix(radix);
141
142 let start = index;
143 // SAFETY: safe as long as the call to write_step_digits is safe.
144 let index = unsafe { write_digits(value, radix, table, buffer, index) };
145 // Write the remaining 0 bytes.
146 // SAFETY: this is always safe since `end < index && index < start`.
147 let end = start.saturating_sub(step);
148 unsafe {
149 let zeros = &mut index_unchecked_mut!(buffer[end..index]);
150 slice_fill_unchecked!(zeros, b'0');
151 }
152
153 end
154}
155
156/// Optimized implementation for radix-N numbers.
157///
158/// # Safety
159///
160/// Safe as long as the buffer is large enough to hold as many digits
161/// that can be in the largest value of `T`, in radix `N`.
162#[inline]
163pub unsafe fn algorithm<T>(value: T, radix: u32, table: &[u8], buffer: &mut [u8]) -> usize
164where
165 T: UnsignedInteger,
166{
167 // This is so that radix^4 does not overflow, since 36^4 overflows a u16.
168 debug_assert!(T::BITS >= 32, "Must have at least 32 bits in the input.");
169 debug_assert_radix(radix);
170
171 // SAFETY: Both forms of unchecked indexing cannot overflow.
172 // The table always has 2*radix^2 elements, so it must be a legal index.
173 // The buffer is ensured to have at least `FORMATTED_SIZE` or
174 // `FORMATTED_SIZE_DECIMAL` characters, which is the maximum number of
175 // digits an integer of that size may write.
176 unsafe { write_digits(value, radix, table, buffer, buffer.len()) }
177}
178
179/// Optimized implementation for radix-N 128-bit numbers.
180///
181/// # Safety
182///
183/// Safe as long as the buffer is large enough to hold as many digits
184/// that can be in the largest value of `T`, in radix `N`.
185#[inline]
186pub unsafe fn algorithm_u128<const FORMAT: u128, const MASK: u128, const SHIFT: i32>(
187 value: u128,
188 table: &[u8],
189 buffer: &mut [u8],
190) -> usize {
191 // NOTE:
192 // Use the const version of radix for u64_step and u128_divrem
193 // to ensure they're evaluated at compile time.
194 assert!(NumberFormat::<{ FORMAT }> {}.is_valid());
195
196 // Quick approximations to make the algorithm **a lot** faster.
197 // If the value can be represented in a 64-bit integer, we can
198 // do this as a native integer.
199 let radix = radix_from_flags(FORMAT, MASK, SHIFT);
200 if value <= u64::MAX as _ {
201 // SAFETY: safe if the buffer is large enough to hold the significant digits.
202 return unsafe { algorithm(value as u64, radix, table, buffer) };
203 }
204
205 // SAFETY: Both forms of unchecked indexing cannot overflow.
206 // The table always has 2*radix^2 elements, so it must be a legal index.
207 // The buffer is ensured to have at least `FORMATTED_SIZE` or
208 // `FORMATTED_SIZE_DECIMAL` characters, which is the maximum number of
209 // digits an integer of that size may write.
210
211 // We use a fast 128-bit division algorithm, described in depth
212 // in lexical_util/div128.
213
214 // Decode 4-digits at a time.
215 // To deal with internal 0 values or values with internal 0 digits set,
216 // we store the starting index, and if not all digits are written,
217 // we just skip down `digits` digits for the next value.
218 let step = u64_step(radix_from_flags(FORMAT, MASK, SHIFT));
219 let (value, low) = u128_divrem(value, radix_from_flags(FORMAT, MASK, SHIFT));
220 let mut index = buffer.len();
221 index = unsafe { write_step_digits(low, radix, table, buffer, index, step) };
222 if value <= u64::MAX as _ {
223 return unsafe { write_digits(value as u64, radix, table, buffer, index) };
224 }
225
226 // Value has to be greater than 1.8e38
227 let (value, mid) = u128_divrem(value, radix_from_flags(FORMAT, MASK, SHIFT));
228 index = unsafe { write_step_digits(mid, radix, table, buffer, index, step) };
229 if index != 0 {
230 index = unsafe { write_digits(value as u64, radix, table, buffer, index) };
231 }
232
233 index
234}