这种除法近似算法如何工作?

时间:2017-01-22 00:00:07

标签: c division bit-shift approximation

我正在使用软件渲染器开发游戏,以获得最准确的PS1外观。当我正在研究PS1图形/渲染系统如何工作,摇摆顶点等的原因时,我偶然发现了一些关于它们划分方式的文档。以下是它的链接:http://problemkaputt.de/psx-spx.htm#gteoverview(参见“GTE Division Inaccuracy”部分)

相关代码:

  if (H < SZ3*2) then                            ;check if overflow
    z = count_leading_zeroes(SZ3)                ;z=0..0Fh (for 16bit SZ3)
    n = (H SHL z)                                ;n=0..7FFF8000h
    d = (SZ3 SHL z)                              ;d=8000h..FFFFh
    u = unr_table[(d-7FC0h) SHR 7] + 101h        ;u=200h..101h
    d = ((2000080h - (d * u)) SHR 8)             ;d=10000h..0FF01h
    d = ((0000080h + (d * u)) SHR 8)             ;d=20000h..10000h
    n = min(1FFFFh, (((n*d) + 8000h) SHR 16))    ;n=0..1FFFFh
  else n = 1FFFFh, FLAG.Bit17=1, FLAG.Bit31=1    ;n=1FFFFh plus overflow flag

我很难理解这是如何工作的,这个'unr'表是什么?我们为什么要转移东西? 如果有人能够更详细地解释这件事实际上是如何实现鸿沟的话,那将是值得赞赏的。

1 个答案:

答案 0 :(得分:4)

该算法是[0,1]中两个无符号16位小数值的定点除法。它首先通过表查找计算除数的倒数的初始9位近似值,使用单个Newton-Raphson迭代对其倒数进行精化,x i + 1 := x i *(2 - d * x i ),导致倒数精确到大约16位,最后乘以红利,在[0,2]中产生17位商)。

对于表查找,通过应用比例因子2 z ,首先将除数归一化为[0.5,1],显然,除数需要通过相同的比例因子进行调整。由于[0.5,1]中操作数的倒数将为[1,2],因此已知倒数的整数位为1,因此可以使用8位表条目来产生1.8个定点通过添加0x100(= 1)进行倒数。这里使用0x101的原因尚不清楚;这可能是由于要求此步骤总是高估真实的倒数。

接下来的两个步骤是Newton-Raphson迭代的逐字翻译,用于考虑定点比例因子的倒数。所以0x2000000代表2.0。代码使用0x2000080,因为它将以下除法的舍入常量0x80(= 128)合并为256,用于重新调整结果。下一步同样将0x00000080作为重新缩放除法的舍入常量加256.如果没有缩放,这将是纯乘法。

最终乘法n*dd中的倒数乘以n中的被除数,得到33位的商。再次,在除以65536之前应用舍入常数0x8000以重新调整到适当的范围,得到1.16定点格式的商。

连续重新缩放是定点计算的典型,其中人们试图保持尽可能大的中间结果,以最大化最终结果的准确性。有点不寻常的是,在所有中间算术中应用舍入,而不是仅在最后一步中应用舍入。也许有必要达到指定的准确度。

但是,该函数并非完全准确,可能是由初始近似的不准确引起的。在所有非例外情况下,2,424,807,756匹配正确舍入的1.16定点结果,780,692,403具有1 ulp的误差,15,606,093具有2-ulp误差,86,452具有3-ulp误差。在快速实验中,初始近似值u中的最大相对误差为3.89e-3。改进的表查找将u中的最大相对误差降低到2.85e-3,减少但不消除最终结果中的3-ulp错误。

如果您想查看具体示例,请考虑h = 0.3(0x4ccd)除以SZ3 = 0.2(0x3333)。然后z = 2,因此d = 0.2 * 4 = 0.8(0xcccc)。这会导致u = 1.25(0x140)。由于估计非常准确,我们期望(2 - d * u)接近1,事实上,d = 1.000015(0x10001)。精确的倒数出现在d = 1.250015(0x14001),因此商为n = 1.500031(0x18002)。