2016-02-19 60 views
1

我使用AlamoFire打电话给我的web服务:AlamoFire responseJSON小数精度

ApiManager.manager.request(.GET, webServiceCallUrl, parameters: ["id": 123]) 
     .validate() 
     .responseJSON { response in 
      switch response.result { 
      case .Success: 
       print(response.result.value!) 

       //... 

      case .Failure: 
       //... 
      } 
    } 

我的web服务返回以下JSON:

{ 
    //... 
    "InvoiceLines": [{ 
     "Amount": 0.94 
    }] 
} 

Alamofire的治疗这是一个双重的,而不是一个十进制,所以在输出控制台我得到:

{ 
    //... 
    InvoiceLines =  (
       { 
      Amount = "0.9399999999999999"; 
     } 
    ); 
} 

然后,这导致四舍五入误差进一步下降在我的代码。

我在服务器上使用Fiddler来检查Web服务JSON响应,以确认它返回0.94。因此,我可以排除服务器是问题,并怀疑responseJSON导致我的问题。

如何获取货币值作为正确的NSDecimalNumber值返回?


吉姆的答案/意见后

额外信息:

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章“数字”

什么你'描述为“十进制”是十进制浮点数,其中有一个整数和一个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) 
+0

感谢您的详细解释。我理解浮点背后的情况。我们在我们的应用程序中使用了NSDecimalNumber,它没有在其他地方引发问题 - 它不是一个可以将整个应用程序乘以整数值进行计算的选项。我可以简单地将NSDecimalNumber设置为我想要的格式,但是我已经做了一些进一步的测试,并且NSJSONSerialization似乎可以很好地返回0.93,0.95,0.96,0.97。它只是0.94,它显示为0.939999999999999,所以我试图理解为什么0.94的行为不同 – Joseph

+0

你真的不需要担心数字的内部表示,除非它实际上不够精确,无法匹配它本身。无论如何,你可以尝试所有这一切都直接在像游乐场: 'VAR一个= 0.93 // 0.93' '变种B = 0.94 // 0.93999999999' '变种C = 0.95 // 0.95' 它会只是内部的var转储程序错误地将一些数字转换为文本;可能对于那个数字的内部表示是0.93999999 ... 995,并且有一个小错误导致var转储处理程序向下舍入而不是向上。只要print()工作,您可以忽略它。 –

+0

问题在于print()以我目前使用它的方式提供了错误的值。在过去的2个小时里,我在操场上让自己感到沮丧,因此我在问题中增加了一些额外的代码。 – Joseph