如何确定NSNumber的真实数据类型? [英] How to determine the true data type of an NSNumber?

查看:260
本文介绍了如何确定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

Have 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

dict is coming from NSJSONSerialization, and the original JSON source data is "interchangeId":635168520811866143. It appears that all 18 digits of the value have been captured in the NSNumber, so it could not possibly have been accumulated by NSJSONSerialization as a double (which is limited to 16 decimal digits). Yet, objCType is reporting that it's a double.

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

We find this in the documentation for NSNumber: "The returned type does not necessarily match the method the receiver was created with." So apparently this is a "feechure" (i.e., documented bug).

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

So how can I determine that this value originated as an integer and not a floating point value, so I can extract it correctly, with all the available precision? (Keep in mind that I have some other values that are legitimately floating-point, and I need to extract those accurately as well.)

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

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

The first, which does not make use of knowledge of 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结果 -

The second, which assumes that we only need worry about NSDecimalNumber objects, and can trust the CType results from regular NSNumbers --

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的未记录的行为/界面 - 字段的含义没有记录在任何地方(我能找到)并被称为私有。

The second should be more efficient, especially since it does not need to create a new object, but is slightly worrisome in that it depends on "undocumented behavior/interface" of NSDecimal -- the meanings of the fields are not documented anywhere (that I can find) and are said to be "private".

两者似乎都有效。

虽然有点考虑 - 第二种方法有一些边界问题,因为人们不能轻易调整限制以确保最大可能的64位二进制int将通过而不会丢失稍大的数字。

Though on thinking about it a bit -- The second approach has some "boundary" problems, since one can't readily adjust the limits to assure that the maximum possible 64-bit binary int will "pass" without risking loss of a slightly larger number.

相当令人难以置信,此方案在某些情况下失败:

Rather unbelievably, this scheme fails in some cases:

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

我没有保存该值,但有一个NSNumber基本上是不等于它自己 - 值显示相同但不注册为相等(并且确定该值起始于整数)。

I didn't save the value, but there is one for which the NSNumber will essentially be unequal to itself -- the values both display the same but do not register as equal (and it is certain that the value originated as an integer).

到目前为止,这似乎有效:

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之间无法可靠地工作。

Apparently isEqualToNumber does not work reliably between an NSNumber and an NSDecimalNumber.

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

(But the bounty is still open, for the best suggestion or improvement.)

推荐答案

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

As documented in NSDecimalNumber.h, NSDecimalNumber always returns "d" for it's return type. This is expected behavior.

- (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 被记录为如果转换是有损的。如果转换有损,或者遇到 NSDecimalNumber ,您将希望回退使用stringValue然后使用 sqlite3_bind_text 绑定它(并使用sqlite的列亲和力)。

CFNumberGetValue is documented to return false if the conversion was lossy. In the event of a lossy conversion, or when you encounter an NSDecimalNumber, you will want to fall back to using the stringValue and then use sqlite3_bind_text to bind it (and use sqlite's column affinity).

这样的事情:

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的真实数据类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆