AlamoFire responseJSON十进制精度

时间:2016-02-19 10:36:29

标签: json swift alamofire nsdecimalnumber

我使用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回答/评论后的额外信息:

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 个答案:

答案 0 :(得分:2)

十进制值本身就是浮点数,而JSON规范特别允许使用高指数值,您可以将其视为" real"浮点:

http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf第8节"数字"

您所描述的"十进制"是十进制浮点数,其中你有一个整数和一个base10指数,在这种情况下是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)