我在 Q22.10 中使用Goldschmidt 除法计算定点倒数,以用于我在 ARM 上的软件光栅化器。
这是通过将分子设置为 1 来完成的,即分子在第一次迭代时变为标量。老实说,我在这里有点盲目地遵循维基百科算法。文章说,如果分母在半开范围(0.5, 1.0] 内缩放,一个好的初步估计可以单独基于分母:设 F 为估计的标量,D 为分母,则 F = 2 - D.
但是这样做时,我会失去很多精度。假设我想找到 512.00002f 的倒数。为了缩小数字,我在小数部分损失了 10 位精度,将其移出。所以,我的问题是:
- 有没有办法选择一个不需要标准化的更好的估计?为什么?为什么不?为什么这可能或不可能的数学证明会很棒。
- 此外,是否可以预先计算第一个估计值,以便序列更快地收敛?现在,它平均在第 4 次迭代后收敛。在 ARM 上,这大约是 50 个周期的最坏情况,并且没有考虑 clz/bsr 的仿真,也没有考虑内存查找。如果可能的话,我想知道这样做是否会增加错误,以及增加多少。
这是我的测试用例。注意:第13行的软件实现clz
来自我的帖子here。如果需要,您可以将其替换为内在函数。clz
应该返回前导零的数量,32 表示值 0。
#include <stdio.h>
#include <stdint.h>
const unsigned int BASE = 22ULL;
static unsigned int divfp(unsigned int val, int* iter)
{
/* Numerator, denominator, estimate scalar and previous denominator */
unsigned long long N,D,F, DPREV;
int bitpos;
*iter = 1;
D = val;
/* Get the shift amount + is right-shift, - is left-shift. */
bitpos = 31 - clz(val) - BASE;
/* Normalize into the half-range (0.5, 1.0] */
if(0 < bitpos)
D >>= bitpos;
else
D <<= (-bitpos);
/* (FNi / FDi) == (FN(i+1) / FD(i+1)) */
/* F = 2 - D */
F = (2ULL<<BASE) - D;
/* N = F for the first iteration, because the numerator is simply 1.
So don't waste a 64-bit UMULL on a multiply with 1 */
N = F;
D = ((unsigned long long)D*F)>>BASE;
while(1){
DPREV = D;
F = (2<<(BASE)) - D;
D = ((unsigned long long)D*F)>>BASE;
/* Bail when we get the same value for two denominators in a row.
This means that the error is too small to make any further progress. */
if(D == DPREV)
break;
N = ((unsigned long long)N*F)>>BASE;
*iter = *iter + 1;
}
if(0 < bitpos)
N >>= bitpos;
else
N <<= (-bitpos);
return N;
}
int main(int argc, char* argv[])
{
double fv, fa;
int iter;
unsigned int D, result;
sscanf(argv[1], "%lf", &fv);
D = fv*(double)(1<<BASE);
result = divfp(D, &iter);
fa = (double)result / (double)(1UL << BASE);
printf("Value: %8.8lf 1/value: %8.8lf FP value: 0x%.8X\n", fv, fa, result);
printf("iteration: %d\n",iter);
return 0;
}