解析时 JSONObjectWithData 小数位错误

Posted

技术标签:

【中文标题】解析时 JSONObjectWithData 小数位错误【英文标题】:JSONObjectWithData wrong decimal places while parsing 【发布时间】:2016-03-25 11:20:35 【问题描述】:

在我的一个 api 中,我得到下面的响应表单服务器(如果将数据转换为字符串)

"success":true,"order_id":102232,"Total":68.6,"delivery_time":"7:30PM-10:00PM","delivery_date":"2016-03-25"

但是当我使用相同的

[NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];

它的回报


Total = "68.59999999999999";
"delivery_date" = "2016-03-25";
"delivery_time" = "7:30PM-10:00PM";
"order_id" = 102232;
success = 1;

由于总字段中小数点后有多个位,因此在计算中产生了问题。

我认为 JSON 序列化方法在内部转换它但不知道如何解决这个问题

【问题讨论】:

你通过哪一个作为阅读选项? 有时浮点数在类型转换时不够精确。在您需要确切的值之前,它不会损害您的程序。当你圆了事情,你会在这里得到你想要的。 我无法四舍五入,因为我需要将其发送回服务器,它将在那里得到验证,因此单精度会扭曲特征 请通读floating-point-gui.de,这是完全正确的行为。当您向用户展示号码时,只需使用NSNumberFormatter ***.com/questions/23019338/… 参考这个 【参考方案1】:

@gnasher 暗示了这个问题,但没有详细解释。

计算机内部使用二进制浮点数来表示浮点数。

浮点数通常存储为浮点数或双精度数。在大多数 64 位平台上,浮点数以 4 个字节存储,双精度数以 8 个字节存储。

二进制浮点不能精确地表示许多十进制值。在内部,68.6 以二进制表示为十进制的 68.59999999999999 的值。这是计算机在将您的值保存为浮点数时可以达到的最接近 68.6。

像浮点数和双精度数这样的二进制浮点值没有“小数位数”。该值被转换为十进制以进行显示或转换为字符串,并且在该转换中总是可能会出现舍入错误。

以这段代码为例:

float aFloat = 68.6;
NSLog(@"aFloat = %f", aFloat);

显示:

aFloat = 68.599998

但是,这段代码:

NSLog(@"aFloat (1 decimal place)= %.1f", aFloat);

显示:

aFloat (1 decimal place)= 68.6

您正在解析的 JSON 字符串中包含不在引号中的数字。当您将 JSON 字符串转换为 Cocoa 对象时,“Total”的值会保存为 NSNumber 对象,内部可能使用双精度类型。

如果您需要对“总计”值进行数学运算,则需要将其设为数字​​。

如果您想避免十进制转换错误,请更改您的服务器代码以将值“Total”作为字符串(用引号括起来)而不是数字值发送。然后JSONObjectWithData函数会将其保存为字符串,"68.6"

在这种情况下,您的 JSON 字符串将如下所示:

"success":true,"order_id":102232,"Total":"68.6","delivery_time":"7:30PM-10:00PM","delivery_date":"2016-03-25"

您说“由于总字段中有多个小数位,因此在计算中产生了问题。”什么问题?在 ios 端,还是在服务器端?

【讨论】:

在 iOS 上,JSON 解析器将所有数字转换为 NSNumber(双精度)实际上是一个大问题,即使是我们希望保留为 NSDecimalNumber 的数字也是如此。替代解析器(例如SBJson)也有同样的问题。有一次我想正确解析,我不得不派生一个解析器并修复行为。 听起来 Apple 需要增强 NSJSONSerialization 以添加一个选项,该选项将为 JSON 字符串中包含的十进制值创建 NSDecimalNumber 对象。您是否发布了您的自定义 JSON 解析器? @DuncanC 是的,这将是正确的解决方案。与NSNumberFormatter 相同,它也将NSDecimalNumber 转换为double(也只能由专有格式化程序修复)。不幸的是,我更新的解析器无法发布(公司代码)。它是SBJson 的第一个版本的一个分支,实际上修复非常简单。 (顺便说一句,javascript解析器也有同样的问题,也转换成双精度)。 我不确定这是一个“大问题”,因为NSJSONSerialization 的 99.99% 的消费者会想要普通的 C 原始数字,而其他人不一定会想要满意NSDecimalNumberNSDecimalNumber 是一个 64 位浮点数,与 double 完全一样,只是恰好有一个以 10 为底的指数,而不是以 2 为底的指数。如果你想“正确解析”,你需要一个可变大小的数字存储容器。我可以在 JSON 中编码无数个数字,但不能存储为 NSDecimalNumber @Tommy,JSON 将数字作为十进制数字字符串发送。在 JSON 和本机数据格式之间来回切换会强制在数字的二进制和十进制表示之间进行转换,这可能会导致舍入错误。提供使用 NSDecimalNumber 的选项将是一个很好的解决方案,可以保留数字的十进制值。【参考方案2】:

这解决了我的问题

NSNumberFormatter *fmt = [[NSNumberFormatter alloc] init];
[fmt setPositiveFormat:@"0.##"];
NSLog(@"%@", [fmt stringFromNumber:dataDict[@"Total"]]);

此日志的输出为 68.6

【讨论】:

【参考方案3】:

Swift 解析时遇到同样的问题,后端团队发送十进制。只需将其转换为浮动即可。

let value = Float(48.9999999999)
print(value) // 49.0

let value = Float(48.9799999999)
print(value) // 48.98

【讨论】:

【参考方案4】:

您正在使用浮点运算。你所看到的完全正常。如果您刚刚分配了 68.6,您将发生完全相同的事情。

你如何解决这个问题?通过学习浮点运算。但你实际看到了什么问题?您是否只是因为看到大量 9 而感到恐慌,还是存在实际问题?

是什么让您真正认为将数据转换回 JSON 会产生服务器误解的内容?你试过了吗?您是担心理论上的可能性,还是有问题的实际证据?显然,服务器存储了一些四舍五入到小数点后的内容。

我的意思是说你真的知道你担心的是 NSLog 语句的输出吗?

【讨论】:

问题是它将 .6 转换为 .599999999,当我将值发送回服务器时它会失败,因为现在我拥有的值是 .59999 而不是 .6 @iOSRocks 在这种情况下,问题出在您的服务器上。有一些方法可以解决这个问题,例如使用NSDecimalNumber 保存数字,使用自定义的 JSON 解析器和编码器,但修复服务器要容易得多。 同样的事情在 android 上运行良好,所以我们无法更改服务器端代码

以上是关于解析时 JSONObjectWithData 小数位错误的主要内容,如果未能解决你的问题,请参考以下文章

Swift 2 - NSJSONSerialization.JSONObjectWithData 处理错误 [重复]

调用 JSONSerialization.JSONObjectWithData 时调用中的额外参数 [重复]

我的 JSONObjectWithData 为空

nsjsonserialization.jsonobjectwithdata 截断接收到的数据

JSONObjectWithData方法里options參数选择解释

NSJSONSerialization JSONObjectWithData 其中数据包含换行符