刚刚在逛Stack的时候,看见有人在问Java下的一个浮点数运算的问题,这个问题我之前也碰到过,不过项目中遇见的比较少,就忘记了。想想还是做个笔记,记录一下,以供后续温习。
有趣的小例子
先做一道算术题0.1+0.2=?,也许你想都不用想就回答等于0.3,那么在计算机中是如何表现的呢?测试如下:
var a = 0.1 + 0.2; var b = 0.3; Console.WriteLine(a); Console.WriteLine(a == b);
输出结果是:0.3和False,0.3!=0.3?且不说为什么,再测试一段:
float a = 0.7f; float b = 0.6f; Console.WriteLine(a - b);
输出结果是:0.09999996,不等于0.1,我的机器抽风了?再试试:
float a = 0.75f; float b = 0.25f; Console.WriteLine(a - b);
输出结果是:0.5,居然又正常了,顿时对C#有了一股深深的怨念,这点小事儿你都办不好,不过想想那些大神的能力肯定甩我几十条街,不会犯这种低级错误,还是看看为什么吧。
分析
究其根本,还是因为计算机采用二进制来表示十进制数据,C#中采用IEEE754标准来存储浮点格式,单精度的浮点格式分为1位符号位、8位偏置指数位、23位小数位,通常,我们将十进制转换为二进制需要进行如下操作:
将浮点型数据分为整数部分和小数部分,整数部分除2取余,得到的商再除以2取余,直到商等于0为止,然后把得到余数反序排列,就得到了整数部分;小数部分用乘2法不断将整数部分取出,知道小数中的部分为0或者达到精度时为止。以4.25为例:
整数位:4
4/2=2 0
2/2=1 0
1/2=0 1
将001反序得到100即整数位4
小数位:0.25
0.25*2=0.5 0
0.5*2=1.0 1
01即表示0.25
可是当小数位为0.6时,推算如下:
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 1
0.2*2=0.4 0
0.4*2=0.8 0
0.8*2=1.6 1
......
如此循环往复,很明显23位是无法完全表述0.6这个小数的,这也就导致了上诉异常的产生。
.NET中提供了decimal来解决这个问题,但decimal也是浮点数类型,只是精度更高,仍然有精度损失存在,所以,浮点数运算是一件非常危险的事情,还请慎重。