赞
踩
最近刚入职,在公司做功能的时候,遇到一个问题。就是在C#中:
Mathf.Floor(9999.9995f) = 9999
Mathf.Floor(9999.9996f) = 10000
这就让我很迷惑。
因为Mathf.Floor()方法的官方文档上面些的是取float的最近的小于等于它的整数,而10000比9999.9996大啊!
于是查阅了相关资料后,发现原来这一切都是浮点数精度搞的鬼!
要解释为什么,就先要从底层原理说起。
所谓浮点数,简而言之就是小数。
IEEE754规定浮点数分为单精度浮点数(32位)、双精度浮点数(64位)、延伸单精确度(43位以上,很少使用)和延伸双精确度(79位以上,通常以80位实现)。
打个比方说,IEEE754将一个单精度浮点数-110.101写成 -1.10101 * 210(注意这里的阶数10是二进制的2) 。转化后的数总共分为三个部分,数符、阶码和尾数。
就是这个数的正负号,共占1位,1正0负。这里也就是1。
就是2的阶数(次方数),以移码表示,单精度浮点数共占8位,偏移量为127(28-1 - 1);双精度浮点数共占11位,偏移量为1023(211-1 - 1)。这里的10可以看成是0000 0010,转换成移码就是1000 0001。(简单的计算方法为:原码 + 偏移量 = 移码)
就是小数点后的数,以原码表示,单精度浮点数共占23位,双精度浮点数共占52位。这里是101 0100 0000 0000 0000 0000。
所以这个数在IEEE754的标准下表示为1 1000 0001 101 0100 0000 0000 0000 0000,写好看一点就是1100 0000 1101 0100 0000 0000 0000 0000。
在IEEE754标准下的浮点数的尾数实际上隐藏了最高位1的。也就是说,如果尾数是0000…,那么它表示的其实是1.0000…。因此,做浮点数计算和转化的时候需要注意一下。
IEEE754规定了几个特殊的浮点数:
NaN,阶码全1,尾数非全0;
无穷大,阶码全1,尾数全0;
非规格化,阶码全0,尾数非全0;
零,阶码全0,尾数全0。
为什么IEEE754的偏移量要为127(28-1 - 1)、1023(211-1 - 1)这种数,原因之一就是为了能够规定这4种特殊的浮点数。
先说尾数的舍入规则。因为这是精度问题的关键原因之一!!!
以单精度浮点数来说:
1、当尾数第24位为0时,直接保留前23位,不进位;
2、当尾数第24位为1,且尾数24位之后的位数不全为0时,保留前23位,之后尾数第23位加1(会影响到整个数,导致整个数改变);
3、当尾数第24位为1,且尾数24位之后的位数全为0时,如果尾数第23位为0,则直接保留前23位,不进位;
4、当尾数第24位为1,且尾数24位之后的位数全为0时,如果尾数第23位为1,则保留前23位,之后尾数第23位加1(会影响到整个数,导致整个数改变)。
以上4条规则中,第2条和第4条是经尝导致数据出现无法解释的异常的罪魁祸首。
实际上,9999.9995是不能精准转化为二进制的,因为0.9995不能用二进制小数来表示。
我们先把9999转化为二进制,即10 0111 0000 1111。整数部分一共14位,即规格化后需要占用尾数13位,也就是说作为一个单精度的float,还剩10位来表示它的小数部分。离0.9995最近的小数就是1111 1111 1101 1111 0011 1011…,这个数无限接近于0.9995。但实际上它只能保留10位,所以根据舍入规则10位以后的数就舍了。这样最后组成的float为0 1000 1100 001 1100 0011 1111 1111 1111,写好看点就是0100 0110 0001 1100 0011 1111 1111 1111。
而当数据为9999.9996时,离0.9996最近的小数是1111 1111 1110 0101 1100…,这个数无限接近于0.9996。根据舍入关系,这个是要进位的,也就是在尾数第23位上加1。0100 0110 0001 1100 0011 1111 1111 1111末尾加个1最终变成为0100 0110 0001 1100 0100 0000 0000 0000,而这个float写成整数形式就是二进制的10 0111 0001 0000也就是十进制的10000。
这也就解释了为什么:
Mathf.Floor(9999.9995f) = 9999
Mathf.Floor(9999.9996f) = 10000
再比如说,0.1f + 0.2f 不等于 0.3f。如果在C#中判断0.1f + 0.2f == 0.3f,编辑器会显示提示:Equality comparison of floating point numbers. Possible loss of precision while rounding values.
也就是说有可能会因为损失精度而产生未知的结果。
对于精度问题的解决方法,最简单的方式就是把单精度浮点数变为双精度浮点数。
在我们理解精度问题的底层原理之后,我们就可以有意识地避免精度问题发生。
总的来说,基础知识十分重要。当遇到各种莫名其妙的问题时,挖底层原理永远是解决问题的好方法。
因为本人也还在学习过程中,所以肯定有很多地方考虑不全,以及会有很多错误。希望有大佬看到能够及时指出,我会立刻进行改正,帮助我自己和大家共同进步!
有问题的小伙伴们可以在下方留言,大家可以一起讨论。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。