While using Godbolt Compiler, I was playing around with various functions when I checked what signed division looks like using the following code:
int div10(int num) {
return num / 10;
}
Where the num/10
section generated the following assembly (RBP-4 is storing num at this point):
mov eax, DWORD PTR [rbp-4]
movsx rdx, eax
imul rdx, rdx, 1717986919
shr rdx, 32
sar edx, 2
sar eax, 31
mov ecx, eax
mov eax, edx
sub eax, ecx
I am aware that this is a typical example of division by reciprocal multiplication, using a fixed-point representation of 1/10 and bit shifts. What I do not understand is the choice of the constant 1717986919
, or 0x6666_6667
in place of the typically cited (at least in Agner Fog's manual, https://www.agner.org/optimize/optimizing_assembly.pdf on page 138) constant 0xCCCC_CCCD
. Note that the former is just the latter shifted to the left by one.
I can only assume that this has something to do with it being signed multiplication instead of unsigned multiplication, as the code generated when the type is uint32_t
is as follows:
mov eax, DWORD PTR [rbp-4]
mov edx, eax
mov eax, 3435973837
imul rax, rdx
shr rax, 32
shr eax, 3
Where 343597387
is the expected value 0xCCCC_CCCD
.
I also do not fully understand the shifting and subtraction that occurs during the signed division example. The first two signed right-shifts are the standard discarding of the decimal (fractional? I do not know the proper terminology for this) bits. However, I do not know why eax
, which as a reminder holds the original dividend, has its sign "extracted" (the sign bit is made to fill all the bits with an arithmetic shift) and then subtracted from the quotient. (Register shuffling occurs to force the desired output into eax
since the return value must go in that register before the ret
instruction executes.)
Sidenote: Unless Agner Fog has published new manuals, I could not find any mention of signed division in his book.