日期:2014-05-20  浏览次数:20685 次

Java浮点运算的一个怪异现象:0.01+0.09不等于0.1?!
public class Test
{
  public static void main(String[] args)
  {
  double x = 0.01;
  double y = 0.09;
  System.out.println(x + y);
  }
}

为什么输出结果是0.09999999999999999而不是0.1啊?
奇怪的是当x,y改为float后,结果就等于0.1了,
更奇怪的是,如果把x,y分别改为float的0.01和0.04,在相加,结果居然是0.049999997,
这种浮点运算不精确的背后原理到底是什么呢?

更加怪异的是,这段代码放到C#中运行,出来的结果却是0.1

    class Test
    {
        static void Main()
        {
             double x = 0.01;
             double y = 0.09;
             Console.WriteLine(x + y);
             Console.ReadKey();
        }
    }

------解决方案--------------------
第一点,你要明确java里面32位的float对0.1在内存当中的表示是不精确的,不是你理解的那么100%精确,抛弃这个概念,产生这个误差的原因简单理解在32位的float的表达能力有限和计算机的二进制缘故吧,往下说也很复杂。
大体说下,float的0.1二进制形式是001111011 10011001100110011001101,根据符号位换算为10进制表达的值精确应该是这样计算 110011001100110011001101乘以2的负27次方,实际值是0.100000001490116119384765625
这样就产生了实际误差
这个误差对我们生活小打小闹没啥影响,但是对科学计算和银行这样的应用或者领域是致命的,因此要用Java银行以及科学计算会用java.math.BigDecimal提高精度,否则后果极其严重。

参考我曾经回答过的CSDN帖子http://topic.csdn.net/u/20110804/00/15380122-33a1-474a-9b7f-3e90a000f0a8.html
------解决方案--------------------
这个涉及到浮点数的结构(符号位,指数部分,尾数部分),以及规格化的表示和非规格化的表示。
这里不详细解释,LZ可查阅相关资料
有几个知识点:
  1.计算机中浮点数的基数是2.
  2.指数部分有偏移量(float为127, double为1023)
  3.规格化的表示小数点左边一定为1.(二进制数)
  4.float类型 符号位占1位,指数部分占8位,尾数占23位(因为规格化表示,小数点左边一定为1,所以实际有24位精度)
  5.double类型 符号位占1位,指数部分占11位,尾数占52位(因为规格化表示,小数点左边一定为1,所以实际有53位精度)

看一个例子float 0.6吧:
第一步,把十进制转2进制:
 0.6的二进制表示(乘2取整,顺序表示):
  .1001 1001 1001 1001 1001 1001 1001 ... 无限循环下去。

第二步,计算尾数部分:
 把.1001 1001 1001 1001 1001 1001 1001 ...规格化表示(小数点移到第一个非0书右边)就是:
  1.001 1001 1001 1001 1001 1001 1001 ...,右移了1位。
 由于规格化表示的数小数点左边一定为1,把这个1舍弃,并保留float尾数能表示的23位,最终尾数部分是:
  001 1001 1001 1001 1001 1001

第三步,计算指数部分: 
 由于计算尾数时右移了1位,相当于乘以2的负1次,所以指数为-1,加上float偏移量127,最后指数为126,
 二进制表示为 0111 1110

第四步,符号部分:
 0.6为正数,符号位为0

最终0.6在计算机中的表示就是:

 符号位 指数 尾数
  0 0111 1110 001 1001 1001 1001 1001 1001
--------------------------------------------------------------
我们再从这个2进制来计算10进制数:(2的指数次 * 尾数)
 符号位0--> 为正
 指数 0111 1110:为126, 减去偏移量127,结果为-1.
 尾数 001 1001 1001 1001 1001 1001: 规格化的时候小数点左边人掉了一个1,现在加上:
  1.001 1001 1001 1001 1001 1001,转为10进制就是:
  1*2^0 + 1*2^-3 + 1*2^-4 + .....= 1.19999992847442626953125
1.19999992847442626953125 * 2^-1 = 0.599999964237213134765625
所以最终结果是一个无限接近于0.6而不能精确表示0.6 

--------------------------------------
其实我在这个帖子已经回复过了