如何估计浮点误差

内容纲要

在进行PBR运算(几何运算)时能更精确地处理好浮点误差。

IEEE 浮点

极大量的当代GPU/CPU都按照IEEE规定的标准实现浮点数。

以float为例,float有32位,其中1位(最高位)为符号,接下来的8位为指数e,剩下的是小数c,那么原数n等于

(符号)2^(e-127)*1.c(2)

所以说1的二进制表示是01111111100000000000000000000000

当e=0时,需要特殊对待,此时n等于

(符号)2^(e-127)*0.c(2)

当e=255时,也需要特殊对待,如果e等于255,若c不为0,那么就是nan,否然是无穷(带符号)

比较有意思的一点就是nan与任何浮点进行任何比较都是false,!(x==x)可以验证x是否为nan。nan是“感染式”传递的,一顿操作猛如虎,最后输出nan,那么就一定是中间在哪儿出了茬子。

IEEE 运算

加减乘除与开根号这几种运算,IEEE规定实现上必须满足“结果最接近精确值”,这意味着(n-n')<0.5e,其中e为结果的指数级别下最小的浮点单位,n为真实结果,n'为计算得到的结果。如果刚好卡在两个数中间,默认会向偶数舎入。

听起来很牛叉,实现起来想必也不简单,不过那不是咱们考虑的东西辣(哈哈哈哈和)。

误差分析

令p为2^(-24)(相对误差下的0.5e),假设a,b,c,d都是float,考虑计算a+b+c+d,那么a+b+c+d最大可为

(((a+b)(1+p)+c)(1+p)+d)(1+p)

展开有

(a+b)(1+p)^3+c(1+p)^2+d(1+p)

由于p很小,那么可以放缩:

(1+p)^m < 1+(m+1)p

其实因为p实在是太小了,(1+p)^m和1+mp已经无限接近了,这里的1+(m+1)p是一个极度保守的估计。

那么刚才的计算中,最大误差为4p|a+b|+3p|c|+2p|d|。

容易发现这个误差和计算的顺序有关,所以渲染中设计核心算式时可以将较小的数字放在前面,能够减少误差,这个trick很鬼畜吧...

计算顺序和误差是有关的,比如这样:

((a+b)+(c+d))

算出来的误差是

3p|a+b|+3p|c+d|

如果有一个先验:a和-b比较接近,那么误差就会极大减小,所以如果精心设计算式,误差是可以扑通扑通地下降的。不过这种技巧已经出离鬼畜了...

这种技巧有个名称,叫做“前向误差分析”(forward error analysis),与之相对的是数值算法中更喜欢用的“后向误差分析”(这种东西可以逆推得到若要输入数据输入算法得出相同结果,输入数据所允许的误差范围)在计算方法李或许会学?

优化

刚才误差估计中,很让人不爽的一点是(1+p)^n放缩太狠了。加强放缩!

(1+p)^n =

1+np+n(n-1)p^2+...+n!p^n <=

1+np+(np)^2+...+(np)^n <

1/(1-np) = 1+np/(1-np)

令 r(n) = np/(1-np)

那么上文中a+b+c+d的误差上界是r(4)|a+b|+r(3)|c|+r(2)|d|,小很多了!

更进一步,(1+-p)^n/(1+-p)^m这种形式的误差也可以用类似方法去放缩,得到r(n+m)。

更多误差分析

考虑乘法

由于IEEE和我们🐮🍺的微电子工程师,咱们的乘法损失精度也是p(但是是在a*b尺度下的p),然后考虑已经产生了精度损失的两个数作乘法:

(a(1+r(n)))(b(1+r(m))) = ab(1+r(n+m))

为啥呢?

1+r(n) = 1/(1-np)

1+r(m) = 1/(1-mp)

(1+r(n))(1+r(m)) = 1/(1-(n+m)p+(nm)p^2) < 1-(1-(n+m)p) = 1+r(n+m)

好了,由于乘法还要多损失一个精度,所以实际上精度损失是r(n+m+1)

此条目发表在学习分类目录,贴了标签。将固定链接加入收藏夹。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注