如何确定NSNumber的真实数据类型?

时间:2013-11-25 16:26:24

标签: ios objective-c json nsnumber

考虑以下代码:

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

结果:

  

Marker iD =(null),interchangeId = 635168520811866143,   long long value = 635168520811866143,doubleNumber = 6.351685208118661e + 17,   doubleAsLL = 635168520811866112,CType = d,longlong = q

dict来自NSJSONSerialization,原始JSON源数据为"interchangeId":635168520811866143。看起来该值的所有18位数都已在NSNumber中捕获,因此NSJSONSerialization可能无法将其累积为double(限制为16位十进制数)。然而,objCType报告它是double

我们在NSNumber的文档中找到了这个:“返回的类型不一定与创建接收器的方法匹配。”所以显然这是一个“蠢货”(即记录在案的bug)。

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

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

第一个,它没有利用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.

第二个应该更有效率,特别是因为它不需要创建一个新对象,但稍微令人担忧的是它依赖于NSDecimal的“未记录的行为/接口” - 字段的含义没有记录在任何地方(我可以找到),据说是“私人的”。

两者似乎都有效。

虽然稍微考虑一下 - 第二种方法存在一些“边界”问题,因为人们无法轻易调整限制以确保最大可能的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之间无法可靠地工作。

(但赏金仍然是公开的,以获得最佳建议或改进。)

5 个答案:

答案 0 :(得分:3)

简单回答:你不能。

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

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

get type of NSNumber

What's the largest value an NSNumber can store?

Why is longLongValue returning the incorrect value

NSJSONSerialization unboxes NSNumber?

答案 1 :(得分:3)

如NSDecimalNumber.h中所述,NSDecimalNumber总是返回"d"的返回类型。这是预期的行为。

- (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会被记录为返回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);
}

答案 2 :(得分:2)

NSJSONSerializer返回:

整数NSNumber,用于最多18位的整数

对于19位或更多位的整数的NSDecimalNumber

带有小数或指数的数字的双重NSNumber

一个BOOL NSNumber,用于true和false。

直接与全局变量kCFBooleanFalse和kCFBooleanTrue(拼写可能错误)进行比较以查找布尔值。检查isKindOfClass:[NSDecimalNumber class]的十进制数;这些实际上是整数。测试

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

表示双NSNumbers。不幸的是,这也会与NSDecimalNumber匹配,所以先测试一下。

答案 3 :(得分:0)

好的 - 这不是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的行。更改代码如下:

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

注意粗体行...这将标记从JSON real创建的数字作为double。

3。最后,您可以检查通过SBJSON

解码的数字对象的isDouble属性

HTH

修改

(当然,如果您愿意,可以对此进行概括并将添加的isDouble替换为通用类型指示符)

答案 4 :(得分:0)

if ([data isKindOfClass: [NSNumber class]]) {
           NSNumber *num = (NSNumber *)data;
           if (strcmp([data objCType], @encode(float)) == 0) {
               return [NSString stringWithFormat:@"%0.1f} ",num.floatValue];
           } else if (strcmp([data objCType], @encode(double)) == 0) {
               return [NSString stringWithFormat:@"%0.1f} ",num.doubleValue];
           } else if (strcmp([data objCType], @encode(int)) == 0) {
               return [NSString stringWithFormat:@"%d} ",num.intValue];
           } else if (strcmp([data objCType], @encode(BOOL)) == 0) {
               return  num.boolValue ? @"Yes} " : @"No} ";
           } else if (strcmp([data objCType], @encode(long)) == 0) {
               return [NSString stringWithFormat:@"%ld} ",num.longValue];
           }
       }