为啥 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 使用NSDecimalNumber
(NSNumber
的子类)应该是我不想关心的实现细节。
该类被记录为有 38 位尾数,no 表示 38 位十进制整数。即使它被记录为表示 38 位十进制整数,也没有记录到保证转换为 long long int
,唯一的非继承访问器实际上是 doubleValue。
JSon 也没有定义为给你long long int
。它给你一个整数。在支持可以包含整数的不同类型的平台上,JSon 解析器可以为您提供其中任何一个,并且即使各种数字类型本身不支持相互转换,解析器也可以说是正确的。
不存在从NSDecimalInteger
到long 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 返回不正确的值的主要内容,如果未能解决你的问题,请参考以下文章
为啥 Frame.Width 和 Frame.Height 返回非常不正确的值