PreciseDecimal

CI status

简介

Swift 的 Decimal 类型长期以来存在一个问题:明显的精度损失

这发生在所有常见的初始化方式中

let badDecimal = Decimal(3.133) // 3.132999999999999488
let badDecimal: Decimal = 3.133 // 3.132999999999999488

但以下方式不会

let goodDecimal = Decimal(string: "3.133") // 3.133
let goodDecimal = Decimal(sign: .plus, exponent: -3, significand: 3133) // 3.133

此外,这同样适用于 JSON 解码,因为它在底层使用了 NSJSONSerialization,据推测,它会将十进制数字解析为 Double,然后通过其有损的 Double 初始化器来初始化 Decimal,如上例所示。一个常见的解决方法是将敏感的 Decimal 值作为字符串接收,并使用可用的字符串初始化器解析为 Decimal,但通常 JSON 有效负载的格式不在您的控制范围内。

苹果很可能会在某个时候修复这个问题。在此期间,PreciseDecimal 将为您提供支持。

用法

这个库声明了一个轻量级的 PreciseDecimal 类型,作为 Decimal 的一个包装器,具有精确的 initDecodable 实现。

let goodDecimal = PreciseDecimal(3.133) // 3.133
let goodDecimal: PreciseDecimal = 3.133 // 3.133
struct Price: Decodable {
    let amount: PreciseDecimal
}

let json = #"{ "amount": 3.133 }"#.data(using: .utf8)!
let goodDecimal = try JSONDecoder().decode(Price.self, from: json).amount // 3.133

常见问题解答

这个解决方案是万无一失的吗?

不是。

PreciseDecimal 在处理非常高精度的数字时会失效。例如:

let a = PreciseDecimal(  1234567890.0123456789 )
let b = Decimal(string: "1234567890.0123456789")!
print(a) // 1234567890.0123458
print(b) // 1234567890.0123456789

因此,如果您要处理超过 6 位小数的数字,则此库不适合您。相反,目前最好的解决方案是将小数表示为字符串,尤其是在 JSON 序列化时。

只有苹果才能在语言中引入真正的 Decimal 字面量,并修复 Foundation 中的 JSON 序列化机制。

为什么不将其设为 @propertyWrapper

因为很容易忘记注解属性,尤其是因为没有任何编译器检查或测试来确保它提供的行为的微小变化,从而导致日后出现隐蔽的错误。

为什么它没有 [x] 功能?

为了尽可能保持库的范围和实现的轻量级,乐观地认为一旦 Apple 修复 Decimal,它就会很快被淘汰。

如果您认为我遗漏了一个绝对应该包含在此库中的重要功能,请随时提出建议

苹果会修复这个问题吗?

我不知道。

但是,在对现状不满意的方面,您并不孤单。以下是两个您可以投票支持的相关问题,以提高苹果看到它们的几率: