为啥 longLongValue 返回不正确的值

Posted

技术标签:

【中文标题】为啥 longLongValue 返回不正确的值【英文标题】:Why is longLongValue returning the incorrect value为什么 longLongValue 返回不正确的值 【发布时间】:2012-09-14 04:39:06 【问题描述】:

我有一个 NSDictionary,其中包含一个值为 4937446359977427944 的键。我尝试将它的值作为 long long 并返回 4937446359977427968?

NSLog(@"value1 = %@", [dict objectForKey"MyKey"]); // prints 4937446359977427944 

long long lv = [dict objectForKey:@"MyKey"] longLongValue];

NSLog(@"value2 = %lld", lv); // prints 4937446359977427968

在做:

NSLog(@"%lld", [@"4937446359977427944" longLongValue]); // prints 4937446359977427944

我假设这是某种舍入问题,因为低位似乎已被清除,我只是不知道如何阻止它(或为什么会发生)。

字典是使用 NSJSONSerialization 创建的,并且 JSON 对象确实(正确地)包含 "MyKey": 4937446359977427944 条目并且 dict 对象是正确的。

NSDictionary 中保存的值是 NSDecimalNumber

是否在幕后将某些东西转换为浮点数?

【问题讨论】:

您很可能想要关闭另一个 JSON 解析器... 我不认为它是 JSON 解析器,因为返回的 dict 使用 NSLog() 正确转储。含义:显示正确的值4937446359977427944。 这可以被隔离到一个测试用例没有 JSON 或 NSDictionary 吗?例如在 objectForKey 的结果中构造实际类型/值 .. 这些值真的不同吗?或者这是日志记录的产物?即在调试器中比较实际值,而不是依赖 NSLog... 是的,它们在调试器中与 NSLog() 输出相同。 【参考方案1】:

NSDecimalValue 不存储为double,它是一个 64 位无符号整数尾数、一个以 10 为底的 8 位有符号整数指数和一个符号位。

问题在于NSDecimalValue 的精确值只能表示为...NSDecimalValue

您可以使用doubleValue 方法获得一个近似 64 位的 IEE754 值。

当您尝试使用 longLongValue 时,您有效地获得了转换为近似 IEE754 值的 long long int 的结果。

您可能会也可能不会认为这是NSDecimalValue 实现中的错误(并最终提交雷达并要求Apple 使用不同的转换例程)。但严格来说,这不是一个错误:这是一个设计决策。

您应该将NSDecimalValue 视为一种浮点十进制。事实上,它非常类似于 IEEE754 所称的扩展精度浮点十进制数的软件实现,只是它不符合该定义(因为它没有支持至少值的指数介于 -6143 和 +6144 之间,因为它不支持 NAN 和无限)。

换句话说,它不是整数的扩展实现,而是双精度的扩展(但缺少 NAN 和无限)实现。 Apple 本身仅提供到 double 的近似转换(这意味着对于任何超过 53 位精度的值,到 long long int 的转换可能准确也可能不准确)这一事实不是错误。

您可能希望也可能不希望自己(使用类别)实施不同的转换。

另一种可能的观点是认为问题是您使用的 JSon 实现中的错误。但这也值得商榷:它给了你一个NSDecimalValue,这可以说是一个正确的表示。要么您使用NSDecimalValue 操作,要么 负责它的任何转换。

【讨论】:

我认为它是NSDecimalNumber 的错误,因为该类被记录为正确表示最多 38 位的十进制整数。我会假设 longLongValue 只要没有溢出就会返回正确的结果。但是对于十进制数d[d longLongValue] 似乎返回与(long long) [d doubleValue] 相同的值,而且只要使用超过 53 位,这当然是不精确的。 最后一点:我还希望我可以将 JSON 数据中的整数读入“long long”,只要没有溢出。 NSJSONSerialization 使用NSDecimalNumberNSNumber 的子类)应该是我不想关心的实现细节。 该类被记录为有 38 位尾数,no 表示 38 位十进制整数。即使它被记录为表示 38 位十进制整数,也没有记录到保证转换为 long long int,唯一的非继承访问器实际上是 doubleValue。 JSon 也没有定义为给你long long int。它给你一个整数。在支持可以包含整数的不同类型的平台上,JSon 解析器可以为您提供其中任何一个,并且即使各种数字类型本身不支持相互转换,解析器也可以说是正确的。 不存在从NSDecimalIntegerlong long int 的转换(通过double 除外)。这不是错误,而是设计决定。最多可以认为是缺少想要的功能,这与错误不同。【参考方案2】:

我不确定您是否对简单的解决方案感兴趣,或者只是想了解为什么会发生精度损失的细节。

如果您对简单的答案感兴趣:-[NSDecimalNumber description] 生成一个带有值的字符串,-[NSString longLongValue] 将字符串转换为 long long

NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithString:@"4937446359977427944"];
long long longLongNumber = [[decimalNumber description] longLongValue];
NSLog(@"decimalNumber %@ -- longLongNumber %lld", decimalNumber, longLongNumber);

输出

2014-04-16 08:51:21.221 APP_NAME[30458:60b] decimalNumber 4937446359977427944 -- longLongNumber 4937446359977427944

最后说明

[decimalNumber descriptionWithLocale:[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]] 可能更可靠,因为您的应用支持多种语言环境。

【讨论】:

我会说这是一个完美的时机:)。 也许你可以只使用number.stringValue.longLongValue 来获取long long 值,它不会被本地化。【参考方案3】:

对于任何有兴趣快速解决问题的人,根据 Analog File 正确答案:

long long someNumber = 8204064638523577098;
NSLog(@"some number lld:               %lld", someNumber);
NSNumber *snNSNumber = [NSNumber numberWithLongLong:someNumber];
NSLog(@"some number NSNumber:          %@", snNSNumber);
NSString *someJson = @"\"someValue\":8204064638523577098";
NSDictionary* dict = [NSJSONSerialization
 JSONObjectWithData:[someJson dataUsingEncoding:NSUTF8StringEncoding]
 options:0
 error:nil];
NSLog(@"Dict: %@", dict);
NSLog(@"Some digit out of dict:        %@", [dict objectForKey:@"someValue"]);
NSLog(@"Some digit out of dict as lld: %lld", [[dict objectForKey:@"someValue"] longLongValue]);
long long someNumberParsed;
sscanf([[[dict objectForKey:@"someValue"] stringValue] UTF8String], "%lld", &someNumberParsed);
NSLog(@"Properly parsed lld:           %lld", someNumberParsed);

结果:

2014-04-16 14:22:02.997 Tutorial4[97950:303] 一些数字 lld: 8204064638523577098

2014-04-16 14:22:02.998 Tutorial4[97950:303] 一些数字 NSNumber: 8204064638523577098

2014-04-16 14:22:02.998 Tutorial4[97950:303] 字典: someValue = 8204064638523577098;

2014-04-16 14:22:02.998 Tutorial4[97950:303] 一些数字超出字典: 8204064638523577098

2014-04-16 14:22:02.999 Tutorial4[97950:303] 一些数字超出 dict 为 lld: 8204064638523577344

2014-04-16 14:22:02.999 Tutorial4[97950:303] 正确解析 lld: 8204064638523577098

【讨论】:

以上是关于为啥 longLongValue 返回不正确的值的主要内容,如果未能解决你的问题,请参考以下文章

为啥以下代码从 NSData 返回不正确的值?

为啥指针不能传递正确的值(C)?

为啥 Frame.Width 和 Frame.Height 返回非常不正确的值

为啥这个递归函数返回正确的值? [复制]

为啥 const char* 返回值丢失了两个字符?但是在返回之前打印正确的值[重复]

MSSQL 2012 - 为啥 AVG 和 TRY_CONVERT 没有返回正确的值?