如果您关心准确性,则 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) 对于好的代码,您需要注意准确性和溢出,这会导致小数点移动得更多(并且使用“小数点所在位置的标准符号”更重要) .