NSNumber使用舍入/截断的小数位数加倍
我有一个NSNumber
的double
。
double myDouble = 1363395572.6129999; NSNumber *doubleNumber = @(myDouble); // using [NSNumber numberWithDouble:myDouble] leads to the same result
这是问题所在。
doubleNumber.doubleValue
似乎返回正确的值和完整值(1363395572.6129999)
但是,在调试器中查看doubleNumber
或者执行doubleNumber.description
会给我(1363395572.613)
。
我会理解,如果这只是一些显示格式,但是当我将这个对象粘贴到JSON有效载荷中时,会插入混乱的圆形值而不是实际的数字。
我这样做的方式是这样的:
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:(Dictionary containing NSNumber) options:0 error:nil]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
此时查看字符串会显示截断的数字,其中包含3个小数位,即使我插入的NSNumber
有7个。
我的问题是为什么会发生这种情况,更重要的是如何阻止它发生?
编辑结论:
对于任何偶然发现这个问题的人来说,问题从一开始就不清楚,但实际问题是NSNumber
和double
都无法保持我正在寻找的精确数字。 正如Martin的回答所示,一旦我从JSON响应中反序列化初始数值,就会出现问题。
我最终通过重新处理整个系统来解决我的问题,取决于客户端上这些数字的精度级别(因为这些是时间戳,微秒),而是使用不同的标识符来传递API。
正如Martin和Leo指出的那样,为了解决这个问题,需要使用一个自定义JSON解析器,它允许将JSON号解析为NSDecimalNumber
而不是NSNumber
。 特别是我问题的一个更好的解决方案是我在上一段中概述的内容,所以我没有采用这种方法。
正如上面的注释中所说, double
的精度约为16位十进制数。 1363395572.612999
有17位数,并且将此十进制数转换为double
可得到与1363395572.613
完全相同的结果:
double myDouble = 1363395572.6129999; double myDouble1 = 1363395572.613; NSLog(@"%.20f", myDouble); // 1363395572.61299991607666015625 NSLog(@"%.20f", myDouble1); // 1363395572.61299991607666015625 NSLog(@"%s", myDouble == myDouble1 ? "equal" : "different"); // equal
因此,在精度为double
,输出1363395572.613
是正确的 。
如果你的目标是准确发送数字“1363395572.6129999”,那么你不能先将它存储在double
因为它已经失去了精度。 一种可能的解决方案是使用NSDecimalNumber
(精度为38位十进制数):
NSDecimalNumber *doubleNumber = [NSDecimalNumber decimalNumberWithString:@"1363395572.6129999"]; NSDictionary *dict = @{@"key": doubleNumber}; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; // {"key":1363395572.6129999}
long double
和NSDecimalNumber
示例:
long double ld1 = 1363395572.6129999L; long double ld2 = 1363395572.613L; NSDecimalNumber *num1 = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%.7Lf", ld1]]; NSDecimalNumber *num2 = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%.7Lf", ld2]]; NSDictionary *dict = @{@"key1": num1, @"key2": num2}; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil]; NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; // {"key1":1363395572.6129999,"key2":1363395572.613}
更新:正如在讨论中发现的那样,当从服务器发送的JSON对象读取数据时,问题就出现了。 以下示例显示NSJSONSerialization
无法从JSON数据中读取具有超过“double”精度的浮点数:
NSString *jsonString = @"{\"key1\":1363395572.6129999,\"key2\":1363395572.613}"; NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding]; NSDictionary *dict2 = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL]; NSNumber *n1 = dict2[@"key1"]; NSNumber *n2 = dict2[@"key2"]; BOOL b = [n1 isEqualTo:n2]; // YES
使用NSDecimalNumber
:
NSDecimalNumber* dc = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%.7f", myDouble]];
在字典中使用此十进制数字。
如果您精确地具有所需数字的字符串值,请将其直接提供给NSDecimalNumber
构造函数以获取精确的十进制数。 不要使用中间double
级,否则会失去精度。
我只是尝试过,似乎精确度在序列化中丢失,而不是在数字的初始化中丢失。 也许JSON序列化正在构建一个具有精确丢失数字格式的字符串,然后从中构建数据。
一种解决方案是将积分和小数值保存在它们自己的8字节字段中……也许使用两个LP64长。 精度部分可以乘以大的东西,然后在检索时分割。
我测试了以下代码:
double myDouble = 1363395572.6129999; NSString *s = [NSString stringWithFormat:@"%.7f", myDouble]; NSLog(@"%@", s);
输出是:
1363395572.6129999
所以只需在放入JSON对象之前自己格式化字符串,你应该没问题。
编辑:如果你想要更高的精度,而不仅仅是对JSON中的内容的更多控制,一个long double
将存储它。 从而:
long double myDouble = 1363395572.6129999; NSString *s = [NSString stringWithFormat:@"%.7L", myDouble]; NSLog(@"%@", s);