【问题标题】:How does division by constant work in avr-gcc? [duplicate]在 avr-gcc 中除以常数是如何工作的? [复制]
【发布时间】:2016-03-12 05:15:37
【问题描述】:

在 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

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

【问题讨论】:

  • 你的问题到底是什么?

标签: assembly avr atmega16


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    Google:关于倒数乘法的琼斯

    我们从小学就知道或应该知道,除以 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 或者谁知道...取决于除数。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-10-30
      相关资源
      最近更新 更多