【问题标题】:Swift 4: Strange Double and Float behaviourSwift 4:奇怪的 Double 和 Float 行为
【发布时间】:2018-01-22 22:23:58
【问题描述】:

基本上问题是在这个例子中:

let d1 = NSNumber(value: 1.4);
let d2 = d1.doubleValue;

let f1 = NSNumber(value: Float(1.4));
let f2 = d1.floatValue;

d1 结果 1.4
d2 结果 1.3999999999999999

f1 结果 1.4
f2 结果 1.3999999999999998

有人知道这是为什么吗?

我正在尝试解析 JSON 文件,例如:

{"name": "something", "version": 1.4}

使用以下代码:

let json = try (JSONSerialization.jsonObject(with: someData) as? [String: Any])!;
let version: Double = (json["version"] as! NSNumber).doubleValue;

let version: Double = json["version"] as! Double;

let version: Double = json["version"] as! Float;

而我就是无法获得 1.4...

四舍五入对我来说不是解决方案,因为我想将此数字写回 JSON 文件,该文件将被 由其他程序/语言解析,并且需要正好是 1.4在文件中。

有什么建议吗?

更新:问题只存在于 1.11.4没有问题 1.2, 1.3, 1.5

更新 2:序列化代码:

    let jsonDict: Dictionary<String,Any> = [
        "name" : name,
        "version" : version
    ];

    let data = try? JSONSerialization.data(withJSONObject: jsonDict, options: []);
    let jsonString = String(data:data, encoding:.utf8);

【问题讨论】:

  • 您是否尝试使用浮点对象来保存版本号?不要那样做。版本号不是数学数字。它们是标识字符串。将其作为字符串保存并操作。
  • 考虑将字符串转换为NSDecimalNumber
  • @DimitarAtanasov:不可能将“1.4”解析为二进制浮点中的 1.4,因为 1.4 不能用二进制浮点表示。如果您坚持使用数字格式进行版本标识,则必须使用十进制浮点格式。 (顺便说一句,你指向的 JSON 信息说“数字是一个十进制数字序列,没有多余的前导零。”它只定义了这个字符序列,而不是解释为数学数字或其他方式。它说“JSON不知道数字的语义。”)您可以查看 NSDecimal。
  • @DimitarAtanasov:1.5 可以精确表示。在 1.3 中,发生的舍入错误恰好可以解决。
  • 了解根本问题是成功的一半。我无法为解决方案做出贡献,因为我不熟悉 Swift。但我会重申@EricPostpischil 所说的 - JSON 文件中包含的所有内容都是一个字符串,如果您的程序正在读取或写入一个数字,那么某个地方正在转换它。您需要更严格地控​​制该转换。显然 Decimal 类型进行了不同的转换。

标签: macos floating-point double swift4


【解决方案1】:

好的,只是为了结束讨论。

最后Decimal 类型成功了。所以我将所有变量引用更改为DecimalNOT NSDecimalNumber,因为我收到错误,它不符合CodableDecodable 协议。也许有一个解决方法,但最简单的解决方案就是坚持使用Decimal

感谢@JamesBucanek 和@EricPostpischil 参与讨论并帮助解决此问题!!!

【讨论】:

  • 谢谢!另请注意,DoubleDecimal 初始化也会导致此问题。首先将Double 转换为String,例如Decimal(string: "1.4")Ref
【解决方案2】:

我也有同样的故事。

我需要使用 API 端点发送/接收浮点值。所以我听从了建议,将Double 更改为Decimal。它可以很好地编码数据,但不能用于解码。

let value = "0.0006"
let decimal = Decimal(string: value)!
print(decimal) // 0.0006 OK
let jsonData = try JSONEncoder().encode(decimal)
print(String(data: jsonData, encoding: .utf8)!) // 0.0006 OK
let decocdedDecimal = try JSONDecoder().decode(Decimal.self, from: jsonData)
print(decocdedDecimal) // 0.0005999999999999998976 NOOOOOO!!!

但是解码为Double 工作正常。

let decocdedDouble = try JSONDecoder().decode(Double.self, from: jsonData)
print(decocdedDouble) // 0.0006 OK

正如上面的答案中提到的那样 - Decimal 应该以 String 开头才能正确编码

let decimal = Decimal(0.0006)
print(decimal) // 0.0005999999999999998976, it will be encoded same way

当然Double 编码没有按预期工作,否则我们没有这样的问题。

// 0.00059999999999999995 for all cases ofcourse
print(String(data: try JSONEncoder().encode(0.0006), encoding: .utf8)!)
print(String(data: try JSONEncoder().encode(decocdedDouble), encoding: .utf8)!)
print(String(data: try JSONEncoder().encode(Double(value)!), encoding: .utf8)!)

所以我现在的肮脏解决方案是对Double 值使用包装器。它将值解码为Double,但编码为Decimal(string:)

struct CodableDouble: Codable {
    var value: Double
    
    init(_ value: Double) {
        self.value = value
    }
    
    init(from decoder: Decoder) throws {
        value = try decoder.singleValueContainer().decode(Double.self)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.singleValueContainer()
        let decimal = Decimal(string: "\(value)") ?? 0
        try container.encode(decimal)
    }
}

但我仍然不明白处理这个问题的正确方法是什么。 (除了不使用浮点值)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-07-10
    • 2016-01-28
    • 2019-01-23
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多