Ideal divisors: when a division compiles down to just a multiplication
lemire.me
lemire.me
The idea is to exploit deeper structures in p-adic number theory to develop useful and novel algorithms with unsigned integer arithmetic, e.g. fast integer exponentiation. Feedback welcome!
(Warning: math heavy)
(As a sidenote on the motivation, I do wonder how common/useful it is to compute x^y mod (2^64) for random 64-bit integers x and y (so they'd be close to 2^63 on average)... from this perspective, the promised future post about computing the k-th root of a perfect k-th power integer sounds very interesting!)
As far as the statement that
log_2(5) = 6713115954038056572,
really that number is only an approximation; it is the last 64 bits of the “true” log_2(5), which has infinitely many bits. That is, the above equation in binary would be log_2(5) = 101110100101001110001110110010010000000011100100010011001111100.
In the true 2-adic world, log_2(5) = ...101110100101001110001110110010010000000011100100010011001111100,
where there are infinitely many leading bits.(Classical positive integers embed into the 2-adic world as numbers with all but finitely many bits set to 0, and similarly classical negative integers have all but finitely many bits set to 1.)
It's 0x1FFF...F / 7 = 0x249...249249249.
Just about the oddest thing I've seen; I guess, if I were the author, it'd be my "1 in 10000" day?
0xFFF..F 111111111111111111111111
0x7 000000000000000000000111
0x249..9 001001001001001001001001“It doesn't seem like there could be much to say about that. The A structure is 7 bytes long so the subtraction implicitly divides by 7. That's about it.”
35-7 = 28, 1
28-7 = 21, 2
21-7 = 14, 3
14-7 = 07, 4
07-7 = 00, 5
thus we can see that in essence, division is repeated subtraction in some contexts, especially computationally.
An easy way to count the amount of X that fits into Y is to simply count the amount of times you can subtract X from Y before Y is a negative value
It's a fact about how the C language does pointer arithmetic.
For instance, suppose you have an array of many 64-bit integers, and a pointer to one of the elements of the array:
int64_t arr[100];
int64_t *p;
p = &arr[50];
Now, the value of p is a certain address in memory. As each element of the array takes 8 bytes (64 bits = 8 bytes), the address of the next element of the array is 8 bytes past the location of p. In some languages (including assembly), you have to refer to this address by manually adding 8 to the address stored in p. In C, to refer to the next element of the array, you write "p + 1", and the compiler translates that into adding 8 bytes.Conversely, if you give the new pointer a name:
int64_t *q;
q = p + 1;
then you expect (q - p) to be 1, not 8 (even though the value stored in q is 8 more than the value stored in p).That is, when subtracting pointers (of the same type), the result implicitly divides by the size of the type. In the code example in the blog post, as the size of "struct A" is 7 bytes, subtracting two pointers to A will subtract the memory addresses and divide by 7.
If that wasn't clear, see this sentence on Wikipedia: https://en.wikipedia.org/w/index.php?title=Pointer_(computer... or the answers to this question: https://stackoverflow.com/questions/3238482/pointer-subtract... or play with it yourself here: https://godbolt.org/z/M6PEWYeYx
Not a particularly good name, as “ideal” already has an established meaning in algebra: https://en.m.wikipedia.org/wiki/Ideal_(ring_theory)
Oh, you're saying "square-free" has an established meaning in mathematics? https://en.wikipedia.org/wiki/Square-free_integer
Well, you see, "square" already had an established meaning in English before number theory existed. Curious, why’d you pick that specialization of this word, out of all that are available? :)
Wasn’t your straw man example ‘square-free’, not ‘square’...?
Unfortunately it is you who seem to be lacking the requisite knowledge to evaluate the merit of my argument…
Suffice to say, the various divisibility-related properties of integers are very closely related to ring theory. In the ring of integers (called Z), numbers of the form n*i for all i form an ideal (called nZ). As you can see these are the numbers divisble by a given n.
Thus in the context of integers and divisibility, the term “ideal” is not some niche jargon, but in fact a crucial concept that anyone with a rudimentary understanding of number theory (which is very closely related to abstract algebra) have encountered.
Of course not all of us software engineers have encountered these concepts as part of our formal education, but I would expect some familiarity from the alumni of any semi-decent software engineering college or university.
Of course, the multiply is fully pipelined while the division is only partially, but even that leaves only a 10-fold difference.
Division is much faster these days than it used to be. It's still worth it to have compiler optimizations for it, but it's not important to avoid it anymore.
> only a 10-fold difference.
A ten fold difference is literally one order of magnitude. Which is what the post you're replying to factually stated.
Even still people (unfortunately) use "orders of magnitude" like "exponential growth", signifying something like "wow, a lot, but no idea how much".
I hate it, too, when people call a quadratic curve "exponential".
Interestingly, the ARM11 in original RPi and RPi0 doesn't have hardware integer division (despite being 10x faster than typical M4 and typically having 10000x RAM), but it does have floating point division.
https://ridiculousfish.com/blog/posts/labor-of-division-epis...
https://ridiculousfish.com/blog/posts/labor-of-division-epis...
And it's faster if and only if you're repeating division by a single number since you're still doing the equivalent of divide to get the invest (but repeated division is actually quite common so this is useful).
This is done at compile-time by the compiler. We're talking about division by a constant, not by a variable. So it's always faster.
However there are other ways the trick can be used. If the compiler (or the programmer) determines that the program will be doing repeated division by a single quantity (say in a loop), you substitution a calculation of the multiplicative inverse of the quantity first and then substitute multiplication by that inverse for the repeated for the divisions. So you can extend things beyond just division by a constant.
`a / b` is completely equivalent to `a * (1 / b)` when both b and 1/b are perfectly representable in floating point without rounding, which is the case for 2 and 0.5
How about other languages (and compiler systems)?
Are there binary tricks worth knowing about to quickly calculate reciprocals?
However, when we enable `-ffast-math`, we permit the compiler to do a bit more aggressive optimization in this domain (trading off precision), and we see multiplication (mulss) is emitted: https://godbolt.org/z/KEb1nc43z
Yes! You can calculate an approximate reciprocal with a single subtraction and some type-casting or bit manipulation.
The accuracy isn’t great (relative error of up to ~5%), but it might be good enough for many applications. The method is famous for being used to normalize 3d vectors in Quake https://en.wikipedia.org/wiki/Fast_inverse_square_root
This same method for calculating 1/x^2 can also be used to calculate 1/x by using a different magic constant and skipping the shift-right by one bit.
Edit: Additionally the serial vs parallel processing paradigm shift is a main advantage of the switch too! Queued systems seem to have severe scheduling limitations, which the real-world does not always inherently have.
What I get in that case is 23 cm for the darkish blue left sidebar and 36 cm for the light blue right area that contains the article. Within that right area, the article is in 19 cm white rectangle with just under 2 cm margins.
So, starting from the left here is what I see:
15 cm empty dark blue background,
6 cm of sidebar text on dark blue background,
2 more cm of empty dark blue background,
2 cm of empty light blue background,
A tad under 2 cm of empty white background,
About 14.5 cm of text on white background,
A tad under 2 cm of empty white background,
15.5 cm of empty light blue background.
It really is quite ugly. It looks like something I would do because I completely suck at CSS.
1 The exception being browser windows to run sites like Netflix where I'm really using it as a video interface.