n/d = n * (2^N/d) / (2^N)
What is "N"? They didn't seem to explain that, although they say "Yet for N large enough", and "drop the least significant N bits". Is it just a very large integer? Or maybe the maximum integer for a certain number of bytes?EDIT: I'm reading this Wikipedia article: https://en.wikipedia.org/wiki/Division_algorithm#Division_by...
For a 32-bit unsigned integer, you would use N = 33 when dividing by 3? And N = 35 when dividing by 10? I'm struggling to see how this works.
EDIT 2: I think this post helped me understand it: https://forums.parallax.com/discussion/114807/fast-faster-fa...
"The·basic idea is to approximate the ratio (1/constant) by another rational number (numerator/denominator) with a power of two as the denominator."
Their example:
(n/10) = (n*205) >> 11
Maps to the original example: n/10 = n * (2^11 / 10) / (2^11)
2^11 / 10 = 2048 / 10 = 204.8, which rounds to 205.So N would be 11 in this example, but I guess it could be any value.
I'm one of the co-authors, and I believe that in our case, N is always equal to the maximum bitwidth of the numerator. So for a 32-bit integer, N=32. I'll see if Daniel can add some clarification to the blog post.
So nkurz is correct that, in the paper, N is always equal to the maximum bitwidth of the numerator. I was not careful enough to make sure that the notation of the paper matches the notation of the blog post. So the N from the blog post is the F from the paper. I am sorry about this.
I added the following footnote.
"What is N? If both the numerator n and the divisor d are 32-bit unsigned integers, then you can pick N=64. This is not the smallest possible value. The smallest possible value is given by Algorithm 2 in our paper and it involves a bit of mathematics (note: the notation in my blog post differs from the paper, N becomes F)."
To make it super simple, if you have 32-bit values, then pick N (from the blog post) to be 64 and you are all set. You can do better, but this requires knowing what the divisor is.
Just curious, did you try making x an 8-bit integer to see if the compiler chose a different multiplier?
Here's a repo with 2 tests, counting bits, and calculating sine/cosine: https://github.com/Const-me/LookupTables
Neither code is particularly cheap but still faster than RAM.
clang 7
mov ecx, edi
mov eax, 3435973837
imul rax, rcx
shr rax, 35
gcc 8.2 mov eax, edi
mov edx, -858993459
mul edx
mov eax, edx
shr eax, 3
msvc 19 mov eax, -858993459 ; cccccccdH
mul edx
shr edx, 3
mov eax, edx
icc 19 mov eax, 1717986919
imul edx
sar edx, 2
mov eax, edx
The same, but when input unsigned char:clang 7
movzx eax, dil
imul eax, eax, 205
shr eax, 11
gcc 8.2 movzx edi, dil
lea eax, [rdi+rdi*4]
lea eax, [rdi+rax*8]
lea eax, [rax+rax*4]
shr ax, 11
movzx eax, al
msvc 19 movzx ecx, al
mov eax, -858993459 ; cccccccdH
mul ecx
shr edx, 3
mov eax, edx
icc 19 mov eax, 1717986919
imul edi
sar edx, 2
mov eax, edx
clang and gcc with -O3, msvc -O2Montgomery reduction requires an augmented number system with conversions to and from. The article's approach works on numbers directly, but if I understand it correctly the multiplications are twice the width.
Are these two methods algebraically related? Would it be possible to get fastmod working without the double width?
[1]: https://en.wikipedia.org/wiki/Montgomery_modular_multiplicat...
The idea is not novel and goes back to at least 1973 (Jacobsohn). However, engineering matters because computer registers have finite number of bits, and multiplications can overflow. I believe that, historically, this was first introduced into a major compiler (the GNU GCC compiler) by Granlund and Montgomery (1994). While GNU GCC and the Go compiler still rely on the approach developed by Granlund and Montgomery, other compilers like LLVM’s clang use a slightly improved version described by Warren in his book Hacker’s Delight.