由double类型判等引发的一点小思考
其实这篇文章很早就想发出来的,由于工作比较忙碌,所以一直也没写,最近有点空闲时间了,所以趁此整理一下,分享出来。
今年以来一直在做一个交易所的项目,其中有部分交易的需求需要通过汇总统计来进行数据的聚合筛选,而聚合筛选的数据就是double类型的,我当然知道double类型进行判等的时候会有点坑,直到后来,有些业务需要其他同事来协同,然后就有个同事问我代码里面,写的if(Math.Abs(x-y)<=1E-7)是什么意思,SQL语句里面也这样写的,类似于这样的:
double[]
price = { 0.1, 0.2, 0.3, 0.8, 0.5 };
double sum = 1.9;
if (Math.Abs(sum - price.Sum()) <= 1E-7)
{
//todo
}
我解释说:double类型可能会有精度溢出的情况,导致在判等的时候会有丢失精度的问题,直接用“==”判等不行,所以就需要相减取绝对值再判断小于等于0.00000001的就认为它们是相等的。
因为这个细节上的问题,公司让我新出的面试题我也加进去了,但最后评审的时候说这道题涉及到的不仅仅是C#的知识点,万一人家没学过计算机组成原理那不整来瓜起了,所以又把这道题剔除了。
其实要究其真正原因,那还是得从计算机组成原理讲起。
我们知道double双精度类型有53 位有效数字精度(包含符号号),并总共占用8字节。
程序里面我们处理的大多是十进制,而计算机处理直接收二进制数据,学过进制转换的童鞋肯定知道整数都可以完美地进行进制间的转换,而double是小数,小数的进制转换就会出现循环小数的情况,而double的位数有限,就导致了十进制小数转二进制时出现了精度丢失的情况。
经典案例:0.1+0.2-0.3=5.551115123125783e-17?。
why?0.1+0.2-0.3居然不等于0?!!
来,把它们都转换成二进制来看看:
十进制0.1
>二进制0.00011001100110011…(循环0011)
>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-4(二进制移码为00000000010),符号位为0
>计算机存储为:0 00000000100 10011001100110011…11001
> 因为尾数最多52位,所以实际存储的值为0.00011001100110011001100110011001100110011001100110011001
再看看十进制0.2
>二进制0.0011001100110011…(循环0011)
>尾数为1.1001100110011001100…1100(共52位,除了小数点左边的1),指数为-3(二进制移码为00000000011),符号位为0
>存储为:0 00000000011 10011001100110011…11001
因为尾数最多52位,所以实际存储的值为0.00110011001100110011001100110011001100110011001100110011
十进制0.3
>按照上面相同的套路,得出来:0.0011001100110011001100110011001100110011001100110011,结尾的0011循环。
那么两者相加得: 0.00011001100110011001100110011001100110011001100110011001+0.00110011001100110011001100110011001100110011001100110011=0.01001100110011001100110011001100110011001100110011001100
也就是说只要0.3在最低位加一的话就和0.2+0.1一样了,而尾数的最低位是第52位,再乘上-2的阶码,就是2的负54次方,这个数刚好就是:5.551115123125783e-17。
所以如果要判等0.1+0.2-0.3和0,不能直接用“==”,必须相减取绝对值!
特别提醒:
这绝对不仅仅限于C#,所有的编程语言的double都需要这样去判等,包括js、Python等弱类型语言(浮点型),谨记! 路过 帮顶 嘿嘿 发发呆,回回帖,工作结束~ 楼下的接上。。。。 我了个去,顶了 支持,楼下的跟上哈~ 是爷们的娘们的都帮顶!大力支持 看起来不错 佩服佩服! 我是个凑数的。。。