"Truth be told, it's a multiplication because many compilers transform all divisions by a constant into multiplications; see e.g. http://goo.gl/LhPeH "
uint64_t div10(uint64_t y) {
const uint64_t magic = 0xCCCCCCCCCCCCCCCDULL;
__uint128_t prod = magic * (__uint128_t)y;
return (uint64_t)(prod >> (64 + 3));
}
See http://libdivide.com for how to get this codegen with runtime constants (I am the author).A deep understanding of the algorithm took me a long time, I think a few months. That was mainly due a lack of information describing the technique. The paper that Andrei linked to (Granlund-Montgomery) is dense and contains a significant error, which I was never able to get resolved. Henry Warren's celebrated Hacker's Delight is more accessible, but is also more of a proof-of-correctness than a learning resource. So my intuitive understanding came from my own investigating and playing around, which is what lead me to find an improvement on the algorithm.
Implementing libdivide took me maybe six months of my hobby time. It's not just the core algorithm - there's a lot of auxiliary functions, for example to compute the high half of a 64 bit multiply in SSE. But working at that level is tons of fun.
Incidentally, I wrote up what I hope to be the most accessible (yet still rigorous) description of the algorithm at http://ridiculousfish.com/blog/posts/labor-of-division-episo... . I advise anyone interested in learning more to start there, instead of the Granlund paper.
This case allows for a simpler method, exemplified here (it also works for signed integers, but more care is needed with the shifting): http://goo.gl/D5q9IO
EDIT: On second thought, compilers are also not doing their best job on that example. f1 could be simplified to
mov rax, 4865095698
add edi, 1
imul rdi
shrd rax, rdx, 39
ret
which has a shorter critical path.How is a compiler supposed to track that information? That requires some serious dependent typing.
What it's happening here is something more clever:
let z be: (2^67 + 2)/10.
2^67 isn't divisible by 10 (that's why we add 2). z = 14757395258967641293L
Now, I claim that n/10 = floor(z * n/ 2^67)
z is an integer, but it represents the exact division of (2^67 + 2)/10
we distribute the n: (n * 2^67)/10 + 2 * n/10
we distribute the 2^67 division: (n * 2^67)/(10 * 2^67) + 2 * n / (10 * 2^67)
simplify the terms: n/10 + n/(10 * 2^66) [1]
Now: floor(x/d + c) == floor(x/d) if c < 1/d
we want to take floor of that number[1], but as n/(10 * 2^66) < 1/10
(the maximum value for n is 2^64 - 1), it follows that floor(z * n / 2^67) = n/10.
That's what the code is doing:
0xcccccccccccccccd is 14757395258967641293L
the multiplication of z * n fits in 128 bits, and the mul instruction stores the higher 64 bits of $rax * $rdx into $rdx.We need to divide by 2^67, whichs is shift 3 67 bits to the right. If we take the higher part (rdx) we get the higher 64 bits, and then we shift 3 bits to the right (shr $0x3, %rax) and that's our answer :).
Cool trick.