浮点数显示背后的谜团 [英] Mystery behind presentation of Floating Point numbers
问题描述
我当时正在为我的应用程序测试一些简单的解决方案,但遇到某种情况,我的脑海中浮现了问题……
为什么一个浮点数正确地以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 $返回的对象c $ c>是
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 Double
s, 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屋!