如何在 avr-gcc 中按常量除法?

How does division by constant work in avr-gcc?

在 avr 汇编中,我想将一个数除以一个常数。 我检查了如何查看 avr-gcc 是如何做到的。 所以在 c 文件中我有:

#include <stdint.h>

uint8_t divide_by_6(uint8_t x) {
    return x / 6;
}

当我 运行 avr-gcc -O3 -mmcu=atmega16 -D__AVR_ATmega16__ -S main.c 它给了我:

divide_by_6:
    ldi r25,lo8(-85)
    mul r24,r25
    mov r24,r1
    clr r1
    lsr r24
    lsr r24
    ret

但是我不明白这个程序集在做什么。 这段汇编代码是如何进行除法的?

-85是0xFFFFFFFFFFFFFFAB,所以lo8(-85)是0xAB,也就是171.

代码将参数乘以 171,然后返回乘积的最高有效字节,右移 2(即除以 4)。

所以它实际上返回了 x * 171 / (256 * 4) == x * 171 / 1024,大约是 == x * 1 / 6 == x / 6。

Google:倒数乘法的琼斯

我们都知道或者应该从小学就知道除以n等于乘以1/n。我们还学习了如何使用小数位。和其他操作,因此乘以 1.234 与乘以 1234 然后除以 1000 或之后除以 1000 相同,只知道您的结果是 1000 到大。就像用便士而不是美元来思考一样,12.34 美元是 1234 便士。 6小时为360分钟等

与小学相比,二进制的长除法是微不足道的。对于您移动的每个数字,除数可以恰好为零或恰好一次适合下拉分子。基本上,对于 1/6,你最终得到 0.001010101010...二进制格式。

所以如果我想要 1234/6,我可以做 1234*0x2AAA = 0xCDA774。 integer/fixed 点答案是 205 也就是 0xCD 所以 0x2AAA 是有意义的 (1/6)65536 所以 (X((1/6)*65536))/65536 = X/6 或 (X*0x2AAA)>>16 约为 X/6。

现在四舍五入呢? 1234/6 实际上是 205 和 2/3,所以如果你想四舍五入怎么办。好吧,四舍五入意味着你取截止点后的数字,如果它等于或超过一半,你四舍五入对吗?那么 10101010 截止点后的数字是 1,1/2 等于或超过一半那么为什么不使用 0x2AAB?

我们还知道 6 = 2*3。这两个数字很容易移位,所以你可以做 (N/3)/2 或 (N/2)/3。 1/3 是 0.01010101...二进制,其中 1/6 是 0.001010101...

所以这一切都应该导致一个粗略的想法,你可以如何乘以除法,以及一个粗略的想法,他们相乘的 0xAB 可能来自哪里。但其他基础数学身份也可能在那里。

请注意,您不必在计算器上进行手二进制除法 0x10000/6 = 0x2AAA。 X/6 = (X*0X10000)/(6*0X10000) = (X/0X10000)(0X10000/6) = (X(0X10000/6))/ 0X10000。然后你在尝试使用这个固定点时只需要考虑 accuracy/rounding 。有时 16 位不够,您需要 24 位或 32 位,或者谁知道……取决于除数。