NSNumber使用舍入/截断的小数位数加倍

我有一个NSNumberdouble

 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个。

我的问题是为什么会发生这种情况,更重要的是如何阻止它发生?

编辑结论:

对于任何偶然发现这个问题的人来说,问题从一开始就不清楚,但实际问题是NSNumberdouble都无法保持我正在寻找的精确数字。 正如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 doubleNSDecimalNumber示例:

 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);