• 【解惑】剖析float型的内存存储和精度丢失问题


    问题提出:12.0f-11.9f=0.10000038,"减不尽"为什么?

     

    现在我们就详细剖析一下浮点型运算为什么会造成精度丢失?

     

    1、小数的二进制表示问题

           首先我们要搞清楚下面两个问题:

         (1)  十进制整数如何转化为二进制数

               算法很简单。举个例子,11表示成二进制数:

                         11/2=5   余   1

                           5/2=2   余   1

                           2/2=1   余   0

                           1/2=0   余   1

                              0结束         11二进制表示为(从下往上):1011

              这里提一点:只要遇到除以后的结果为0了就结束了,大家想一想,所有的整数除以2是不是一定能够最终得到0。换句话说,所有的整数转变为二进制数的算法会不会无限循环下去呢?绝对不会,整数永远可以用二进制精确表示 ,但小数就不一定了。

          (2) 十进制小数如何转化为二进制数

               算法是乘以2直到没有了小数为止。举个例子,0.9表示成二进制数

                         0.9*2=1.8   取整数部分  1

                         0.8(1.8的小数部分)*2=1.6    取整数部分  1

                         0.6*2=1.2   取整数部分  1

                         0.2*2=0.4   取整数部分  0

                         0.4*2=0.8   取整数部分  0

                         0.8*2=1.6   取整数部分  1

                         0.6*2=1.2   取整数部分  0

                                  .........      0.9二进制表示为(从上往下): 1100100100100......

               注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。

     

    2、 float型在内存中的存储 

         众所周知、 Java 的float型在内存中占4个字节。float的32个二进制位结构如下

               

             float内存存储结构   

                     4bytes          31                  30            29----23        22----0         

                            表示       实数符号位      指数符号位        指数位          有效数位

            其中符号位1表示正,0表示负。有效位数位24位,其中一位是实数符号位。

     

             将一个float型转化为内存存储格式的步骤为:

            (1)先将这个实数的绝对值化为二进制格式,注意实数的整数部分和小数部分的二进制方法在上面已经探讨过了。 
         (2)将这个二进制格式实数的小数点左移或右移n位,直到小数点移动到第一个有效数字的右边。 
         (3)从小数点右边第一位开始数出二十三位数字放入第22到第0位。 
         (4)如果实数是正的,则在第31位放入“0”,否则放入“1”。 
         (5)如果n 是左移得到的,说明指数是正的,第30位放入“1”。如果n是右移得到的或n=0,则第30位放入“0”。 
         (6)如果n是左移得到的,则将n减去1后化为二进制,并在左边加“0”补足七位,放入第29到第23位。如果n是右移得到的或n=0,则将n化为二进制后在左边加“0”补足七位,再各位求反,再放入第29到第23位。

     

              举例说明: 11.9的内存存储格式

           (1) 将11.9化为二进制后大约是" 1011. 1110011001100110011001100..."。

           (2) 将小数点左移三位到第一个有效位右侧: "1. 011 11100110011001100110 "。 保证有效位数24位,右侧多余的截取(误差在这里产生了 )。

           (3) 这已经有了二十四位有效数字,将最左边一位“1”去掉,得到“ 011 11100110011001100110 ”共23bit。将它放入float存储结构的第22到第0位。

           (4) 因为11.9是正数,因此在第31位实数符号位放入“0”。

           (5) 由于我们把小数点左移,因此在第30位指数符号位放入“1”。

           (6) 因为我们是把小数点左移3位,因此将3减去1得2,化为二进制,并补足7位得到0000010,放入第29到第23位。

     

               最后表示11.9为:  0 1 0000010 011 11100110011001100110

     

               再举一个例子:0.2356的内存存储格式
          (1)将0.2356化为二进制后大约是0.00111100010100000100100000。 
          (2)将小数点右移三位得到1.11100010100000100100000。 
          (3)从小数点右边数出二十三位有效数字,即11100010100000100100000放
    入第22到第0位。 
          (4)由于0.2356是正的,所以在第31位放入“0”。 
          (5)由于我们把小数点右移了,所以在第30位放入“0”。 
          (6)因为小数点被右移了3位,所以将3化为二进制,在左边补“0”补足七
    位,得到0000011,各位取反,得到1111100,放入第29到第23位。 
           

               最后表示0.2356为:0 0 1111100 11100010100000100100000

     

               将一个内存存储的float二进制格式转化为十进制的步骤: 
         (1)将第22位到第0位的二进制数写出来,在最左边补一位“1”,得到二十四位有效数字。将小数点点在最左边那个“1”的右边。 
         (2)取出第29到第23位所表示的值n。当30位是“0”时将n各位求反。当30位是“1”时将n增1。 
         (3)将小数点左移n位(当30位是“0”时)或右移n位(当30位是“1”时),得到一个二进制表示的实数。 
         (4)将这个二进制实数化为十进制,并根据第31位是“0”还是“1”加上正号或负号即可。

     

    3、浮点型的减法运算

     

              浮点加减运算过程比定点运算过程复杂。完成浮点加减运算的操作过程大体分为四步:   
        (1) 0操作数的检查;

                    如果判断两个需要加减的浮点数有一个为0,即可得知运算结果而没有必要再进行有序的一些列操作。 

       (2) 比较阶码(指数位)大小并完成对阶;

                    两浮点数进行加减,首先要看两数的 指数位 是否相同,即小数点位置是否对齐。若两数 指数位 相同,表示小数点是对齐的,就可以进行尾数的加减运算。反之,若两数阶码不同,表示小数点位置没有对齐,此时必须使两数的阶码相同,这个过程叫做对阶 。

                    如何对 阶(假设两浮点数的指数位为 Ex 和 Ey ):

            通过尾数的移位以改变 Ex 或 Ey ,使之相等。 由 于浮点表示的数多是规格化的,尾数左移会引起最高有位的丢失,造成很大误差;而尾数右移虽引起最低有效位的丢失,但造成的误差较小,因此,对阶操作规定 使尾数右移,尾数右移后使阶码作相应增加,其数值保持不变。很显然,一个增加后的阶码与另一个相等,所增加的阶码一定是小阶。因此在对阶时,总是使小阶向大阶看齐 ,即小阶的尾数向右移位 ( 相当于小数点左移 ) ,每右移一位,其阶码加 1 ,直到两数的阶码相等为止,右移的位数等于阶差 △ 。 
       (3) 尾数(有效数位)进行加或减运算;

                   对阶完毕后就可 有效数位 求和。 不论是加法运算还是减法运算,都按加法进行操作,其方法与定点加减运算完全一样。 
       (4) 结果规格化并进行舍入处理。

                    略

     

            浮点数的加减法:具体见http://www.zzslxx.com/wmy/jy/Chap02/2.7.1.htm

     

    4、 计算12.0f-11.9f

     

           12.0f 的内存存储格式为:    0 1 0000010 10000000000000000000000     

         11.9f 的内存存储格式为:     0 1 0000010 011 11100110011001100110

     

         可见两数的指数位完全相同,只要对有效数位进行减法即可。

         12.0f-11.9f   结果:         0 1 0000010 00000011001100110011010

          

         将结果还原为十进制为: 0.000 11001100110011010= 0.10000038

  • 相关阅读:
    托付和事件的使用
    在使用supervisord 管理tomcat时遇到的小问题
    无法安装vmware tools的解决方PLEASE WAIT! VMware Tools is currently being installed on your system. Dependin
    (转)Openlayers 2.X加载高德地图
    (转)openlayers实现在线编辑
    (转) Arcgis for js加载百度地图
    (转)Arcgis for js加载天地图
    (转) 基于Arcgis for Js的web GIS数据在线采集简介
    (转) Arcgis for js之WKT和GEOMETRY的相互转换
    (转)Arcgis for Js之Graphiclayer扩展详解
  • 原文地址:https://www.cnblogs.com/vinozly/p/5877694.html
Copyright © 2020-2023  润新知