浮点数的这些坑,你未必知道
作者:互联网
我猜作为开发工程师,大部分人都用过浮点数。但是你是否用对了呢?你是否知道,浮点数有近一半的值,在-1与+1以内呢?
本节大纲有:
1、基本数据类型
2、认识浮点数规律
3、我在浮点数上踩过的坑
在计算机的眼中,一切都是数字,一切都是二进制。
从以上数值计算可以看出,有近一半的浮点数值,在-1和1以内。
一、基本数据类型
关于数值,你应该时刻牢记在心里的三点, 1、每个基本类型的数值都是有范围限制的,不是无限大的。 2、无论是boolean,int,float,string,struct,object,最终都对应计算机的一个byte或者多个byte。 3、非必需,不要使用浮点数。 下面说几个在工作中会经常遇到的几个问题: 1、计算机中32位的有符号整型int,其最大值就是2147483647,这个数大约是21亿多。如果放普通计算应该都不成问题。但是,如果你数据库表的主键是int类型,那你就要小心了,好多大厂都在这个数据类型上面栽过跟头。因为现在中国网民基数太大,一张表很容易上亿,一旦超过21亿多,你的系统可能会因为db主键溢出导致故障。 2、在金融行业应该注意数值溢出的问题。两个integer相加或者相乘,其结果值很有可能超过integer的表示范围。在金融这种对精确度要求特别高的行业,这种错误更是不可忍受的。 3、返回给前端的json数据,尽量不要使用数值类型。如果你做过前端相关的项目,你会发现,当你使用json格式字符串给前端返回数值类型时,前端展示出来的数字可能跟你返回的数字完全不一样。其根本原因就是Javascript的浮点数没有办法精确表示较大数值。二、认识浮点数规律
如果你没有耐心去深入理解浮点数(说句实话,确实复杂),那么我建议你至少应该记住关于浮点数的几个规律和建议。- Javascript中的数字都是双精度浮点数。
- 在浮点数中,0有+0和-0两种表示方法
- 浮点数表示的值是不连续的,不均匀的。越大的数,越无法用浮点数表示出来。
- 有近一半的浮点数数值分布在-1和+1之间。
- 不要用浮点数来生成随机数。
- 不要用浮点数来存储或计算有关金钱和资产方面的一切,包括常见的会员积分。不能用浮点数用于任何需要精确值的运算,因为浮点数无法精确的表示数字,比如说0.1就无法用浮点数精确表示出来。
图一
上图列出的是正浮点数,即只列出了0及以上127个数值部分。 其中第二列为二进制表示。从上至下是连续增长的二进制,一共有128-8=120个状态值(01111000之后有8个状态值浮点数没有使用),但是因为是浮点数,所以其表示的值,需要经过一定计算规则才能得出,最后一列就是二进制对应十进制值。 8位浮点数,其所表示的数值范围是0-240。尽管其二进制只有120个状态值,但是其表示的最大值却达到了240,所以在240以内,一定有很多整数值,无法用8位浮点数表示。所以说,浮点数表示的值,不是连续的。如果将其能够表现出来的值,使用蓝点画出来,其所表示的数值大概如下:图二
图出自Computer Systems: A Programmer's Perspective。 从上图可以看出,浮点数表示的数值,靠近0中间最多,越靠外(数值越大),浮点数数值越少。比如从图一的表中可以看出,数值224-240之间的整数,都没有办法用8位的浮点数表示出来。所以说,越大的数,其用浮点数表示的概率越低。 另外,如果你利用浮点数小数乘以一个倍数来生成整数随机数,你会发现,你生成的随机数出现跟浮点数一样的规律,即生成的随机数大多数都集中靠近中心0的位置。所以,不要利用浮点数生成随机数。 我们放大-1至+1范围的数值图三
我们发现在1以内,像0.1,0.2等小数,也是没有办法用8位浮点数表示出来的。从这里也可以看出,浮点数的0有+0和-0之分。 我们使用8位浮点数和32位浮点,计算一下1以内和1以外的规格化数字的大概个数。因为64位太大,所以我这里就不再计算了,但是其数值个数比例应该是类似的。浮点数 | 8位浮点数 | 32位浮点数 |
1的二进制 | 0 0111 000 | 0 0111 1111 000 0000 0000 0000 0000 0000 |
1以内数值个数 | 56 | 1,065,353,216 |
最大规格数二进制 | 0 1110 111 | 0 1111 1110 111 1111 1111 1111 1111 1111 |
最大规格数表示的数值 | 119 | |
1以上二进制个数 | 119-56= 63 | 2,139,095,039 -1,065,353,216= 1,073,741,823 |
三、我在浮点数上踩过的坑
作为亲身经历者,我说两个案例。 第一个案例,用户返现金额是前端JS计算。我上面说过了,JS中的数值都是双精度浮点数,正好返现中用到了减法,于是就出现计算出的返现结果是1.199999999结果,然后直接传给了后端。但是钱的精度是不能超过2位小数的,后端直接将后面的9给截掉了,于是就返给客人的钱就少了1分钱。当然,这都是极少的情况,所以问题隐藏了很久。 第二个案例,后端用double类型来计算退款金额,当一笔交易多次退款的时候,有时出现钱不够退或者没有退完的情况。经过多次用户投诉,最终才修复这个问题; 浮点数使用不当的问题,通常情况很难发现。但是只要存在,就一定会出现,现在没有遇见,不代表将来也没有。迄今为止,为浮点数交的学费最贵的当属欧洲航空总署,因为浮点数转换不当,导致在1996年6月4号发射阿丽安娜5火箭爆炸,当时的火箭里面放着一个价值5亿美金的卫星。 国内也有好多一些工程师将遇见的问题分享了出来,随便百度了一个浮点数踩坑案例,https://blog.csdn.net/yangsh3002/article/details/52507097 如果你有时间,你可以运行如下Java代码,看看效果public class FloatTest { public static void main(String[] args) { double a = 1.0/10; double b = 1-0.9; System.out.println("1.0/10="+a); System.out.println("1-0.9="+b); System.out.println(a==b); } }
在我的电脑上输出如下
1.0/10=0.1 1-0.9=0.09999999999999998 false
相关阅读:
二进制 做支付遇到的httpclient大坑 参考资料: 浮点数表示_shuzfan的专栏-CSDN博客_浮点数 浮点数不能表示的最小正整数是? - 知乎 (zhihu.com) 浅谈JavaScript浮点数及其运算 - theWalker - 博客园 (cnblogs.com)标签:表示,二进制,32,浮点数,数值,1111,未必,知道 来源: https://www.cnblogs.com/donlianli/p/15510543.html