考虑以下代码:
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];
}
}