考虑以下代码:
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之间无法可靠地工作。
(但赏金仍然是公开的,以获得最佳建议或改进。)
答案 0 :(得分:3)
简单回答:你不能。
为了做你要求的事情,你需要自己跟踪确切的类型。 NSNumber更像是一个“哑”包装器,因为它可以帮助您以更客观的方式使用标准数字(作为Obj-C对象)。仅使用NSNumber,-objCType
是您唯一的方法。如果你想要另一种方式,你必须自己做。
以下是其他可能有所帮助的讨论:
What's the largest value an NSNumber can store?
答案 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中添加了一些代码来实现你想要的目标。
的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
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。
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];
}
}