【问题标题】:Fixed point multiplication in assembly (x86)汇编中的定点乘法(x86)
【发布时间】:2021-10-31 15:17:11
【问题描述】:

我想乘除一个无符号的 8.8 定点数 ax 用 1.00125 注册并将结果也存储在 ax 中。

我知道定点乘法/除法需要一些额外的步骤 但我不知道如何在汇编中实现这些。

非常感谢您的帮助。

【问题讨论】:

  • 从编写伪代码或高级语言开始。找出需要的数学运算序列(乘法、除法、加法、减法、移位、屏蔽)。一旦你清楚地定义了算法,然后考虑如何在汇编中实现它。查找可用于完成算法的每个步骤的指令或序列。
  • 请注意,x86 具有扩大乘法和类似的除法,除数是商和除数的两倍,所以如果你使用它们而不是移位加法,你可以利用它来获得高半积很容易。 0.00125 小于 1/256,如果您尝试将其表示为 8.8 定点数,它将舍入为 0。而且它不是 2^-n 二进制小数,因此它不仅仅是简单的右移和加法。
  • 最简单的方法可能是使用浮点数,然后将结果转换回 8.8 定点。

标签: math assembly x86 fixed-point


【解决方案1】:

如果您关心准确性,则 1.00125 不能以任何整数格式或任何浮点格式精确存储,因为它是二进制的递归分数(在二进制中,它是 1.000000000101000111101011100001010001111010111...b00001010001111010111 序列永远重复)。出于这个原因,我会将其转换为有理数 801/800;然后执行x * 1.00125 = (x * 801) / 800(可能在除法上使用“四舍五入”)。

如果您不关心准确性,那么您可以为“1.00125”使用的位数越多,结果就越接近正确答案。使用 8 位(“1.7 定点”),您可以获得的最接近的是 1.0000000b,这意味着您可以跳过乘法(x * 1.00125 = x)。使用 16 位(“1.15 定点”),您可以获得的最接近的是 1.000000000101001b(或十进制的 1.001220703125)。

但是,您可以作弊更多。具体来说,您可以通过执行(x * 1) + (x * 0.00125) 在相同位数的情况下显着提高准确性。例如。而不是像 1.000000000101001b 那样的 16 位常量(其中 9 位是零),您可以使用像 0.0000000001010001111010111b 这样的 16 位常量(其中 16 位是最后 16 位,没有任何前导零)。在这种情况下,常数非常接近(如 0.00124999880),而不是“不太接近”(如 1.001220703125)。

讽刺的是;只有 16 位,这个“0.00125”比 1.00125 的 32 位浮点表示更准确。

所以.. 在汇编中(假设一切都是无符号的)它可能看起来像:

    ;ax = x << 8 (or x as an 8.8 fixed point number)

    mov cx,ax         ;cx = x << 8

    mov bx,41943      ;bx = 41943 = 0.00124999880 << 25
    mul bx            ;dx:ax = (x << 8) * (0.00124999880 << 25) = x * 0.00124999880 << 33
                      ;dx = x * 0.00124999880 << 17
    shr dx,9          ;dx = x * 0.00124999880 << 17 >> 9 = x * 0.00124999880 << 8, carry flag = last bit shifted out
    adc dx,0          ;Round up to nearest (add 1 if last bit shifted out was set)

    lea ax,[dx+cx]    ;ax = x << 8 + x * 0.00124999880 << 8 = x * 1.00124999880 << 8

当然,这里的问题是,将其转换回“8.8 定点”无论如何都会破坏大部分精度。为了保持大部分准确性,您可以改用 32 位结果(“8.24 定点”)。这可能看起来像:

    ;ax = x << 8 (or x as an 8.8 fixed point number)

    mov cx,ax         ;cx = x << 8

    mov bx,41943      ;bx = 41943 = 0.00124999880 << 25
    mul bx            ;dx:ax = (x << 8) * (0.00124999880 << 25) = x * 0.00124999880 << 33

    add ax,1 << 8     ;To cause the following shift to round to nearest
    adc dx,0

    shrd ax,dx,9
    shr dx,9          ;dx:ax = x * 0.00124999880 << 33 >> 0 = x * 0.00124999880 << 24

                      ;cx:0 = x << 24
    add dx,cx         ;dx:ax = x << 24 + x * 0.00124999880 << 24 = x * 1.00124999880 << 24

另一个问题是潜在的溢出。例如。如果x 为 0xFF.FF(或大约 255.996),则结果将类似于 256.32,它太大而无法适应“8.8”或“8.24”或“8.anything”定点格式。为避免该问题,您只需增加整数位数(并将精度降低 1 位) - 例如使结果为“9.7 定点”或“9.23 定点”。

这里的重点是:

a) 对于“定点”计算,每次运算(乘法、除法、加法……)都会导致小数点移动。

b) 因为小数点一直在移动,所以最好采用标准符号来表示小数点在每一步的位置。我的方法是在 cmets 中包含一个“显式移位”(例如“x 这种“在 cmets 中记录的显式移位”可以很容易地确定小数点的移动位置,以及是否/需要向左/向右移动多少才能转换为不同的定点格式。

c) 对于好的代码,您需要注意准确性和溢出,这会导致小数点移动得更多(并且使用“小数点所在位置的标准符号”更重要) .

【讨论】:

  • 加法/减法不会导致小数点(实际上是二进制点或“小数点”)移动,除非您更改格式以使溢出不可能。 add ax, cx 是将两个 8.8 数字相加到 8.8 结果中所需的全部内容。但是,是的,要获得 9.7 定点,您需要 rcr ax, 1 将进位移回顶部,然后丢弃低位。 (然后可选 adc ax, 0 如果丢弃的位是 1 则四舍五入,或者 inc ax before rcr; inc 保留 CF。两个数字的总和不能是 0xFFFF 有一个结转所以没关系)
  • @PeterCordes:是的,假设您以相同的格式添加数字。否则(如果小数位数不同)在加法或减法之前至少需要移位一个操作数。
  • 是的,因此我认为在点 (a) 中说“每个操作(...,加法,...)都会导致小数点移动”可能会产生误导。至少值得注意的是,这只是由于那里的格式更改,以避免让定点初学者对他们认为他们理解的东西感到困惑,这听起来像是你在自相矛盾。
【解决方案2】:

一个简单的解决方案是只使用 x87 浮点单元进行乘法运算。假设使用 nasm 的实模式(未经测试):

example:
        push    bp
        mov     sp, bp         ; establish stack frame
        push    ax
        push    ax             ; make space for quotient
        fild    word [bp-2]    ; load number
        fld     st0            ; duplicate top of stack
        fmul    dword [factor] ; compute product
        fistp   word [bp-2]
        fmul    dword [invfac] ; compute quotient
        fistp   word [bp-4]
        pop     dx             ; quotient
        pop     ax             ; product
        pop     bp             ; tear down stack framt
        ret

factor  dd 1.00125
invfac  dd 0.999875        ; 1/1.00125

这留下了dx 中的商和ax 中的产品。根据 x87 FPU 中配置的舍入模式进行舍入(默认情况下应该舍入到最接近的值)。

【讨论】:

    【解决方案3】:

    关于定点乘法的一点要理解,即结果的点是操作数 1 的点加上操作数 2 的点。

    因此,当将两个数字与定点零相乘时,我们会得到一个定点为零的结果。

    当两个数字在 8 位(二进制)处有定点时,我们得到一个在 16 位处有定点的数字。

    因此,需要根据需要缩小此类结果。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-05-15
      • 2016-01-11
      • 1970-01-01
      相关资源
      最近更新 更多