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.93var b = 0.94 // 0.93999999999var 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

Alamofire 无法在 .responseJSON 中创建 throw

AlamoFire 与 Swift 1.2:“responseJSON”的模糊使用

Swift 2 - 如何从 Alamofire.responseJSON 获取数据

Alamofire中的responseJSON json解析

未调用 responseJSON 的 Alamofire completionHandler