如何确定一个NSNumber的真正的数据types?

考虑这个代码:

NSNumber* interchangeId = dict[@"interchangeMarkerLogId"]; long long llValue = [interchangeId longLongValue]; double dValue = [interchangeId doubleValue]; NSNumber* doubleId = [NSNumber numberWithDouble:dValue]; long long llDouble = [doubleId longLongValue]; if (llValue > 1000000) { NSLog(@"Have Marker iD = %@, interchangeId = %@, long long value = %lld, doubleNumber = %@, doubleAsLL = %lld, CType = %s, longlong = %s", self.iD, interchangeId, llValue, doubleId, llDouble, [interchangeId objCType], @encode(long long)); } 

结果:

具有标记iD =(空),互换ID = 635168520811866143,长长值= 635168520811866143,doubleNumber = 6.351685208118661e + 17,doubleAsLL = 635168520811866112,CType = d,longlong = q

dict来自NSJSONSerialization,而原始的JSON源数据是"interchangeId":635168520811866143 。 看起来这个值的所有18个数字都被捕获到了NSNumber中,所以它不可能被NSJSONSerialization作为一个double (限制在16个十进制数字)。 然而,objCType报告说这是double

我们在NSNumber的文档中find这个:“返回的types不一定匹配接收者创build的方法。 所以显然这是一个“非”(即有logging的错误)。

那么我怎样才能确定这个值起源于一个整数,而不是一个浮点值,所以我可以正确提取它,所有可用的精度? (请记住,我还有其他一些合法的浮点值,我也需要精确地提取这些值。)

到目前为止,我已经提出了两个解决scheme:

第一个,没有利用NSDecimalNumber的知识 –

 NSString* numberString = [obj stringValue]; BOOL fixed = YES; for (int i = 0; i < numberString.length; i++) { unichar theChar = [numberString characterAtIndex:i]; if (theChar != '-' && (theChar < '0' || theChar > '9')) { fixed = NO; break; } } 

第二,假设我们只需要担心NSDecimalNumber对象,并且可以信任常规NSNumbers的CType结果 –

 if ([obj isKindOfClass:[NSDecimalNumber class]]) { // Need to determine if integer or floating-point. NSDecimalNumber is a subclass of NSNumber, but it always reports it's type as double. NSDecimal decimalStruct = [obj decimalValue]; // The decimal value is usually "compact", so may have a positive exponent even if integer (due to trailing zeros). "Length" is expressed in terms of 4-digit halfwords. if (decimalStruct._exponent >= 0 && decimalStruct._exponent + 4 * decimalStruct._length < 20) { sqlite3_bind_int64(pStmt, idx, [obj longLongValue]); } else { sqlite3_bind_double(pStmt, idx, [obj doubleValue]); } } else ... handle regular NSNumber by testing CType. 

第二个应该更有效率,特别是因为它不需要创build一个新的对象,但有点令人担忧,因为它依赖于NSDecimal的“无证行为/接口” – 字段的含义没有logging在任何地方(即I可以find),并被认为是“私人”。

两者都似乎工作。

虽然有一点思考 –第二种方法有一些“边界”的问题,因为不能很容易地调整限制,以确保最大可能的64位二进制int将“通过”,而没有损失一个稍大数。

令人难以置信的是 ,这个计划在某些情况下失败了:

 BOOL fixed = NO; long long llValue = [obj longLongValue]; NSNumber* testNumber = [[NSNumber alloc] initWithLongLong:llValue]; if ([testNumber isEqualToNumber:obj]) { fixed = YES; } 

我没有保存的值,但有一个NSNumber将本质上是不等于自己 – 值都显示相同,但​​不注册为平等(并确保该值始发于整数)。

到目前为止,这似乎工作:

 BOOL fixed = NO; if ([obj isKindOfClass:[NSNumber class]]) { long long llValue = [obj longLongValue]; NSNumber* testNumber = [[[obj class] alloc] initWithLongLong:llValue]; if ([testNumber isEqualToNumber:obj]) { fixed = YES; } } 

显然isEqualToNumber不能在NSNumber和NSDecimalNumber之间可靠地工作。

(但是,赏金仍然是开放的,最好的build议或改善。)

简单的回答:你不能。

为了做你所要求的,你需要自己跟踪确切的types。 NSNumber更像是一个“愚蠢的”包装,因为它可以帮助您以更客观的方式使用标准数字(如Obj-C对象)。 只使用NSNumber, -objCType是唯一的方法。 如果你想要另一种方式,你必须自己做。

以下是可能有所帮助的其他一些讨论:

获取NSNumber的types

什么是NSNumber可以存储的最大值?

为什么longLongValue返回不正确的值

NSJSONSerialization unboxes NSNumber?

如NSDecimalNumber.h中所logging, NSDecimalNumber总是返回"d"因为它是返回types。 这是预期的行为。

 - (const char *)objCType NS_RETURNS_INNER_POINTER; // return 'd' for double 

而且在开发者文档中:

 Returns a C string containing the Objective-C type of the data contained in the receiver, which for an NSDecimalNumber object is always “d” (for double). 

如果转换是有损的,则CFNumberGetValue被logging为返回false。 在发生有损转换的情况下,或遇到NSDecimalNumber ,您将希望退回到使用stringValue,然后使用sqlite3_bind_text绑定它(并使用sqlite的列关联)。

像这样的东西:

 NSNumber *number = ... BOOL ok = NO; if (![number isKindOfClass:[NSDecimalNumber class]]) { CFNumberType numberType = CFNumberGetType(number); if (numberType == kCFNumberFloat32Type || numberType == kCFNumberFloat64Type || numberType == kCFNumberCGFloatType) { double value; ok = CFNumberGetValue(number, kCFNumberFloat64Type, &value); if (ok) { ok = (sqlite3_bind_double(pStmt, idx, value) == SQLITE_OK); } } else { SInt64 value; ok = CFNumberGetValue(number, kCFNumberSInt64Type, &value); if (ok) { ok = (sqlite3_bind_int64(pStmt, idx, value) == SQLITE_OK); } } } // We had an NSDecimalNumber, or the conversion via CFNumberGetValue() was lossy. if (!ok) { NSString *stringValue = [number stringValue]; ok = (sqlite3_bind_text(pStmt, idx, [stringValue UTF8String], -1, SQLITE_TRANSIENT) == SQLITE_OK); } 

NSJSONSerializer返回:

整数NSNumber最多18位数字的整数

具有19位或更多位数的整数的NSDecimalNumber

用小数或指数的数字的双NSNumber

布尔NSNumber为真和假。

直接比较全局variableskCFBooleanFalse和kCFBooleanTrue(拼写可能是错误的)来查找布尔值。 检查isKindOfClass:十进制数字的[NSDecimalNumber类]; 这些实际上是整数。 testing

 strcmp (number.objCType, @encode (double)) == 0 

为双重NSNumbers。 这将不幸地匹配NSDecimalNumber,所以先testing一下。

好吧 – 这不是100%的理想,但你添加一点点的代码SBJSON达到你想要的。

1.首先,将NSNumber + SBJson添加到SBJSON项目中:

的NSNumber + SBJson.h

 @interface NSNumber (SBJson) @property ( nonatomic ) BOOL isDouble ; @end 

的NSNumber + SBJson.m

 #import "NSNumber+SBJSON.h" #import <objc/runtime.h> @implementation NSNumber (SBJson) static const char * kIsDoubleKey = "kIsDoubleKey" ; -(void)setIsDouble:(BOOL)b { objc_setAssociatedObject( self, kIsDoubleKey, [ NSNumber numberWithBool:b ], OBJC_ASSOCIATION_RETAIN_NONATOMIC ) ; } -(BOOL)isDouble { return [ objc_getAssociatedObject( self, kIsDoubleKey ) boolValue ] ; } @end 

2.现在,在SBJson4StreamParser.m中sbjson4_token_real处理sbjson4_token_real的行。 更改代码如下:

 案例sbjson4_token_real:{
     NSNumber * number = @(strtod(token,NULL));
     number.isDouble = YES;
     [_delegate parserFoundNumber:number];
     [_state parser:self shouldTransitionTo:tok];
    打破;
 } 

注意粗线 …这将标记一个从JSON实际创build的数字作为double。

3.最后,您可以检查通过SBJSON解码的数字对象的isDouble属性

HTH

编辑

(当然,如果你愿意的话,你可以概括这个,并用一个通用types指示符replace添加的isDouble