Float Double 失精问题

2,069 阅读2分钟

结论

  • 浮点数计算不精确并不是bug,因为标准就是这样的。

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

  • 解决方法:对于需要精确结果的场景,别直接使用浮点数进行计算。使用Decimal类型。

先看个例子

print(Double(6.6) + Double(1.3))
print(Float(6.6) + Float(1.3))
print(Decimal(6.6) + Decimal(1.3))

// 输出: 
// 7.8999999999999995
// 7.8999996
// 7.9

从上面的结果我们可以看到 DoubleFloat 类型计算 6.6 + 1.3 的时候 结果会和我们预期不一样。
至于为什么会导致这种问题,经过翻阅一些文章和资料,我在下面话题给出答案。

为什么会导致这种情况么

要解释这个问题,就得从10进制与2进制的转换说起。

十进制数字在二进制中表示,众所周知,计算机做运算是使用二级制的。所以其实咱们在程序中做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
……

发现了吗?它乘不尽,是无限循环的……

而 swift 使用64位双精度浮点数存储数字,类似科学计数法,其中1位用来存储符号,11位用来存储指数值,52位用来存储尾数值(真正的数字),当计算的结果的二进制有效位数超过 52 位时,就会出现精度丢失的问题……

怎么处理

Swift中已经内置了Decimal类型,当计算金钱的时候建议使用Decimal来保证精度问题

print(Double(6.6) + Double(1.3))
print(Float(6.6) + Float(1.3))
print(Decimal(6.6) + Decimal(1.3))

// 7.8999999999999995
// 7.8999996
// 7.9

参考: www.ruanyifeng.com/blog/2010/0…