浮点精度问题是怎么产生的
对于小数的运算,相信大家都有遇到过精度丢失问题,利于0.1+0.2得到的是0.30000000000000004而不是0.3,那么如何解释为什么计算机中 0.2 + 0.1 不等于 0.3 呢?在剖析这个问题之前我们要先理解IEEE754标准。
什么是IEEE754?下面是一段官方的解释了解即可,重点是我们要关注IEEE754存储格式。
概念:IEEE二进制浮点数算术标准(IEEE754)是20世纪80年代以来最广泛使用的浮点数运算标准,为许多CPU与浮点运算器所采用。这个标准定义了表示浮点数的格式(包括负零-0)与反常值(denormal number),一些特殊数值(无穷∞与非数值NaN),以及这些数值的“浮点数运算符”。 常见的四种浮点数值表示方式:单精确度(32位)、双精确度(64位)、延伸单精确度(43比特以上,很少使用)与延伸双精确度(79比特以上,通常以80位实现)。C语言的float通常是指IEEE单精确度,而double是指双精确度。
存储格式:IEEE 754标准准确地定义了单精度和双精度浮点格式
单精度浮点格式(32 位)。
双精度浮点格式(64 位)。
EEE754 标准中规定 float 单精度浮点数在机器中表示用 1 位表示数字的符号,用 8 位表示指数,用 23 位表示尾数,即小数部分。对于 double 双精度浮点数,用 1 位表示符号,用 11 位表示指数,52 位表示尾数,其中指数域称为阶码。IEEE754 浮点数的格式如下图所示。
下面以0.1和0.2为例将其转换为IEEE754的格式存储到内存中
0.1这个浮点数转换成一个二进制数0.00011001100110011...
0.2这个浮点数转换成一个二进制数0.0011001100110011...
这两个浮点数无法精确转换成一个二进制数,由于在内存中表示精度有限必须舍弃后面的尾数部分。
单精度为例0.1和0.2转换为IEEE754格式以如下
0.1 : 0 01111011 10011001100110011001100
0.2: 0 01111100 10011001100110011001100
所以我们看到0.1+0.2得到的是0.300000004而不是0.3的结论,而造成这个精度问题的根本原因在于不是所有的数字(如例子中的0.1和0.2)都可以用二进制表示而进行截断,造成精度丢失。
但是尽管如此我们还是有办法在运算是避免精度问题,那就是BigDecimal,那么BigDecimal是怎么解决这个问题的呢?
解决方案就是不使用用二进制,而是使用十进制(BigInteger)+小数点位置(scale)来表示小数,所有的十进制数字都可以用这种形式来表示,比如 0.1 =1*10^-1 scale=1
BigDecimal 运算分成两部分 ,BigInteger部分和更新scale即可,具体有兴趣的同学可以翻翻BigDecimal 的源码,在这里不再展开了。