Swift 中如何避免精度丢失

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Swift 中如何避免精度丢失相关的知识,希望对你有一定的参考价值。

参考技术A 如果你开发过涉及金额计算的 ios app, 那么你很有可能经历过在使用浮点型数字时精度丢失的问题

让我们来看看为什么会丢失以及如何解决吧

这里我不想系统地讲解浮点型是如何由基数尾数指数组成的, 直接说原因: 因为用二进制能表示的以 2 为底的指数必然是 2 的倍数, 也就是说只能为 0.5 , 0.25 , 0.125 ... 以此类推, 那么我们就可以发现无论将这些数字怎么组合, 都不可能达到 0.3 这个值, 因此计算机这个时候会给我们一个最接近 0.3 且恰好是这些数字之和的一个近似值.

因此, 对于精度丢失我们可以得出如下结论:

上面我们简单的解释了为什么会丢失精度, 那么精度丢失对我们在什么时候有影响呢?

根据我的经验, 我认为主要场景集中如下:

所以, 精度丢失并不可怕 (起码出现的场景很少). 下面让我们看下如何才能在我们真的遇到了精度丢失问题时候进行解决

NSDecimalNumber 是 NSNumber 的一个子类, 比 NSNumber 的功能更为强大, 四舍五入, 取整, 输入后自动去掉数值前面无用的 0 等等. 由于 NSDecimalNumber 精度较高, 所以会比基本数据类型费时, 所以需要权衡考虑, 苹果官方建议在货币以及要求精度很高的场景下使用.

通常情况下我们会使用 NSDecimalNumberHandler 这个格式化器对其需要约束的格式进行设置, 然后构建出需要的 NSDecimalNumber

NSDecimalNumber 与 Decimal 基本是无缝桥接的, Decimal 是一个值类型 Struct , NSDecimalNumber 是一个引用类型 Class , 看起来 NSDecimalNumber 的设置功能更为丰富, 但是如果只是需要对位数, 四舍五入方式有要求的话 Decimal 也完全可以满足, 而且性能会更好, 所以我认为 NSDecimalNumber 仅在 Decimal 无法实现某个功能时才作为备用考虑.

总的来说, NSDecimalNumber 与 Decimal 的关系类似 NSString 与 String 的关系.

当我们声明一个 Decimal 属性后, 然后使用一个 json 字符串对其进行赋值, 我们会发现精度仍然丢失了, 为什么会有这样的结果呢?

答案是简单的: 我们使用的 JSONDecoder() 内部使用了 JSONSerialization() 进行反序列化, 其逻辑非常简单, 在碰到 9021.234891 这个数字时, 其会毫不犹豫的将其看做 Double 类型, 然后再将 Double 转为 Decimal 是可以成功的, 但是这个时候已经是精度丢失的 Double 了, 转换得来的 Decimal 类型自然也是精度丢失的.

对于这个问题, 我们必须要能够控制其反序列化过程. 我现在的选择方案是使用 ObjectMapper , 其可以使用自定义规则灵活控制序列化与反序列化的过程.

ObjectMapper 默认情况下是不支持 Decimal 的, 我们可以自定义一个支持 Decimal 类型的 TransformType , 如下:

然后将此 TransformType 应用于我们需要转换的属性上

Decimal 有多种初始化方式, 我们可以传入整型值, 传入浮点型, 传入字符串方式进行初始化, 我认为正确的初始化方式应该是使用字符串.

上面这张图应该很简单明了的说明了我为什么这么认为了. 其原因与上个反序列问题相似, 也是因为我们传入 Double 时, Swift 对其进行了一次承载, 这一次承载就对其造成了精度丢失, 根据已经丢失精度的 Double 初始化出 Decimal , 这个 Decimal 是精度丢失的也就不难理解了

以上是关于Swift 中如何避免精度丢失的主要内容,如果未能解决你的问题,请参考以下文章

BigDecimal精度丢失问题

Java 避免精度丢失之BigDecimal 运算

JAVA 中出现double 及float计算丢失精度的问题,大家是怎么避免的

Uva1639(概率期望/对数处理避免丢失精度)

JSON中非常大的ID,如何在不丢失精度的情况下获取它

iOS数据解析精度丢失