zoukankan      html  css  js  c++  java
  • 【转】为何浮点数可能丢失精度

    转自铅笔

    为何浮点数可能丢失精度?
    浮点十进制值通常没有完全相同的二进制表示形式。 这是 CPU 所采用的浮点数据表示形式的副作用。为此,可能会经历一些精度丢失,并且一些浮点运算可能会产生意外的结果。

    导致此行为的原因是下面之一:
    1、十进制数的二进制表示形式可能不精确。
    2、使用的数字之间类型不匹配(例如,混合使用浮点型和双精度型)。

    为解决此行为,大多数程序员或是确保值比需要的大或者小,或是获取并使用可以维护精度的二进制编码的十进制 (BCD) 库。

    详细剖析:浮点型运算为什么会造成精度丢失?

    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*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个二进制位结构如下
      ———————————————————————————————
      |  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 01111100110011001100110

    再举一个例子: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,直到两数的阶码相等为止,右移的位数等于阶差△ E 。

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

  • 相关阅读:
    mysqldump 导出数据库为DBname的表名为Tname的表结构 导出数据库的所有表的表结构
    mysqldump 备份某张表 Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions,
    nfs missing codepage or helper program, or other error
    date 增加一个小时 减少一个小时
    mysqldump 备份单个数据库
    mysql删除账户
    怎么删除某个用户的所有帖子?
    mongodb删除重复数据
    ReSharper2018破解详细方法
    激活windows和office
  • 原文地址:https://www.cnblogs.com/LLGemini/p/4764425.html
Copyright © 2011-2022 走看看