`
beneo
  • 浏览: 55347 次
  • 性别: Icon_minigender_1
  • 来自: 希伯來
社区版块
存档分类
最新评论

为神马 1.0 - 0.7 != 0.3 ???

    博客分类:
  • java
阅读更多
直接上代码

    public static void main(String[] args) {
        System.out.println(1.0 - 0.1);
        System.out.println(1.0 - 0.2);
        System.out.println(1.0 - 0.3);
        System.out.println(1.0 - 0.4);
        System.out.println(1.0 - 0.5);
        System.out.println(1.0 - 0.6);
        System.out.println(1.0 - 0.7);
        System.out.println(1.0 - 0.8);
        System.out.println(1.0 - 0.9);
        System.out.println(1.0 - 1.0);
    }


最后输出的结果为神马是

0.9
0.8
0.7
0.6
0.5
0.4
0.30000000000000004
0.19999999999999996
0.09999999999999998
0.0


为什么呢?

简单的说,问题处在"IEEE 754 floating-point arithmetic",虽然在java是遵循这个规则的,但是java语言的实现,并不是使用小数点或者十进制来表示数字,相反,它是采用分数和指数来表示,而且是
引用
uses binary fractions and exponents to represent
使用二进制的,我们可以举个例子:

0.5 = 1/2
0.75 = 1/2 + 1/(2^2)
0.85 = 1/2 + 1/(2^2) + 1/(2^3)
0.1 = 1/(2^4) + 1/(2^5) + 1/(2^8) + ...


注意,0.1只能是无限循环下去的,这就意味着0.1在java里面不能够准确的用浮点数来表示,也就造成了浮点数运算上面的误差。

举个例子:

        if (0.1 + 0.1 + 0.1 != 0.3)
            System.out.println("0.1 + 0.1 + 0.1 is not equal with 0.3");
        else
            System.out.println("0.1 + 0.1 + 0.1 is equal to 0.3");


每个人都知道,0.1 + 0.1 + 0.1 == 0.3,但是在java的实际结果却不是这样。

更深入的话

有人会问,为什么
System.out.println(0.1f);

输出的还是0.1呢?
因为在源代码里面println调用的是Float#toString(float),最终的实现是在
    public static String toString(float f) {
        return new FloatingDecimal(f).toJavaFormatString();
    }

有兴趣的童鞋可以去阅读源代码,FloatingDecimal帮你做了很多事情。

这也牵涉出另外一个话题,如何避免上面出现的问题,

对的,就是BigDecimal,关于BigDecimal,我相信你们在api上面会找到更多的答案。

题外话


BigDecimal(java.lang.String)
不要用
BigDecimal(double) or BigDecimal(float)

为什么呢?API上面写的很清楚了
引用
The results of this constructor can be somewhat unpredictable. One might assume that writing new BigDecimal(0.1) in Java creates a BigDecimal which is exactly equal to 0.1 (an unscaled value of 1, with a scale of 1), but it is actually equal to 0.1000000000000000055511151231257827021181583404541015625. This is because 0.1 cannot be represented exactly as a double (or, for that matter, as a binary fraction of any finite length). Thus, the value that is being passed in to the constructor is not exactly equal to 0.1, appearances notwithstanding.

所以
        System.out.println(new BigDecimal(0.1f));
        System.out.println(new BigDecimal("0.1"));
        System.out.println(0.1f);

结果是不一样的

更加更加更加深入的话
        if (0.1f + 0.1f + 0.1f != 0.3f)
            System.out.println("0.1 + 0.1 + 0.1 is not equal to 0.3");
        else
            System.out.println("0.1 + 0.1 + 0.1 is equal to 0.3");

        if (0.1 + 0.1 != 0.2)
            System.out.println("0.1 + 0.1 is not equal to 0.2");
        else
            System.out.println("0.1 + 0.1 is equal to 0.2");


为何上面两串代码输出不一样?为何0.3d就不能用3个0.1d相加,而0.2d就可以用2个0.1d相加呢

原因的话,自己看下面的输出了
        System.out.println(new BigDecimal(0.2d));
        System.out.println(new BigDecimal(0.1d).add(new BigDecimal(0.1d)));

        System.out.println(new BigDecimal(0.2f));
        System.out.println(new BigDecimal(0.1f).add(new BigDecimal(0.1f)));

        System.out.println(new BigDecimal(0.3d));
        System.out.println(new BigDecimal(0.1d).add(new BigDecimal(0.1d)).add(new BigDecimal(0.1d)));

        System.out.println(new BigDecimal(0.3f));
        System.out.println(new BigDecimal(0.1f).add(new BigDecimal(0.1f)).add(new BigDecimal(0.1f)));








分享到:
评论
25 楼 miaow 2011-01-27  
double也不是不能用...只要确知结果的误差小于所需的最小精度就行。现代CPU算double可比BigDecimal快得多,特别是除法。
一概用BigDecimal,说得难听点,纯粹是用一个偷懒(分析数据的具体情况)超过另一个更偷懒(不注意精度问题)。
账目只有RMB/USD的基本可以放心用。当然要记得用差数代替比较。

真正算帐讨厌的是汇率和分摊之类形成的舍入误差处理,呵呵。
24 楼 必杀熊猫 2011-01-26  
wu_quanyin 写道
float doule都不能用于加减乘除运算,会得到一些奇怪的结果.,用BigDecimal


受教了
23 楼 diaoweili 2011-01-24  
你学JAVA了,你还能问出 “为神马”??
22 楼 robiplus 2011-01-24  
貌似有书上推荐说涉及到钱都要自己写个money类那个是什么情况?
21 楼 caoxiaohucxh 2011-01-24  
lanbingfeihan 写道
steafler 写道
浮点数不是这样比较的

是的, double类型不能这么进行比较。应该是数据的精度问题。

能不能详细说下,怎么回事!谢谢
20 楼 xingzhaozhong 2011-01-24  
hatedance 写道
这不是语言的问题,这是计算机cpu最终采用2进制的问题。

19 楼 ugibb510 2011-01-24  
tedeyang 写道
金融运算的常识。




18 楼 beneo 2011-01-24  
看来大家都不屑于这类“基础”的问题啊。。。不知道有多少人实际用的时候出现问题,却不知道答案
17 楼 tedeyang 2011-01-24  
金融运算的常识。
16 楼 zjhlht 2011-01-24  
原来如此!真的受教了!

一直都是用BigDamiel(String)
15 楼 liu00791 2011-01-24  
浮点 是不精确的 这样比较肯定是有问题的,一般是把两个比较的数字相减 然后如果其结果值在某个范围内则认为这两个数字是相等的
14 楼 lanbingfeihan 2011-01-24  
steafler 写道
浮点数不是这样比较的

是的, double类型不能这么进行比较。应该是数据的精度问题。
13 楼 wu_quanyin 2011-01-24  
float doule都不能用于加减乘除运算,会得到一些奇怪的结果.,用BigDecimal
12 楼 xjtusehcy 2011-01-23  
推荐《深入理解计算机系统》。里面对这个浮点数问题讲的很详细。浮点数不能比较等于。
11 楼 thinpay 2011-01-23  
没仔细看,貌似有点明了
10 楼 hatedance 2011-01-23  
这不是语言的问题,这是计算机cpu最终采用2进制的问题。
9 楼 steafler 2011-01-23  
浮点数不是这样比较的
8 楼 猫博士 2011-01-23  
受教了。
之前一直只知道浮点数不精确,但是不知道原因。
7 楼 vieri122 2011-01-23  
可能真是语言实现机制不一样。写了个c++的程序(TRACE函数来自mfc)
TRACE("%f\n",1.0 - 0.1);
TRACE("%f\n",1.0 - 0.2);
TRACE("%f\n",1.0 - 0.3);
TRACE("%f\n",1.0 - 0.4);
TRACE("%f\n",1.0 - 0.5);
TRACE("%f\n",1.0 - 0.6);
TRACE("%f\n",1.0 - 0.7);
TRACE("%f\n",1.0 - 0.8);
TRACE("%f\n",1.0 - 0.9);
TRACE("%f\n",1.0 - 1.0);
输出:
0.900000
0.800000
0.700000
0.600000
0.500000
0.400000
0.300000
0.200000
0.100000
0.000000
6 楼 學會☆~Snow 2011-01-22  
之前被问到,涉及比较抽象。故当时答得很烂。真后悔没有好好去研究

相关推荐

Global site tag (gtag.js) - Google Analytics