Computing the number of digits of an integer even faster
lemire.me
lemire.me
static uint64_t table[] = {
0x100000000, 0x1FFFFFFF6, 0x1FFFFFFF6,
0x1FFFFFFF6, 0x2FFFFFF9C, 0x2FFFFFF9C,
0x2FFFFFF9C, 0x3FFFFFC18, 0x3FFFFFC18,
0x3FFFFFC18, 0x4FFFFD8F0, 0x4FFFFD8F0,
0x4FFFFD8F0, 0x4FFFFD8F0, 0x5FFFE7960,
0x5FFFE7960, 0x5FFFE7960, 0x6FFF0BDC0,
0x6FFF0BDC0, 0x6FFF0BDC0, 0x7FF676980,
0x7FF676980, 0x7FF676980, 0x7FF676980,
0x8FA0A1F00, 0x8FA0A1F00, 0x8FA0A1F00,
0x9C4653600, 0x9C4653600, 0x9C4653600,
0xA00000000, 0xA00000000,
};// this increments the upper 32 bits (log10 T - 1) when >= T is added
#define K(T) (((sizeof(#T)-1)<<32) - T)
(T is 10,100, etc.)
The naive implementation of an integer-to-string conversion involves writing the number backwards, starting from the least significant digit, and then reversing the string at the end.
In a 2017 talk titled "Fastware"[1], Andrei Alexandrescu showed that it's faster to start by counting the number of digits you're going to print, and then you can print by working backwards from the least significant digit. It comes out in the correct order without any need to reverse at the end.
His implementation of the digit count was itself interesting, since it does 4 comparisons per loop and then a divide by 10000, instead of the naive single comparison and divide by 10.
uint32_t digits10(uint64_t v) {
uint32_t result = 1;
for (;;) {
if (v < 10) return result;
if (v < 100) return result + 1;
if (v < 1000) return result + 2;
if (v < 10000) return result + 3;
v /= 10000U;
result += 4;
}
}
IIRC, his insight was that smaller numbers tend to occur more often in the real world, so most calls to this function will not end up doing any divisions.Andrei Alexandrescu
"ok, fine..."
I'm only familiar with using "unary" to mean single-parameter.
And the etymology is basically a coincidence because "mono" got used in different places, isn't it?
Actually, all monads are monadic functions/operators on their (usually type) parameter. Eg, 'list' is a monadic operator from element types to types of lists of those elements.
It's completely different (AFAICT), and mostly unrelated, but it's not completely unrelated.
For integer tricks like this, you'll want to look at number theory. Modular equivalence is particularly useful, but lots of things in that space are useful. However, there are a lot of other tricks that rely on geometric sequences, or geometry themselves, and other higher maths -- a lot of advances like this are simply connecting random theorum a to situation b, if you can envision the situation in the correct space.
Digits = LEN( STR$( SomeNumber ))
(run and hide in shame)For 64, a variable shift may work. Powers of 10 have a lot of trailing 0 bits.
It's effectively:
size_t logx = int_log2(x);
uint32_t carry = ((uint64_t)x + tablo[logx]) >> 32;
return tabhi[logx] + carry;
Assembly can do (from a equivalent start): mov rdx rax # save x
xor eax eax
add rdx [8*rcx+tablo] # set carry (would cmp be better?)
adc al [1*rcx+tabhi] # result never exceeds 64/log(10) < 20Isn't this effectively a table-optimized logBase(x) rounded-up to the ceiling, which can be reduced to efficient log2(x)/log2(Base)? If you know the base, then you can combine many ops into table-lookups.
As an exercise to the reader, I suggest creating efficient code constrained to 32-bit or 16-bit unsigned.
PS: I was going to say "look at Hacker's Delight." I can definitely read. LOL.
The results of such a benchmark would probably heavily depend on whether the lookup table is allowed to be in the CPU caches.
Are you saying you've got a better solution than the blog post that does a direct table lookup? Can you share it?
Do you have a better solution?
x |= x >> 1;
x |= x >> 2;
x |= x >> 4;
x |= x >> 8;
x |= x >> 16;
return popcount(x-1);
but there isn't really much of an advantage to it. lzcnt/bsr are better.Anyway I'm glad to know there's a cool lookup table trick out there, I never really get to use anything like that but it makes for a nice read.
I don't know what that means. Can you illustrate (preferably with a code example)?
is part of the solution. the other part is getting to the most significant digit.