0

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

#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

但我不明白这个大会在做什么。这个汇编代码如何执行除法?

4

2 回答 2

4

-85 是 0xFFFFFFFFFFFFFFAB,lo8(-85)0xAB 也是如此,即 171。

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

所以它有效地返回 x * 171 / (256 * 4) == x * 171 / 1024,大约是 == x * 1 / 6 == x / 6。

于 2015-12-07T15:15:10.293 回答
3

谷歌:关于倒数乘法的琼斯

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

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

所以如果我想要 1234/6,我可以做 1234*0x2AAA = 0xCDA774。整数/定点答案是 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。然后你只需要在尝试使用这个固定点时考虑精度/舍入。有时 16 位是不够的,你需要 24 或 32 或者谁知道......取决于除数。

于 2015-12-07T18:23:01.563 回答