一、浮点数不精确性

在自算计中打开python命令行输入0.3+0.6我们可以看到输出结果是0.899999,出现这种结果和计算机中对于浮点数的保存方法有关。

二、定点数的表达

        假如我们使用四个字节表示一个0~9的整数,32位就可以保存8个这样的整数,使用左边的6个表示整数位,右边两个表示小数位,这样就可以表示0~999999.99范围的数,总共有一亿个,这就是BCD编码。这种编码方式的缺陷也很明显:

  1. 浪费,32位本可以表示2^32个数字;
  2. 表示范围极其有限;

三、浮点数制表示

        计算机中实际对于浮点数的表示采用科学计数法,细节如下:

python 每天定点 python 定点数_数位

  1. 第一位表示符号位,0为正1为负。为s
  2. 2~9位为指数位,8位可以表示0~255,这里将1~254其映射到-126~127。为e
  3. 其余23位为有效数位。为f

        其所表示的数为 (-1)^s*1.f*2^e,除此之外有一些特殊表示:

python 每天定点 python 定点数_计算机组成原理_02

        例如0.5表示如下:

   

python 每天定点 python 定点数_python 每天定点_03

        由于指数位用1~254表示 -126~127,所以-1就是1~254的第126个数

四、浮点数的二进制转化

        十进制小数转为二进制小数方法是指数位用除2取余法;小数位则不断乘以2,如乘2的结果大于1则当前位取1,如小于1则当前位取0,继续乘以2,不断重复这个过程,直至乘的结果为1:

         

python 每天定点 python 定点数_浮点数_04

        十进制数9.1转为二进制,需要将整数位与小数位分开处理,整数位为1001,小数位如下,是一个无限循环小数0.000110011……,因此表示为1001.000110011……,左移三位表示为1.001000110011……

由于有效位只有23位,后面的会被截掉,最终表示结果为:

python 每天定点 python 定点数_python 每天定点_05

        将这个数转为十进制,准确结果为:9.09999942779541015625,这也就是浮点数表示法中精度损失的原因。

五、浮点数加法和精度损失

        浮点数的加法原则是六个字:先对齐,再计算;就是先将两个数的指数位转换成一致,就是把指数位较小的数右移之后再对齐,再计算有效位即可。例如十进制的0.5+0.125计算过程如下:

python 每天定点 python 定点数_数位_06

        在这个过程中我们发现在对齐操作中,有些数字的有效位会由于位移丢失,因此损失了精度。两个数相差越大则=对齐操作中损失精度的可能性越大,32位数有效位长23位,这就意味着如果两个数相差2^24倍(大概1600万倍),则两个数相加结果不会改变。

六、Kahan Summation 算法

        上面精度损失的例子中,假若我们连续加2000万次较小的那个数,计算结果还是不会变化,但实际值增加一倍多,在这种“积少成多”的情况下,误差就太大了,在机器学习的计算场景下这种情况尤其多。解决这个问题的办法就是科学家提出的Kahan Summation算法,这种方式计算2000万个1.0f相加的过程如下:

public class KahanSummation {
  public static void main(String[] args) {
    float sum = 0.0f;
    float c = 0.0f;
    for (int i = 0; i < 20000000; i++) {
      float x = 1.0f;
      float y = x - c;
      // 求和,需要使用sum值加1.0再加上上一步的精度损失c,主要是这个加法造成了进度损失。
      float t = sum + y;
      // 这一步拆分为两步,t和sum相差较小,t-sum的结果和y也非常接近,因此这一步精度损失几乎为0
    c = (t-sum)-y;
      sum = t;      
    }
    System.out.println("sum is " + sum);   
  }  
}

        实质是,每次计算时都使用一次减法(c=(t-sum)-y)将损失精度记录下来,下次相加时再补足(x=1.0f,y=x-c)