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();
}
}
...全文
2505 20 打赏 收藏 举报
写回复
用AI写文章
20 条回复
切换为时间正序
请发表友善的回复…
发表回复
aptx4869cool 2013-11-01
  • 打赏
  • 举报
回复
我好奇的是为什么在C#中,浮点的运算看起来要比java中精确很多,如同LZ提到的例子,在C#中就是精确的1,当中是做了什么处理?java和C\C++一样,0.1多打印几位最后就会出现非0精度问题,C#中却一直都是精确的,为什么
老张-AI 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
源码直接下载地址: https://pan.quark.cn/s/387d7c8f6289 在使用IE浏览器的过程中,偶尔会出现重置为默认配置操作未能顺利完成的现象。这种情况的发生可能源于多种因素,包括注册表项出现偏差、浏览器附加组件之间存在不兼容、操作系统授权设置不当或受到恶意程序影响等。针对这一挑战,我们可以通过一系列系统性的方法来逐一诊断和纠正。首先需要验证当前用户是否具备管理员级别的操作权限。在执行重置配置的操作时,必须确保IE浏览器是以管理员身份启动的。具体操作方式为,使用鼠标右键单击IE程序图标,并从弹出的菜单中选择“以管理员身份运行”这一选项。其次,应当检查是否存在恶意软件。恶意程序有时会非法修改浏览器的配置信息,从而阻碍重置过程的正常进行。用户可以借助Windows系统自带的Microsoft Safety Scanner进行检测,或者选择安装并使用第三方安全防护软件执行全面的系统扫描。紧接着,需要对浏览器的缓存数据以及Cookies文件进行清理。在IE浏览器界面中,通过访问“工具”菜单并选择“Internet选项”,然后在弹出的设置界面中,切换至“常规”标签页,并执行清除浏览历史记录的操作,此过程应包含Cookies和临时互联网文件。倘若上述步骤均未能解决问题,那么可以考虑禁用或移除那些可能引发冲突的浏览器附加组件。相关操作需要在“工具”菜单下找到并打开“管理加载项”,对可疑的插件进行禁用处理,并在浏览器重新启动后再次尝试执行重置操作。再者,修复IE浏览器相关的注册表配置也是至关重要的环节。注册表编辑器是一个功能强大的系统工具,任何不当的操作都可能导致严重的系统问题,因此在进行修改前必须创建系统还原点作为安全备份。用户可以通过按下“Win+R”组合...
内容概要:本文详细介绍了光伏储能虚拟同步发电机(VSG)并网仿真模型的Simulink实现方法,系统研究了光伏发电与储能系统融合VSG技术并入电网的动态特性与控制策略。通过构建完整的VSG控制模块,模拟其在并网过程中的虚拟惯性响应、频率调节能力和有功无功功率支撑特性,验证了该系统在增强电网稳定性、提升新能源并网适应性方面的有效性。模型全面涵盖光伏出力特性、储能系统充放电管理、VSG有功-无功解耦控制、下垂控制机制以及并网点动态响应等关键环节,并通过多种工况下的仿真分析,评估系统在不同运行条件下的动态性能与鲁棒性。; 适合人群:具备电力系统、新能源发电、自动控制等相关专业背景,熟悉Matlab/Simulink仿真环境,从事新能源并网、微电网控制或电力电子系统研究的科研人员、工程技术人员及研究生。; 使用场景及目标:①深入理解光伏储能系统如何通过VSG技术模拟同步发电机的惯性和阻尼特性以支持电网稳定;②掌握虚拟惯量控制、下垂控制等VSG核心控制策略在Simulink中的建模、参数设计与实现方法;③为新型电力系统中新能源并网的动态仿真、稳定性分析及控制策略优化提供可靠的模型基础和技术参考。; 阅读建议:建议结合所提供的仿真模型进行逐步操作与参数调试,重点关注VSG控制环路的结构设计、控制器参数整定及其对系统动态性能的影响,同时可将该模型拓展应用于多机并联、微电网孤岛/并网切换、多能源协同控制等更复杂的系统场景中进行进阶研究。

62,620

社区成员

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

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