浮点数为什么算不准

0.1 + 0.2 #=> 0.30000000000000004
1.2 - 1.0 #=> 0.19999999999999996

结论

使用了 IEEE 754 标准来存储浮点类型的任何编程语言(C/C++/C#/Java 等等)都存在精度丢失问题。

  • 浮点数计算不精确并不是bug,因为标准就是这样的。
  • 原因简单来说是这样:2进制的小数无法精确的表达10进制小数,计算机在计算10进制小数的过程中要先转换为2进制进行计算,这个过程中出现了误差。
  • 使用 BigDecimal 来计算

为什么不算准?

在程序中做10进制运算,都是要转换为2进制再进行计算的。10进制整数转换为2进制的方法可能大家都知道:

除以2,取余数部分,商继续除以2,得到0为止,将余数逆序排列
例如:
22 / 2 11 余 0
11 / 2 5 余 1
5 / 2 2 余 1
2 / 2 1 余 0
1 / 2 0 余 1
所以22的的二进制是10110

那10进制小数转换为2进制的方法呢?

乘以2,取整,小数部分继续乘以2,取整数部分,得到小数部分0为止,将整数顺序排列
0.8125 x 2 1.625 取整数部分 1
0.625 x 2 1.25 取整数部分 1
0.25 x 2 0.5 取整数部分 0
0.5 x 2 1.0 取整数部分 1
所以0.8125的二进制是0.1101

那么问题就来了,比如你想计算 10 进制 0.2 的2进制:

0.2 x 2 0.4
0.4 x 2 0.8
0.8 x 2 1.6
0.6 x 2 1.2
0.2 x 2 0.4
...

它乘不尽,是无限循环的。多数语言都是使用 64 位双精度浮点数存储数字,类似科学计数法,其中 1 位用来存储符号,11 位用来存储指数值,52 位用来存储尾数值(真正的数字),当计算的结果的二进制有效位数超过 52 位时,就会出现精度丢失的问题。

如何解决这个问题?

使用 Ruby 自带的 BigDecimal 标准库。

require 'bigdecimal'
sum = BigDecimal("0")
10_000.times do
  sum = sum + BigDecimal("0.0001")
end
print sum #=> 0.1E1

(BigDecimal("1.2") - BigDecimal("1.0")) == BigDecimal("0.2") #=> true
(1.2 - 1.0) == 0.2 #=> false

原文

如果觉得我的文章对您有用,请在支付宝公益平台找个项目捐点钱。 @Victor Mar 16, 2018

奉献爱心