对于上面这两张图很多人是不是会有疑问,为什么计算出来的结果与实际不符。
这就牵扯到了计算机语言中浮点数计算的精度问题。
数序引申计算机
数学是一门抽数学引申计算机象的语言,而计算机的功能就是将问题进行具体化实现。所以建立浮点数标准(IEE-754),其目的就是计算机的存储中的数据到数学中一对一映射。但实际上无论任何浮点数的标准,总会存在用有限集合去映射无线集合的问题,这也就导致了存在精度误差问题。
计算机误差的产生
举个例子就比如0.1,0.2对于数学(十进制)而言是一个有限小数,但如果用二进制去表示他就会出现无线序列,所以在计算机进行运算是就会存在误差。
这里说明一下计算误差并不是单单因为计算的数据不能精确表示,最本质的语言还是因为有效位数的限制
这里我们假设有效位数是三位计算15+3.14+2.77,精确计算得到20.91,四舍五入最后的20.9
计算机计算最后得到20.8,这里就存在精度误差是由有效位数引起的。
有效位的位数取决于具体使用的浮点数标准,单精度浮点数(float)中,我们有 23 位有效位,8位浮点位,而在双精度浮点数(double)中,则有 52 位有效位,11 位浮点位。
计算误差的累计
根据上面的例子有些人就会想误差就那么一点,不会太影响整体结果。那么接下我们再来看一个例子:计算15+0.1+0.1……+0.1中间包含100个0.1。(假设有效位数是两位)
精确计算得到115
计算机计算:
………… 最后得到15,两者足足相差100,可见有些时候误差的巨大。
减少精度误差
使用定点误差:使用整数存储有效数字,在使用另一个整数存储字数,最后以这种思路实现各种基础运算。
kahan求和算
最后在给大家介绍一种补偿算法
function KahanSum(input: number[]) {
double sum = 0.0;
double c = 0.0; // 存储被舍弃的精度数据for (let i = 1; i < input.length; i++)
{
double y = input[i] - c; // 将 c 加到新的数值中
double t = sum + y; // 舍弃了精度的结果(注意这里必须sum比y大)
c = (t - sum) - y; // (t - sum) 的到 y 没有被舍弃的高位部分,再 - y 就得到被舍弃的精 度 c
sum = t;
}
return sum
}
我们通过计算15 + 3.14159 + 2.71828的例子来过一遍(假设保留3位有效数)
精确计算得到20.85987,四舍五入20.9
第一次循环
y=3.14159-0.0=3.14159
t=15+3.14159=18.14159=18.1
c=(18.1-15)-3.14159=-0.04159
sum=18.1
第二次循环
y= 2.71828-(-0.04159)=2.75987
t=18.1+2.75987=20.85987=20.9
c=(20.9-18.1)-2.75987=0.04013
sum=20.9//得到精确结果
但是Kahan 求和算法并不适用所有场景,我们要学习的是这种补偿误差的思想。