Java浮点运算的一个怪异现象:0.01+0.09不等于0.1?!

bigbro001 2011-08-31 10:28:16
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();
}
}
...全文
1987 20 打赏 收藏 转发到动态 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
aptx4869cool 2013-11-01
  • 打赏
  • 举报
回复
我好奇的是为什么在C#中,浮点的运算看起来要比java中精确很多,如同LZ提到的例子,在C#中就是精确的1,当中是做了什么处理?java和C\C++一样,0.1多打印几位最后就会出现非0精度问题,C#中却一直都是精确的,为什么
zl3450341 2011-09-01
  • 打赏
  • 举报
回复
浮点数,不能用直接用来计算,会损失精度的。
风尘中国 2011-09-01
  • 打赏
  • 举报
回复
第一点,你要明确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
一蚊惊人小号 2011-09-01
  • 打赏
  • 举报
回复
精度问题,楼上回答相当专业
wangfeiwoyun 2011-09-01
  • 打赏
  • 举报
回复
这个涉及到浮点数的结构(符号位,指数部分,尾数部分),以及规格化的表示和非规格化的表示。
这里不详细解释,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

--------------------------------------
其实我在这个帖子已经回复过了
lixu1988714 2011-09-01
  • 打赏
  • 举报
回复
这个其实很好理解,有些数在二进制是不能准确表示的。就像1/3如果在三进制就是3的负一次,而在十进制不能表示一样。
clementxr 2011-09-01
  • 打赏
  • 举报
回复
转换成二进制再转回来中间难免会有误差
teemai 2011-09-01
  • 打赏
  • 举报
回复
请查看《Java解惑》
24K純帥 2011-09-01
  • 打赏
  • 举报
回复
精度问题,用BigDecimal
彩票虫虫 2011-09-01
  • 打赏
  • 举报
回复
因为java 是不会表示1/10这个数值的,你可以使用bigdecimal这个类型试一下.
lpc10rj 2011-09-01
  • 打赏
  • 举报
回复
这样也可以吧!
public class DoubleClass{
public static void main(String[] args){
double x = 0.09;
double y = 0.01;
System.out.println((float)(x + y));
}
}
xiaona1047985204 2011-09-01
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 ioe_gaoyong 的回复:]

第一点,你要明确java里面32位的float对0.1在内存当中的表示是不精确的,不是你理解的那么100%精确,抛弃这个概念,产生这个误差的原因简单理解在32位的float的表达能力有限和计算机的二进制缘故吧,往下说也很复杂。
大体说下,float的0.1二进制形式是001111011 10011001100110011001101,根据符号位换算为10进制表达的值精确应该是这样计算 1100……
[/Quote]

同意 呵呵 学习了
izard999 2011-09-01
  • 打赏
  • 举报
回复
[Quote=引用 6 楼 ioe_gaoyong 的回复:]

第一点,你要明确java里面32位的float对0.1在内存当中的表示是不精确的,不是你理解的那么100%精确,抛弃这个概念,产生这个误差的原因简单理解在32位的float的表达能力有限和计算机的二进制缘故吧,往下说也很复杂。
大体说下,float的0.1二进制形式是001111011 10011001100110011001101,根据符号位换算为10进制表达的值精确应该是这样计算 1100……
[/Quote]
这个不错
meran 2011-09-01
  • 打赏
  • 举报
回复
不怪异 找本组成原理书自己看
teemai 2011-09-01
  • 打赏
  • 举报
回复
[Quote=引用 17 楼 wangfeiwoyun 的回复:]

这个涉及到浮点数的结构(符号位,指数部分,尾数部分),以及规格化的表示和非规格化的表示。
这里不详细解释,LZ可查阅相关资料
有几个知识点:
1.计算机中浮点数的基数是2.
2.指数部分有偏移量(float为127, double为1023)
3.规格化的表示小数点左边一定为1.(二进制数)
4.float类型 符号位占1位,指数部分占8位,尾数占23位(因为规格化表……
[/Quote]

很详细!
原来缘来 2011-08-31
  • 打赏
  • 举报
回复
这个很正常
The details of proper input conversion from a Unicode string representation of a floating-point number to the internal IEEE 754 binary floating-point representation are described for the methods valueOf of class Float and class Double of the package java.lang.
原因
amdgaming 2011-08-31
  • 打赏
  • 举报
回复
是的,用bigdecimal吧
龙四 2011-08-31
  • 打赏
  • 举报
回复
很多浮点数都是不能精确表示出来的,很正常
打油的程序员 2011-08-31
  • 打赏
  • 举报
回复

//java是一门很怪异的语言
System.out.println(1.0/0);//Infinity
System.out.println(1/0);// java.lang.ArithmeticException:

kai27ks 2011-08-31
  • 打赏
  • 举报
回复
JAVA的小数(浮点,DOUBLE)是会丢失精度的。要用精确计算就用BigDecimal

62,615

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧