【发布时间】:2020-11-09 17:30:08
【问题描述】:
与所有 IEEE 7540 系统一样,在 Swift 中,4.7 之类的数字被视为4.7000000000000002 之类的值。所以也就不足为奇了:
% swift
Welcome to Apple Swift version 5.2.4 (swiftlang-1103.0.32.9 clang-1103.0.32.53).
Type :help for assistance.
1> 4.7
$R0: Double = 4.7000000000000002
2> 4.7 == 4.7000000000000002
$R1: Bool = true
这是一个众所周知的现实,因此不需要使用包含指向浮点精度损失的背景文章链接的 cmets 来解决。
当使用内置的JSONEncoder 编码这个数字时,我们看到:
4> String(data: JSONEncoder().encode([4.7]), encoding: .utf8)
$R2: String? = "[4.7000000000000002]"
这并没有错,正如 Wikipedia 所说的 this 关于 JSON 和浮点数:
JSON 标准对上溢、下溢、精度损失、舍入或有符号零等实现细节没有任何要求,但它确实建议期望不超过 IEEE 754 binary64 精度以实现“良好的互操作性”。将浮点数的机器级二进制表示(如 binary64)序列化为人类可读的十进制表示(如 JSON 中的数字)并返回,并没有固有的精度损失,因为存在已发布的算法可以准确地做到这一点并且是最优的。
但是,其他 JavaScript 环境倾向于对这些数字进行四舍五入。例如。使用 JavaScriptCore:
% /System/Library/Frameworks/JavaScriptCore.framework/Versions/A/Helpers/jsc
>>> 4.7 == 4.7000000000000002
true
>>> JSON.stringify([4.7000000000000002])
[4.7]
与节点:
% node
Welcome to Node.js v13.13.0.
Type ".help" for more information.
> 4.7 == 4.7000000000000002
true
> JSON.stringify([4.7000000000000002])
'[4.7]'
对我来说,问题是我有大量 Swift doubles 集合,当序列化为 JSON 用于存储和/或传输时,包含很多不必要的箔条(“4.7000000000000002”的字符比“4.7”多 6 倍),因此大大增加了序列化数据的大小。
谁能想到一个很好的方法来覆盖 Swift 的数字编码以将双精度数序列化为它们的舍入等效值,而不是放弃自动合成可编码性并手动重新实现整个类型图的编码?
【问题讨论】:
-
你能用
Decimal代替Double吗? -
这里有类似的观察:stackoverflow.com/q/46271842/1187415.
-
@MartinR 你很快就会失去
Decimal精度和JSONDecoder/JSONDecoder,因为两者实际上都在后台使用JSONSerialization,它只能将浮点数解析为@ 987654338@,它无法处理Decimal。见SR-7054。 -
@DávidPásztor stackoverflow.com/q/58322521/2303865
-
@LeoDabus 是的,这不是一项简单的任务,因为您几乎需要创建自己的
Encoder和Decoder来支持 JSON 而不依赖于JSONSerialization。我想苹果本身仍然没有解决这个问题是有原因的,即使他们已经知道这个问题至少 2.5 年了。