如何确定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));
}

结果:

标记iD=(空),交换iD=635168520811866143,长-长值=635168520811866143,doubleNumber=6.351685208118661e+17,doubleAsLL=635168520811866112,C类型=d,长=q

dict来自NSJSONSerialization,原始JSON源数据为"interchangeId":635168520811866143。该值的所有18位数字似乎都已在NSNumber中捕获,因此NSJSONSerialization不可能将其累加为double(限制为16位小数)。然而,objCType报告它是一个double

我们在NSNumber的文档中发现:"返回的类型不一定与创建接收器的方法匹配。"因此,显然这是一个"feechure"(即记录的错误)。

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

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

第一种,不利用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之间不能可靠地工作。

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

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

简单的答案:你不能。

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

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

获取NSNumber 的类型

什么';NSNumber可以存储的最大值是多少?

为什么longLongValue返回错误的值

NSJSONSerialization取消装箱NSNumber?

NSJSONSerializer返回:

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

一个NSDecimalNumber,用于具有19位或更多位的整数

双NSNumber,用于带小数或指数的数字

BOOL NSNumber用于true和false。

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

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

对于双NSNumbers。不幸的是,这也将匹配NSDecimalNumber,所以请先测试它。

好的——这不是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:{NSNumber*数字=@(strtod(标记,NULL));number.isDouble=YES[_delegate parserFoundNumber:number];[_state parser:self-shouldTransitionTo:tok];打破}

注意粗体行。。。这将把从JSON实数创建的数字标记为双精度。

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

HTH-

编辑

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

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

最新更新