在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
但我不明白这个集会在做什么。 这个汇编代码如何执行除法?
答案 0 :(得分:4)
-85是0xFFFFFFFFFFFFFFAB,因此lo8(-85)
是0xAB,即171。
代码将参数乘以171,然后返回产品的最高有效字节,右移2(即除以4)。
所以它有效地返回x * 171 /(256 * 4)== x * 171/1024,大约是== x * 1/6 == x / 6.
答案 1 :(得分: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)&gt;&gt; 16约为X / 6。
那么四舍五入呢? 1234/6实际上是205和2 / 3rds所以如果你想要整理的话。四舍五入意味着你在截止后得到数字,如果它是在你的一半或更高的一半?好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或谁知道......取决于除数。