Skip to content

Commit 636d0df

Browse files
authored
Merge pull request #186 from Alexhuszagh/2n-fast
Add in optimizations for 2^N radix integer writing.
2 parents 8f21471 + 56ccd40 commit 636d0df

File tree

2 files changed

+61
-16
lines changed

2 files changed

+61
-16
lines changed

lexical-write-integer/src/digit_count.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -122,34 +122,46 @@ fn digit_log2<T: UnsignedInteger>(x: T) -> usize {
122122

123123
/// Highly-optimized digit count for base4 values.
124124
///
125-
/// This is very similar to base 2, except we shift right by
126-
/// 1 and adjust by 1. For example, `fast_log2(3) == 1`, so
127-
/// `fast_log2(3) >> 1 == 0`, which then gives us our result.
125+
/// This is very similar to base 2, except we divide by 2
126+
/// and adjust by 1. For example, `fast_log2(3) == 1`, so
127+
/// `fast_log2(3) / 2 == 0`, which then gives us our result.
128+
///
129+
/// This works because `log2(x) / 2 == log4(x)`. Flooring is
130+
/// the correct approach since `log2(15) == 3`, which should be
131+
/// 2 digits (so `3 / 2 + 1`).
128132
#[inline(always)]
129133
fn digit_log4<T: UnsignedInteger>(x: T) -> usize {
130-
// NOTE: This cannot be `fast_log2(fast_log2())`
131-
(fast_log2(x | T::ONE) >> 1) + 1
134+
(fast_log2(x) / 2) + 1
132135
}
133136

134-
/// Specialized digit count for base8 values.
137+
/// Highly-optimized digit count for base8 values.
138+
///
139+
/// This works because `log2(x) / 3 == log8(x)`. Flooring is
140+
/// the correct approach since `log2(63) == 5`, which should be
141+
/// 2 digits (so `5 / 3 + 1`).
135142
#[inline(always)]
136143
fn digit_log8<T: UnsignedInteger>(x: T) -> usize {
137-
// FIXME: Optimize
138-
digit_count!(@naive T, 8, x)
144+
(fast_log2(x) / 3) + 1
139145
}
140146

141-
/// Specialized digit count for base16 values.
147+
/// Highly-optimized digit count for base16 values.
148+
///
149+
/// This works because `log2(x) / 4 == log16(x)`. Flooring is
150+
/// the correct approach since `log2(255) == 7`, which should be
151+
/// 2 digits (so `7 / 4 + 1`).
142152
#[inline(always)]
143153
fn digit_log16<T: UnsignedInteger>(x: T) -> usize {
144-
// FIXME: Optimize
145-
digit_count!(@naive T, 16, x)
154+
(fast_log2(x) / 4) + 1
146155
}
147156

148-
/// Specialized digit count for base32 values.
157+
/// Highly-optimized digit count for base32 values.
158+
///
159+
/// This works because `log2(x) / 5 == log32(x)`. Flooring is
160+
/// the correct approach since `log2(1023) == 9`, which should be
161+
/// 2 digits (so `9 / 5 + 1`).
149162
#[inline(always)]
150163
fn digit_log32<T: UnsignedInteger>(x: T) -> usize {
151-
// FIXME: Optimize
152-
digit_count!(@naive T, 32, x)
164+
(fast_log2(x) / 5) + 1
153165
}
154166

155167
/// Quickly calculate the number of digits in a type.
@@ -183,6 +195,14 @@ pub unsafe trait DigitCount: UnsignedInteger + DecimalCount {
183195
_ => digit_count!(@naive Self, radix, self),
184196
}
185197
}
198+
199+
/// Get the number of digits in a value, always using the slow algorithm.
200+
///
201+
/// This is exposed for testing purposes.
202+
#[inline(always)]
203+
fn slow_digit_count(self, radix: u32) -> usize {
204+
digit_count!(@naive Self, radix, self)
205+
}
186206
}
187207

188208
// Implement digit counts for all types.

lexical-write-integer/tests/digit_count_tests.rs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,8 @@ mod util;
44

55
use lexical_write_integer::decimal::DecimalCount;
66
use lexical_write_integer::digit_count::{self, DigitCount};
7-
#[rustversion::since(1.67)]
87
use proptest::prelude::*;
98

10-
#[rustversion::since(1.67)]
119
use crate::util::default_proptest_config;
1210

1311
#[test]
@@ -177,6 +175,33 @@ default_quickcheck! {
177175
}
178176
}
179177

178+
proptest! {
179+
#![proptest_config(default_proptest_config())]
180+
181+
#[test]
182+
fn decimal_slow_u64_test(x: u64) {
183+
prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10));
184+
}
185+
186+
#[test]
187+
fn basen_slow_u64_test(x: u64, power in 1u32..=5) {
188+
let radix = 2u32.pow(power);
189+
prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix));
190+
}
191+
192+
#[test]
193+
fn decimal_slow_u128_test(x: u128) {
194+
prop_assert_eq!(x.digit_count(10), x.slow_digit_count(10));
195+
}
196+
197+
#[test]
198+
#[cfg(feature = "power-of-two")]
199+
fn basen_slow_u128_test(x: u128, power in 1u32..=5) {
200+
let radix = 2u32.pow(power);
201+
prop_assert_eq!(x.digit_count(radix), x.slow_digit_count(radix));
202+
}
203+
}
204+
180205
#[rustversion::since(1.67)]
181206
macro_rules! ilog {
182207
($x:ident, $radix:expr) => {{

0 commit comments

Comments
 (0)