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

时间:2014-04-11 17:49:31

标签: ios objective-c nsnumber

我在double中有一个NSNumber

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。特别是我的问题的一个更好的解决方案是我在前一段中概述的内容,所以我没有采用这条路线。

4 个答案:

答案 0 :(得分:9)

正如上面的评论所述,double的精度约为16位小数 数字。 1363395572.612999有17位数字,并转换此十进制数字 double1363395572.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数据的“双精度”:

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

答案 1 :(得分:3)

使用NSDecimalNumber

NSDecimalNumber* dc = [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%.7f", myDouble]];

在字典中使用此十进制数字。


如果您精确地具有所需数字的字符串值,请将其直接提供给NSDecimalNumber构造函数以获取精确的十进制数。不要使用中间double阶段,否则会失去精确度。

答案 2 :(得分:1)

我刚试过,似乎精确度在序列化中丢失,而不是在数字的初始化中丢失。也许JSON序列化正在构建一个具有精确丢失数字格式的字符串,然后从中构建数据。

一种解决方案是将积分和小数值保存在自己的8字节字段中......也许使用两个LP64长。精度部分可以乘以大的东西,然后在检索时分开。

答案 3 :(得分:1)

我测试了以下代码:

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