GCC / ARM的快速划分

问题描述 投票:10回答:2

据我所知,大多数编译器将通过相乘然后向右移位来进行快速除法。例如,如果您勾选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?
gcc assembly arm integer-division
2个回答
10
投票

但是之后,它从结果中减去(股息/ 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,对于负-1dividend,对于非负除数为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

是,是。


5
投票

对于6 + 3*2^31/5 < (2^34+6)/10 1288490194 < 1717986919 的负值,[x SAR 310xffffffff(-1),对于正值,x0x00000000

因此,如果股息为负,则rsb从结果中减去-1(与加1相同)。>

假设您的股息为-60。仅需乘和移位就可以得到结果-7,因此它减去-1即可得到-6的预期结果。

© www.soinside.com 2019 - 2024. All rights reserved.