浮点数显示背后的谜团 [英] Mystery behind presentation of Floating Point numbers

查看:65
本文介绍了浮点数显示背后的谜团的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我当时正在为我的应用程序测试一些简单的解决方案,但遇到某种情况,我的脑海中浮现了问题……
为什么一个浮点数正确地以JSON表示(如我所期望的),而另一个一个不是...?

I was testing some simple solution for my app, and I ran into some case where question comes up in my head... "Why one floating number is represented in JSON correctly (as I expect) and other one not...?"

在这种情况下,从String转换为Decimal,然后转换为数字JSON: 98.39从人类的角度来看是完全可以预测的,但是数字: 98.40看起来不那么漂亮...

in this case conversion from String to Decimal and then to JSON of number: "98.39" is perfectly predictable from human point of view, but number: "98.40" doesn't look so beautiful...

我的问题是,有人可以向我解释一下吗,为什么从String转换为Decimal

And my question is, could someone explain please to me, why conversion from String to Decimal works as I expect for one floating number, but for another it is not.

我对浮点数错误有很多看法,但是我可以t弄清楚从
字符串-> ...基于二进制的转换内容...->到Double的过程在两种情况下的精度都不同。

I have red a lot about Floating Point number error, but I can't figure it out how the proces from String ->... binary based conversion stuff...-> to Double has different precision for both cases.

我的游乐场代码:

struct Price: Encodable {
    let amount: Decimal
}

func printJSON(from string: String) {
    let decimal = Decimal(string: string)!
    let price = Price(amount: decimal)

    //Encode Person Struct as Data
    let encodedData = try? JSONEncoder().encode(price)

    //Create JSON
    var json: Any?
    if let data = encodedData {
        json = try? JSONSerialization.jsonObject(with: data, options: [])
    }

    //Print JSON Object
    if let json = json {
        print("Person JSON:\n" + String(describing: json) + "\n")
    }
}

let stringPriceOK =     "98.39"
let stringPriceNotOK =  "98.40"
let stringPriceNotOK2 = "98.99"

printJSON(from: stringPriceOK)
printJSON(from: stringPriceNotOK)
printJSON(from: stringPriceNotOK2)
/*
 ------------------------------------------------
 // OUTPUT:
 Person JSON:
 {
 amount = "98.39";
 }

 Person JSON:
 {
 amount = "98.40000000000001";
 }

 Person JSON:
 {
 amount = "98.98999999999999";
 }
 ------------------------------------------------
 */

我一直在寻找/试图找出它逻辑单元执行了哪些步骤来转换:
98.39->小数->字符串-结果为 98.39
,并且链转换:
98.40->小数->字符串-结果为 98.40000000000001

I was looking/trying to figure it out what steps has been performed by the logical unit to convert: "98.39" -> Decimal -> String - with result of "98.39" and with the same chain of conversion: "98.40" -> Decimal -> String - with result of "98.40000000000001"

非常感谢所有回复!

推荐答案

这纯粹是 NSNumber 如何打印自身的人工产物。

This is purely an artifact of how an NSNumber prints itself.

JSONSerialization 在Objective-C中实现,并使用Objective-C对象( NSDictionary NSArray NSString NSNumber 等。 )表示从JSON反序列化的值。由于JSON包含一个带有小数点的裸数字作为金额 键的值,因此 JSONSerialization 对其进行解析作为 double 并包装在 NSNumber 中。

JSONSerialization is implemented in Objective-C and uses Objective-C objects (NSDictionary, NSArray, NSString, NSNumber, etc.) to represent the values it deserializes from your JSON. Since the JSON contains a bare number with decimal point as the value for the "amount" key, JSONSerialization parses it as a double and wraps it in an NSNumber.

每个Objective-C类实现了 description 方法来打印自身。

Each of these Objective-C classes implements a description method to print itself.

JSONSerialization NSDictionary String(describing:) NSDictionary 转换为 String 通过向其发送说明方法。 NSDictionary 通过向每个键发送 description 来实现 description 和值,包括金额 键的 NSNumber 值。

The object returned by JSONSerialization is an NSDictionary. String(describing:) converts the NSDictionary to a String by sending it the description method. NSDictionary implements description by sending description to each of its keys and values, including the NSNumber value for the "amount" key.

描述 NSNumber 实现格式为 double 值使用 printf 说明符%0.16g 。 (我使用反汇编程序进行了检查。)关于 g 说明符,C标准表示

The NSNumber implementation of description formats a double value using the printf specifier %0.16g. (I checked using a disassembler.) About the g specifier, the C standard says


除非使用#标志,否则将从结果的小数部分中删除所有结尾的零,如果没有剩余的小数部分,则删除小数点宽字符。

Finally, unless the # flag is used, any trailing zeros are removed from the fractional portion of the result and the decimal-point wide character is removed if there is no fractional portion remaining.

最接近98.39的两倍是98.3900 0000 0000 0005 6843 4188 6080 8014 8696 8994 140625。因此%0.16g 格式为%0.14f (请参阅标准为何为14,而不是16),它给出 98.39000000000000 ,然后将尾随零,给出 98.39

The closest double to 98.39 is exactly 98.3900 0000 0000 0005 6843 4188 6080 8014 8696 8994 1406 25. So %0.16g formats that as %0.14f (see the standard for why it's 14, not 16), which gives "98.39000000000000", then chops off the trailing zeros, giving "98.39".

最接近98.40的双精度正好是98.4000 0000 0000 0056 8434 1886 0808 0808 0148 6968 9941 4062 5.因此,%0.16g 格式为%0.14f ,从而得到。 98.40000000000001 (由于四舍五入),并且没有尾随的零可以切掉。

The closest double to 98.40 is exactly 98.4000 0000 0000 0056 8434 1886 0808 0148 6968 9941 4062 5. So %0.16g formats that as %0.14f, which gives "98.40000000000001" (because of rounding), and there are no trailing zeros to chop off.

所以这就是为什么,当打印 JSONSerialization.jsonObject(with:options:)的结果时,对于98.40,您会得到很多小数位,而对于98.39,您只会得到两位数。

So that's why, when you print the result of JSONSerialization.jsonObject(with:options:), you get lots of fractional digits for 98.40 but only two digits for 98.39.

如果从JSON对象中提取金额并将其转换为Swift的本机 Double 类型,然后打印那些 Double s,您得到的输出要短得多,因为 Double 实现了一种更智能的格式化算法,该算法可打印出最短的字符串,该字符串在被解析时会产生完全相同的 Double

If you extract the amounts from the JSON object and convert them to Swift's native Double type, and then print those Doubles, you get much shorter output, because Double implements a smarter formatting algorithm that prints the shortest string that, when parsed, produces exactly the same Double.

尝试一下:

import Foundation

struct Price: Encodable {
    let amount: Decimal
}

func printJSON(from string: String) {
    let decimal = Decimal(string: string)!
    let price = Price(amount: decimal)

    let data = try! JSONEncoder().encode(price)
    let jsonString = String(data: data, encoding: .utf8)!
    let jso = try! JSONSerialization.jsonObject(with: data, options: []) as! [String: Any]
    let nsNumber = jso["amount"] as! NSNumber
    let double = jso["amount"] as! Double

    print("""
    Original string: \(string)
        json: \(jsonString)
        jso: \(jso)
        amount as NSNumber: \(nsNumber)
        amount as Double: \(double)

    """)
}

printJSON(from: "98.39")
printJSON(from: "98.40")
printJSON(from: "98.99")

结果:

Original string: 98.39
    json: {"amount":98.39}
    jso: ["amount": 98.39]
    amount as NSNumber: 98.39
    amount as Double: 98.39

Original string: 98.40
    json: {"amount":98.4}
    jso: ["amount": 98.40000000000001]
    amount as NSNumber: 98.40000000000001
    amount as Double: 98.4

Original string: 98.99
    json: {"amount":98.99}
    jso: ["amount": 98.98999999999999]
    amount as NSNumber: 98.98999999999999
    amount as Double: 98.99

请注意,两者都是实际的JSON(标有<$ c $的行c> json:)和Swift Double 版本在所有情况下使用的位数最少。使用-[NSNumber描述] (标记为 jso:的行)为NSNumber的行: )使用多余的数字表示某些值。

Notice that both the actual JSON (on the lines labeled json:) and the Swift Double versions use the fewest digits in all cases. The lines that use -[NSNumber description] (labeled jso: and amount as NSNumber:) use extra digits for some values.

这篇关于浮点数显示背后的谜团的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆