gpt4 book ai didi

json - Swift JSONEncoder 数字四舍五入

转载 作者:行者123 更新时间:2023-12-03 16:20:46 31 4
gpt4 key购买 nike

与所有 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
这是一个很好理解的世界现实,因此不需要通过包含指向浮点精度损失背景文章链接的评论来解决。
使用内置 JSONEncoder 编码此数字时, 我们看:
  4> String(data: JSONEncoder().encode([4.7]), encoding: .utf8) 
$R2: String? = "[4.7000000000000002]"
这不是错误的,正如维基百科所说 this关于 JSON 和浮点数:

The JSON standard makes no requirements regarding implementation details such as overflow, underflow, loss of precision, rounding, or signed zeros, but it does recommend to expect no more than IEEE 754 binary64 precision for "good interoperability". There is no inherent precision loss in serializing a machine-level binary representation of a floating-point number (like binary64) into a human-readable decimal representation (like numbers in JSON), and back, since there exist published algorithms to do this exactly and optimally.


但是,其他 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 的数字编码以将 double 序列化为它们的四舍五入等效项,而不是放弃自动合成可编码性并手动重新实现整个类型图的编码?

最佳答案

您可以扩展 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)
}

为了使数据大小尽可能小,您可以将 Decimal 编码和解码为字符串:
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)
}

这将打印

{"decimal":"0.007"}
0.007

关于json - Swift JSONEncoder 数字四舍五入,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62995804/

31 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com