与所有IEEE7540系统一样,像4.7这样的Swift中的数字被视为像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这是一个被充分理解的世界现实,因此不需要用包含关于浮点精度损失的背景文章的链接的评论来处理。
当使用内置JSONEncoder对这个数字进行编码时,我们看到:
4> String(data: JSONEncoder().encode([4.7]), encoding: .utf8)
$R2: String? = "[4.7000000000000002]"这并不是不正确的,因为维基百科说这关于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加倍集合,当序列化到JSON以进行存储和/或传输时,包含大量不必要的箔条("4.7000000000000002“比”4.7“多6倍),从而大大增大了序列化数据的大小。
有没有人能想到一种很好的方法来覆盖Swift的数字编码,将双数序列化为它们的圆形等价物,而不是放弃自动合成可编码性和手动重新实现整个类型图的编码?
发布于 2020-07-20 14:45:29
您可以扩展KeyedEncodingContainer和KeyedDecodingContainer,并实现自定义编码和解码方法,以将Decimal作为普通数据发送。您只需将编解码器dataEncodingStrategy设置为deferredToData即可。另一种可能是对其base64Data进行编码和解码,或者将其编码/解码为普通字符串。
extension Numeric {
var data: Data {
var bytes = self
return .init(bytes: &bytes, count: MemoryLayout<Self>.size)
}
}extension DataProtocol {
func decode<T: Numeric>(_ codingPath: [CodingKey], key: CodingKey) throws -> T {
var value: T = .zero
guard withUnsafeMutableBytes(of: &value, copyBytes) == MemoryLayout.size(ofValue: value) else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "The key \(key) could not be converted to a numeric value: \(Array(self))"))
}
return value
}
}extension KeyedEncodingContainer {
mutating func encode(_ value: Decimal, forKey key: K) throws {
try encode(value.data, forKey: key)
}
mutating func encodeIfPresent(_ value: Decimal?, forKey key: K) throws {
guard let value = value else { return }
try encode(value, forKey: key)
}
}extension KeyedDecodingContainer {
func decode(_ type: Decimal.Type, forKey key: K) throws -> Decimal {
try decode(Data.self, forKey: key).decode(codingPath, key: key)
}
func decodeIfPresent(_ type: Decimal.Type, forKey key: K) throws -> Decimal? {
try decodeIfPresent(Data.self, forKey: key)?.decode(codingPath, key: key)
}
}操场测试:
struct Root: Codable {
let decimal: Decimal
}
// using the string initializer for decimal is required to maintain precision
let root = Root(decimal: Decimal(string: "0.007")!)
do {
let encoder = JSONEncoder()
encoder.dataEncodingStrategy = .deferredToData
let rootData = try encoder.encode(root)
let decoder = JSONDecoder()
decoder.dataDecodingStrategy = .deferredToData
let root = try decoder.decode(Root.self, from: rootData)
print(root.decimal) // prints "0.007\n" instead of "0.007000000000000001024\n" without the custom encoding and decoding methods
} catch {
print(error)
}为了尽可能地保持数据大小,可以将十进制编码和解码为字符串:
extension String {
func decimal(_ codingPath: [CodingKey], key: CodingKey) throws -> Decimal {
guard let decimal = Decimal(string: self) else {
throw DecodingError.dataCorrupted(.init(codingPath: codingPath, debugDescription: "The key \(key) could not be converted to decimal: \(self)"))
}
return decimal
}
}extension KeyedEncodingContainer {
mutating func encode(_ value: Decimal, forKey key: K) throws {
try encode(String(describing: value), forKey: key)
}
mutating func encodeIfPresent(_ value: Decimal?, forKey key: K) throws {
guard let value = value else { return }
try encode(value, forKey: key)
}
}extension KeyedDecodingContainer {
func decode(_ type: Decimal.Type, forKey key: K) throws -> Decimal {
try decode(String.self, forKey: key).decimal(codingPath, key: key)
}
func decodeIfPresent(_ type: Decimal.Type, forKey key: K) throws -> Decimal? {
try decodeIfPresent(String.self, forKey: key)?.decimal(codingPath, key: key)
}
}操场测试:
struct StringDecimal: Codable {
let decimal: Decimal
}
let root = StringDecimal(decimal: Decimal(string: "0.007")!)
do {
let stringDecimalData = try JSONEncoder().encode(root)
print(String(data: stringDecimalData, encoding: .utf8)!)
let stringDecimal = try JSONDecoder().decode(StringDecimal.self, from: stringDecimalData)
print(stringDecimal.decimal) // "0.007\n"
} catch {
print(error)
}这个会打印出来
{“十进制”:“0.007”} 0.007
https://stackoverflow.com/questions/62995804
复制相似问题