lin*_*xxx 0 math x86 assembly fixed-point
我想将 ax 寄存器中的无符号 8.8 定点数与 1.00125 相乘和相除,并将结果也存储在 ax 中。
我知道定点乘法/除法需要一些额外的步骤,但我不知道如何在汇编中实现这些步骤。
非常感谢您的帮助。
如果您关心准确性,则 1.00125 无法以任何整数格式或任何浮点格式精确存储,因为它是二进制的递归分数(在二进制中,它是1.000000000101000111101011100001010001111010111...b
该00001010001111010111
序列永远重复的位置)。因此,我将其转换为有理数 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)
。例如,您可以使用 16 位常量,如 0.0000000001010001111010111b(其中 16 位是最后 16 位,没有任何前导零),而不是像 1.000000000101001b 这样的 16 位常量(其中 9 位是零)。在这种情况下,常数非常接近(如 0.00124999880),而不是“不太接近”(如 1.001220703125)。
讽刺地; 由于只有 16 位,这个“0.00125”比 32 位浮点表示的 1.00125 更准确。
所以..在汇编中(假设一切都未签名)它可能看起来像:
;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
Run Code Online (Sandbox Code Playgroud)
当然,这里的问题是,将其转换回“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
Run Code Online (Sandbox Code Playgroud)
另一个问题是存在潜在的溢出。例如,如果x
是 0xFF.FF(或大约 255.996),则结果将类似于 256.32,它太大而无法适应“8.8”或“8.24”或“8.anything”定点格式。为了避免这个问题,您只需增加整数位数(并将精度降低 1 位) - 例如,使结果为“9.7 定点”或“9.23 定点”。
这里的要点是:
a) 对于“定点”计算,每个运算(乘法、除法、加法……)都会导致小数点移动。
b) 由于小数点不断移动,因此最好对每一步的小数点位置采用标准表示法。我的方法是在注释中包含“显式转变”(例如“x << 8”而不仅仅是“x”)。这种“注释中记录的显式移位”可以轻松确定小数点移动的位置,以及是否/需要左移/右移多少以转换为不同的定点格式。
c) 对于好的代码,您需要注意准确性和溢出,这会导致小数点移动得更多(并且使得使用“小数点位置的标准表示法”更加重要)。