据我所知,大多数编译器将通过相乘然后向右移位来进行快速除法。例如,如果您勾选this SO thread,它表示当您要求Microsoft编译器除以10时,它将被除数乘以0x1999999A(即2 ^ 32/10),然后将结果除以2 ^ 32(使用向右移动32个)。
((编者注:直到这个链接的答案都是错误的。编译器不会那样做,因为并非所有输入都正确。编译器会进行乘法和移位,但是具有确定魔术常数和移位计数的更复杂方式:[C0 ])
到目前为止一切顺利。
但是,一旦我使用GCC在ARM上测试了相同的10分频,编译器就会做些不同的事情。首先将红利乘以0x66666667(2 ^ 34/10),然后将结果除以2 ^ 34。到目前为止,它与Microsoft相同,只是使用了更高的乘数。但是,此后,它从结果中减去(股息/ 2 ^ 31)。
我的问题:为什么在ARM版本上有额外的减法?您能给我一个数字示例,如果不减去,结果将是错误的?
如果要检查生成的代码,请参见下面的内容(附我的评论):
Why does GCC use multiplication by a strange number in implementing integer division?
但是之后,它从结果中减去(股息/ 2 ^ 31)。
实际上,当右移负整数是算术右移(并且 ldr r2, [r7, #4] @--this loads the dividend from memory into r2
movw r3, #:lower16:1717986919 @--moves the lower 16 bits of the constant
movt r3, #:upper16:1717986919 @--moves the upper 16 bits of the constant
smull r1, r3, r3, r2 @--multiply long, put lower 32 bits in r1, higher 32 in r3
asr r1, r3, #2 @--r3>>2, then store in r1 (effectively >>34, since r3 was higher 32 bits of multiplication)
asr r3, r2, #31 @--dividend>>31, then store in r3
rsb r3, r3, r1 @--r1 - r3, store in r3
str r3, [r7, #0] @--this stores the result in memory (from r3)
为32位宽时),它减去dividend >> 31
,对于负-1
为dividend
,对于非负除数为0。 。
int
因此,对于0x6666667 = (2^34 + 6)/10
,我们用x < 0
编写x = 10*k + r
,
-10 < r <= 0
现在,负整数的算术右移产生0x66666667 * (10*k+r) = (2^34+6)*k + (2^34 + 6)*r/10 = 2^34*k + 6*k + (2^34+6)*r/10
的下限,所以
v / 2^n
结果
(0x66666667 * x) >> 34
所以我们需要看一下
k + floor((6*k + (2^34+6)*r/10) / 2^34)
正确的不等式很容易,-2^34 < 6*k + (2^34+6)*r/10 < 0
和k
都是非正数,而且都不都是0。
对于左不等式,需要更多分析。
r
所以r >= -9
的绝对值最多为(2^34+6)*r/10
。
2^34+6 - (2^34+6)/10
所以|k| <= 2^31/10,
。
还有待验证
|6*k| <= 3*2^31/5
是,是。
对于6 + 3*2^31/5 < (2^34+6)/10
1288490194 < 1717986919
的负值,[x SAR 31
是0xffffffff
(-1),对于正值,x
是0x00000000
。
因此,如果股息为负,则rsb
从结果中减去-1(与加1相同)。>
假设您的股息为-60
。仅需乘和移位就可以得到结果-7
,因此它减去-1即可得到-6
的预期结果。