金融类 App 中的数字精度问题

在交易市场中,一分一厘都是很重要的,要是开发的时候来个向上向下取整或者四舍五入就会造成纠纷。因此在开发金融类 app 时对数字的精度要求是至关重要的。

而众所周知,计算机中的浮点数计算是不精确的,比如在 Swift 中 print(0.1 + 0.2) 这句代码执行的结果是 0.30000000000000004 而不是 0.3。产生此种结果的原因此处不再赘述,只说解决的方案。

NSDecimalNumber

NSDecimalNumber 可以用来处理的浮点数的精度问题。它可以使用 Double 类型或者字符串来进行初始化,支持基本的四则运算。对应的方法如下:

  • 加法:`adding(_:)
  • 减法:subtracting(_:)
  • 乘法:multiplying(by:)
  • 除法:dividing(by:)
    当然,还有其它的一些用法,但是此处只列举常用的一些方法。
1
2
3
4
let numberA = NSDecimalNumber(value: 0.1)
let numberB = NSDecimalNumber(value: 0.2)
let result = numberA.adding(numberB)
print(result)

这下再运行,可以看到输出结果为 0.3

如果是购买东西,假如单价是1.99,数量是2.5,那么结果是4.975,我们要保留两位小数,这时候就要引入 behavior 了。
还是上面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
let behavior = NSDecimalNumberHandler(
    roundingMode: .bankers,
    scale: 2,
    raiseOnExactness: false,
    raiseOnOverflow: false,
    raiseOnUnderflow: false,
    raiseOnDivideByZero: true)
   
let numberA = NSDecimalNumber(value: 1.99)
let numberB = NSDecimalNumber(value: 2.5)
let result = numberA.multiplying(by: numberB, withBehavior: behavior)
print(result)

此时输出的结果为 4.98。
此段代码中在两个数相乘时添加了一个 behavior 参数,而 behavior 是一个 NSDecimalNumberHandle 类型。 在这里主要看前两个参数。
第一个是确定小数保留的类型,一共有如下几种:

  • NSRoundPlain,四舍五入
  • NSRoundDown,只舍不入
  • NSRoundUp,只入不舍
  • NSRoundBankers,四舍六入,当为5时,确保舍去之后的小数最后一位为偶数
    具体的示例如下图表所示:
    精度设置

第二个参数则是设置保留的小数位数,但是不会自动补零,如果需要补零则需要在转为字符串时进行格式化后显示:

1
print(String(format: "%.2f", result.doubleValue))