NSNumber object of type long - ios

I'm facing a wierd problem with NSNumber,When i run the following code,
NSNumber *num = [NSNumber numberWithLong:1000];
const char* type =[num objCType];
if (strcmp (type, #encode (long)) == 0) {
NSLog(#"Type is long");
}else if(strcmp (type, #encode (int)) == 0){
NSLog(#"Type is int");
}
The output is :
Type is int
Edit: I tried [NSNumber numberWithLong:1000L] and [NSNumber numberWithLong:1000000000]
But still the same output.
Why is the type int here??

The answer is simple... the documentation for objCType says:
Special Considerations
The returned type does not necessarily match the method the receiver
was created with.

This is like asking a stringWithInt method to return a string that still knows it is an int. You create an NSNumber, you get an NSNumber.

Related

Large NSNumber to NSString conversion [duplicate]

Consider this code:
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));
}
The results:
Have Marker iD = (null), interchangeId = 635168520811866143,
long long value = 635168520811866143, doubleNumber = 6.351685208118661e+17,
doubleAsLL = 635168520811866112, CType = d, longlong = q
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.
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.)
I've come up with two solutions so far:
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;
}
}
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.
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".
Both appear to work.
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;
}
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).
This appears to work, so far:
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;
}
}
Apparently isEqualToNumber does not work reliably between an NSNumber and an NSDecimalNumber.
(But the bounty is still open, for the best suggestion or improvement.)
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
And also in the Developer Docs:
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 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).
Something like this:
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);
}
Simple answer: You can't.
In order to do what you're asking, you'll need to keep track of the exact type on your own. NSNumber is more of a "dumb" wrapper in that it helps you use standard numbers in a more objective way (as Obj-C objects). Using solely NSNumber, -objCType is your only way. If you want another way, you'd have to do it on your own.
Here are some other discussions that may be of help:
get type of NSNumber
What's the largest value an NSNumber can store?
Why is longLongValue returning the incorrect value
NSJSONSerialization unboxes NSNumber?
NSJSONSerializer returns:
an integer NSNumber for integers up to 18 digits
an NSDecimalNumber for integers with 19 or more digits
a double NSNumber for numbers with decimals or exponent
a BOOL NSNumber for true and false.
Compare directly with the global variables kCFBooleanFalse and kCFBooleanTrue (spelling might be wrong) to find booleans. Check isKindOfClass:[NSDecimalNumber class] for decimal numbers; these are actually integers. Test
strcmp (number.objCType, #encode (double)) == 0
for double NSNumbers. This will unfortunately match NSDecimalNumber as well, so test that first.
Ok--It's not 100% ideal, but you add a little bit of code to SBJSON to achieve what you want.
1. First, add NSNumber+SBJson to the SBJSON project:
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. Now, find the line in SBJson4StreamParser.m where sbjson4_token_real is handled. Change the code as follows:
case sbjson4_token_real: {
NSNumber * number = #(strtod(token, NULL)) ;
number.isDouble = YES ;
[_delegate parserFoundNumber:number ];
[_state parser:self shouldTransitionTo:tok];
break;
}
note the bold line... this will mark a number created from a JSON real as a double.
3. Finally, you can check the isDouble property on your number objects decoded via SBJSON
HTH
edit:
(Of course you could generalize this and replace the added isDouble with a generic type indicator if you like)
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];
}
}

Resolve numbers and bools in nested NSDictionary

Let's say I loaded a JSON string into an NSDictionary that had some numbers written as strings. The resulting NSDictionary might look something like this:
NSDictionary* example = #{
#"aNumber": #"1",
#"aFloat": #"2.9708",
#"aBool": #"true",
#"aNestedDict": #{
#"more": #"220",
#"evenMore": #"false",
#"anArray": #[
#"1",
#"2"
]
}
};
I want to parse the float, integer, and bool ('true', 'false', 'yes', 'no' - case insensitive) values into their respective Objective-c class types. I've looked around, but can't find any examples of built in APIs to do this.
(Enlarged since people aren't reading the question)
Am I stuck writing a recursive parser and converting each value manually, or does Apple offer a built-in API to recursively parse it for me?
There isn't an API to do it, however you can make a helper function to figure it out. The API that apple does provide however are helper functions on NSString, i.e.: .integerValue, .doubleValue, .boolValue. However not only is this limited to NSString, it's also not comprehensive / intelligent.
So if you want to parse the string into a variable of type BOOL you can do something as simple as:
- (NSNumber *)parseBool:(NSString *)value
{
if( [value caseInsensitiveCompare:#"yes"] == NSOrderedSame || [value caseInsensitiveCompare:#"true"] == NSOrderedSame )
{
return #YES;
} else if ([value caseInsensitiveCompare:#"no"] == NSOrderedSame || [value caseInsensitiveCompare:#"false"] == NSOrderedSame )
{
return #NO;
} else
{
return nil;
}
}
EDIT:
For int and double just use:
NSString *string = #"1";
NSInteger intValue = string.integerValue;
double doubleValue = string.doubleValue;
JSON supports strings, numbers with and without decimals, boolean values, null values, dictionaries and arrays. So anyone wanting to represent numbers and boolean values in JSON can just do that.
Anyone producing JSON should document what they are producing. So if they insist on representing a boolean value as a string, then they should document which possible strings will be used to represent true and false. And then it's just a matter of string comparison.
For numbers stored as string, you can use integerValue or doubleValue which works just fine for strings.

Comparing in objective C - Implicit conversion of 'int' to 'id' is disallowed with ARC

I i'm getting the error "Implicit conversion of 'int' to 'id' is disallowed with ARC" at the line marked with "faulty line". I guess it have something to do with that i'm checking for an integer in an array, that contains objects instead of integers.
#import "RandomGenerator.h"
#implementation RandomGenerator
NSMutableArray *drawnNumbers;
-(int) randomNumber:(int)upperNumber {
return arc4random_uniform(upperNumber);
}
-(NSMutableArray*) lotteryNumbers :(int)withMaximumDrawnNumbers :(int)andHighestNumber {
for (int i = 1; i <= withMaximumDrawnNumbers; i++)
{
int drawnNumber = [self randomNumber:andHighestNumber];
if ([drawnNumbers containsObject:drawnNumber]) { //faulty line
//foo
}
}
return drawnNumbers;
}
#end
NSArrays can only contain objective-c objects. So actually the method containsObject: is expecting an object, not an int or any other primitive type.
If you want to store number inside an NSArray you should pack them into NSNumber objects.
NSNumber *someNumber = [NSNumber numberWithInt:3];
In your case, if we assume that drawnNumbers is already an array of NSNumbers, you should change the randomNumber: generation to:
-(NSNumber*) randomNumber:(int)upperNumber {
return [NSNumber numberWithInt:arc4random_uniform(upperNumber)];
}
And then when picking it up on the lotteryNumbers method, you should:
NSNumber *drawnNumber = [self randomNumber:andHighestNumber];
Another note would go for the method you defined for lotteryNumbers. You used a really strange name for it, I think you misunderstood how the method naming works in objective-c. You were probably looking for something more like:
-(NSMutableArray*) lotteryNumbersWithMaximumDrawnNumbers:(int)maximumDrawnNumbers andHighestNumber:(int)highestNumber;
Late edit:
Objective-C now allows a way more compact syntax for creating NSNumbers. You can do it like:
NSNumber *someNumber = #(3);
And your method could be rewritten as:
-(NSNumber*) randomNumber:(int)upperNumber {
return #(arc4random_uniform(upperNumber));
}
You are using an int where an object (presumably NSNumber) is expected. So convert before use:
if ([drawnNumbers containsObject:#( drawnNumber )])

How to determine the true data type of an NSNumber?

Consider this code:
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));
}
The results:
Have Marker iD = (null), interchangeId = 635168520811866143,
long long value = 635168520811866143, doubleNumber = 6.351685208118661e+17,
doubleAsLL = 635168520811866112, CType = d, longlong = q
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.
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.)
I've come up with two solutions so far:
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;
}
}
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.
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".
Both appear to work.
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;
}
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).
This appears to work, so far:
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;
}
}
Apparently isEqualToNumber does not work reliably between an NSNumber and an NSDecimalNumber.
(But the bounty is still open, for the best suggestion or improvement.)
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
And also in the Developer Docs:
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 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).
Something like this:
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);
}
Simple answer: You can't.
In order to do what you're asking, you'll need to keep track of the exact type on your own. NSNumber is more of a "dumb" wrapper in that it helps you use standard numbers in a more objective way (as Obj-C objects). Using solely NSNumber, -objCType is your only way. If you want another way, you'd have to do it on your own.
Here are some other discussions that may be of help:
get type of NSNumber
What's the largest value an NSNumber can store?
Why is longLongValue returning the incorrect value
NSJSONSerialization unboxes NSNumber?
NSJSONSerializer returns:
an integer NSNumber for integers up to 18 digits
an NSDecimalNumber for integers with 19 or more digits
a double NSNumber for numbers with decimals or exponent
a BOOL NSNumber for true and false.
Compare directly with the global variables kCFBooleanFalse and kCFBooleanTrue (spelling might be wrong) to find booleans. Check isKindOfClass:[NSDecimalNumber class] for decimal numbers; these are actually integers. Test
strcmp (number.objCType, #encode (double)) == 0
for double NSNumbers. This will unfortunately match NSDecimalNumber as well, so test that first.
Ok--It's not 100% ideal, but you add a little bit of code to SBJSON to achieve what you want.
1. First, add NSNumber+SBJson to the SBJSON project:
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. Now, find the line in SBJson4StreamParser.m where sbjson4_token_real is handled. Change the code as follows:
case sbjson4_token_real: {
NSNumber * number = #(strtod(token, NULL)) ;
number.isDouble = YES ;
[_delegate parserFoundNumber:number ];
[_state parser:self shouldTransitionTo:tok];
break;
}
note the bold line... this will mark a number created from a JSON real as a double.
3. Finally, you can check the isDouble property on your number objects decoded via SBJSON
HTH
edit:
(Of course you could generalize this and replace the added isDouble with a generic type indicator if you like)
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];
}
}

NSDictionary objectForKey returns wrong value

I'm trying to use a dictionary which I got as a JSON response from my server. When I print the description of the dictionary, everything is in order
Printing description of dict:
{
category = 1;
code = 1;
name = "xxxx";
pictureUrl = "xxxx";
sessionId = xxx;
status = 0;
}
I need the "code" value, and when I use objectForKey:#"code" to get it, I get a wrong value:
int code = [NSDictionary objectForKey:#"code"];
After this i print out the value of code and its something like 3483765348, which is very, very wrong.
Why is this happening?
The object returned is an NSNumber and not an int (which isn't an object).
If you want the int value try this
int code = [[myDictionary objectForKey:#"code"] intValue];
Try the following code which will be use to solve your issue.. As per your code it returns only value you have to convert it to int value.Use this piece of code
int code = [[NSDictionary objectForKey:#"code"]intValue];
Hope this Helps !!!
-objectForKey: doesn't return an int, but an object instead (in your case of type NSNumber).
NSNumber *code = [myDict objectForKey:#"code"];
NSInteger codeInteger = [code integerValue];
int code = [[yourResultingDictionary objectForKey:#"code"] intValue];
I think it will be helpful to you.
The object key returns only for array values, so you will use value for key path.
user_idstr = [tweet valueForKeyPath:#"properties.user_id"];

Resources