AlamoFire responseJSON 十进制精度
Posted
技术标签:
【中文标题】AlamoFire responseJSON 十进制精度【英文标题】:AlamoFire responseJSON decimal precision 【发布时间】:2016-02-19 10:36:29 【问题描述】:我正在使用 AlamoFire 调用我的网络服务:
ApiManager.manager.request(.GET, webServiceCallUrl, parameters: ["id": 123])
.validate()
.responseJSON response in
switch response.result
case .Success:
print(response.result.value!)
//...
case .Failure:
//...
我的网络服务返回以下 JSON:
//...
"InvoiceLines": [
"Amount": 0.94
]
Alamofire 将其视为双精度而不是小数,因此在输出控制台中我得到:
//...
InvoiceLines = (
Amount = "0.9399999999999999";
);
这会导致我的代码进一步向下舍入错误。
我已在服务器上使用 Fiddler 检查 Web 服务 JSON 响应以确认它返回 0.94。因此,我可以排除服务器是问题并怀疑responseJSON
导致了我的问题。
如何让货币值返回正确的NSDecimalNumber
值?
Jim 回答/cmets 后的额外信息:
var stringToTest = "\"Amount\":0.94"
var data = stringToTest.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var object = try NSJSONSerialization.JSONObjectWithData(data!, options: opt)
var a = object["Amount"]
print(a) //"Optional(0.9399999999999999)"
var b = NSDecimalNumber(decimal: (object["Amount"] as! NSNumber).decimalValue)
print(b) //"0.9399999999999999"
如果我将值作为 JSON 中的字符串传递,则可以得到我想要的结果:
var stringToTest = "\"Amount\":\"0.94\""
var data = stringToTest.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
var object = try NSJSONSerialization.JSONObjectWithData(data!, options: opt)
var c = NSDecimalNumber(string: (object["Amount"] as! String))
print(c) //0.94
但是,我不想对 API 进行实施更改,因此需要一种使 JSON 保持相同格式的解决方案。每次都是我唯一的选择吗?这似乎是一种错误的做事方式,并可能在未来引发舍入问题。
【问题讨论】:
【参考方案1】:十进制值本身就是浮点数,JSON 规范特别允许您可能认为是“真实”浮点数的高指数值:
http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf 第 8 节“数字”
您所描述的“十进制”是十进制浮点数,其中您有一个整数和一个以 10 为底的指数,在这种情况下为 94 * 10^-2。很少有系统支持这种存储格式,而是使用二进制浮点数,即整数和 base2 指数。由于 2 不是 5 的因数,因此无法用二进制浮点数精确表示十分之一、百分之一等。这意味着内部表示总是有点偏差,但如果您尝试进行比较或打印,它会知道精度并且会按您预期的那样工作。
var bar = 0.94 // 0.939999999999999
bar == 0.94 // true
print(bar) // "0.94\n"
您在其他代码中遇到问题的地方将是由于复杂的精度问题。在十进制浮点中,如果你尝试做 1/3 + 1/3 - 2/3 你会得到 0.333333 + 0.333333 - 0.666667 = -0.000001 != 0,而在二进制浮点中你会得到同样的问题。这是因为经过一个或多个操作后,该值将超出从原始数字转换的精度范围,所以它是按字面意思取的。
如果您处于这种情况下,数据实际上是固定点(例如大多数情况下的货币值),最安全的方法是将其相乘,然后只使用整数值,直到您必须显示,如:
var bar = 0.94 * 100
var foo = bar * 5 // multiply price by 5
print(bar / 100)
另外,由于双精度浮点具有非常高的基精度,远远超过所需的 1/100,因此您可以在执行比较或输出时进行四舍五入。
var bar = 0.94
var foo = bar * 5 // multiply price by 5
print(round(bar*100) / 100)
【讨论】:
感谢您的详细解释。我了解浮点的幕后情况。我们在我们的应用程序中使用 NSDecimalNumber 并没有在其他地方引起问题 - 它不是更改整个应用程序以乘以整数值进行计算的选项。我可以简单地将 NSDecimalNumber 设置为我想要的格式,但是我已经做了一些进一步的测试,并且 NSJSONSerialization 似乎返回了 0.93、0.95、0.96、0.97 的罚款。它只是显示为 0.939999999999999 的 0.94,所以我试图了解为什么 0.94 的行为不同 您真的不需要担心数字的内部表示,除非它实际上不够精确以至于无法匹配自身。无论如何,您可以直接在操场上尝试所有这些,例如:var a = 0.93 // 0.93
var b = 0.94 // 0.93999999999
var c = 0.95 // 0.95
这只是内部 var dump 例程错误处理了某些数字的文本转换;可能对于那个数字,内部表示是 0.93999999...995 并且有一个小错误导致 var 转储处理程序向下舍入而不是向上舍入。只要 print() 有效,您就可以忽略它。
存在的问题是 print() 以我当前使用它的方式给出了错误的值。由于过去 2 小时在操场上感到沮丧,我在问题中添加了一些额外的代码。
乘除法。 print(NSDecimalNumber(double: round(0.94*100)).decimalNumberByDividingBy(100))
工作正常,而 print(NSDecimalNumber(double: 0.94))
不行。从结果的外观来看,可能存在一些类型推断问题。请再次注意,前者的内部转储不准确,但实际打印正常。以上是关于AlamoFire responseJSON 十进制精度的主要内容,如果未能解决你的问题,请参考以下文章
Alamofire 无法在 .responseJSON 中创建 throw
AlamoFire 与 Swift 1.2:“responseJSON”的模糊使用
Swift 2 - 如何从 Alamofire.responseJSON 获取数据