Checking that String has a valid number - ios

I'm a programming newbie and I'm currently writing a conversion calc program in objective c and I'm really struggling.
I have a string representing a unsigned long long value. I need a way either when attempting to add another character to check that the new character would not go above LONG_LONG_MAX before adding it. Or deleting the last character if the value is/would be above LONG_LONG_MAX
the only possible way I could think to even try this is:
- (BOOL) isNumberStringValid:(NSString *)stringValue {
unsigned long long uVal = strtoull(stringValue.UTF8String, NULL, 0);
if (uVal <= ULLONG_MAX) return TRUE;
else return FALSE;
}
I know this doesn't work because uVal would always be <= ULLONG_MAX but I can't think of any other ways to possibly check. Can anyone help me find a way to accomplish this???

You can use the fact that strtoull() sets the value of errno to ERANGE if the given
string was out of range:
- (BOOL) isNumberStringValid:(NSString *)stringValue {
errno = 0;
unsigned long long uVal = strtoull(stringValue.UTF8String, NULL, 0);
return (errno == 0);
}
Some test (ULLONG_MAX = 264-1 = 18446744073709551615):
1234 : TRUE
18446744073709551615 : TRUE
18446744073709551616 : FALSE
1844674407370955161678 : FALSE

You can use NSNumberFormatter. Unfortunately NSNumberFormatter stores the 'maximum' value as a float, so there are some problems around the boundary of LONG_LONG_MAX. To deal with that this code checks for nil or a long long value that is negative (which means that it overflowed)
-(BOOL) isNumberStringValid:(NSString *)stringValue
{
[NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4];
NSNumberFormatter *f=[[NSNumberFormatter alloc]init];
NSNumber *max=[NSNumber numberWithLongLong:LONG_LONG_MAX];
[f setMaximum:max];
BOOL valid=NO;
NSNumber *num=[f numberFromString:stringValue];
if (num != nil) // A nil value means that input was > LONG_LONG_MAX
{
long long x=[num unsignedLongLongValue]; // A negative value here means that the input was > LONG_LONG_MAX
if (x>0)
{
valid=YES;
}
}
return valid;
}

the if statement you have is checking if uVal is less than or equal to LONG_LONG_MAX
unsigned long long uVal = (unsigned)stringValue.longLongValue;
if (uVal >= LONG_LONG_MAX) {
return YES;
}
else {
return NO;
}
I ran this and it works fine.

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

Looping over BOOLs in Objective-C

Is it safe to loop over Objective-C BOOLs like so:
for (BOOL flagA = NO; flagA <= YES; flagA++)
for (BOOL flagB = NO; flagB <= flagA; flagB++)
// ...
I'd like to use this to cycle through all relevant permutations of flags in a XCTestCase.
But it appears as if YES++ is still YES at least on some platforms (hence leading to an infinite loop e.g. on iPhone 6 Plus simulator) whereas I would have expected that BOOL would just be treated as int (and hence YES++ becoming 2).
Do I have to loop over ints (my best guess) instead, or can the use of BOOL be salvaged in some convenient way?
You are all missing the point here. Drux is asking why can't he increment over BOOL, while it should be a char (8 bit value), which is perfectly incrementable.
The Answer is very easy. BOOL is sometimes a char and sometimes a bool depending on the target. From objc.h file:
#if !defined(OBJC_HIDE_64) && TARGET_OS_IPHONE && __LP64__
typedef bool BOOL;
#else
typedef signed char BOOL;
If you iterate over a bool you will get value of 1 maximum.
EDIT:
Can you please add a reference to where the semantics of ++ for bool are specified? - Drux
Even though that bool has to be 8 bits minimum, it can't have any other value than 0 or 1. Why ? Because bool a = 3 (bool equal operator) converts 3 into a bool value, which is true which is 1.
So bool a = true; a++ is the same as bool a = 2; which makes a have a value of 1
The only way I see would be adding a break in your loop to escape the infinite loop.
Another possibilities is to use simple integer and stop the for loop when counter == 2
for (BOOL flagA = NO; YES; flagA++) {
for (BOOL flagB = NO; YES; flagB++) {
// Do something
if (flagB)
break;
}
if (flagA)
break;
}
I think #Sulthan means something like this (made overly explicit on purpose):
for(int indexA = 0; indexA <= 1; indexA++){
for(int indexB = 0; indexB <= indexA; indexB++){
BOOL flagA = (indexA == 1) ? YES : NO;
BOOL flagB = (indexB == 1) ? YES : NO;
// Use your flags (booleans) here...
}
}
(Of course, you can use just the ints in place of booleans in Objective-C, if you want to avoid using too many redundant variables).
ADDENDUM: I actually performed a "Jump to definition" in Xcode (OSX project), and the part looks like this:
#if __has_feature(objc_bool)
#define YES __objc_yes
#define NO __objc_no
#else
#define YES ((BOOL)1)
#define NO ((BOOL)0)
#endif
(usr/include/objc/objc.h)
Can't "Jump to Definition" on __objc_yes (gives "Symbol Not Found")
If you're set on operating over BOOLs, then instead of:
for (BOOL flagA = NO; flagA <= YES; flagA++)
for (BOOL flagB = NO; flagB <= flagA; flagB++)
// ...
You should really be doing something this (though it is not what you want):
for (BOOL flagA = NO; flagA != YES; flagA = !flagA)
for (BOOL flagB = NO; flagB != flagA; flagB = !flagB)
// This is the only safe way to 'iterate' BOOLs
The behaviour, (BOOL)++ is not well-defined* as a BOOL can only be YES or NO. What you really should be doing is casting your BOOL to an int, and iterating over that, or refactoring your loop entirely to use int types.
The problem with casting your BOOL values to ints is, as you have pointed out, BOOL is typedef'd to something with only 8 bits of information*, therefore it only makes sense to have 255 iterations. In fact in more recent times, BOOL is not cast-able at all because it is defined as a compiler intrinsic (objc_bool, which can have values __objc_yes and __objc_no). __objc_no++ has no meaning.
TL;DR My (strong) suggestion would be to refactor your code so you are iterating over integers, and inspecting BOOLs within each iteration. Whether you cast your BOOL values, or refactor your loop is up to you, but iterating over BOOL values in the way you have indicated is both unsafe and (now, because of that) unsupported.
* In past years, the implementation details of BOOL were obvious (namely a cast to an unsigned char). With the advent of compiler intrinsics, the details are hidden (though they are likely the same). The reason they are now hidden is because you're really not supposed to rely on them, and the easiest way to stop people relying on them is to hide them from the compiler altogether.

iOS : NSInteger and NSUInteger comparison

Surprise!
I've a variable like this,
NSInteger index = 0;
I'm comparing it with one of subviews count (which returns NSUInteger) like this,
if((index-1) <= [[currentmonth subviews] count])
{
NSLog(#"true");
}
else
{
NSLog(#"false");
}
This always giving false.
but If I'll do like this,
if ((index-1) <= 42) {
NSLog(#"true");
} else {
NSLog(#"false");
}
This always giving true.
I feel that, this because we can't compare NSInteger with NSUInteger correct?
I caught this issue, when I have a working solution based on this logic. But its not true at all.
I've found this, NSUInteger vs NSInteger, int vs unsigned, and similar cases
This answer gives the good explanations on this!
You should also be aware of integer conversion rules when dealing with NSUInteger vs. NSInteger:
The following fragment for example returns 0 (false) although you'd expect it to print 1 (true):
NSInteger si = -1;
NSUInteger ui = 1;
printf("%d\n", si < ui);
The reason is that the [si] variable is being implicitly converted to an unsigned int!
See CERT's Secure Coding site for an in-depth discussion around these 'issues' and how to solve them.

IOS String length comparison issue

I'm struggling with an if Comparison - I basically want to make two comparisons - both of which need to pass - Firstly a basic if a string variable is equal to 'rec' and secondly if a strings character limit is not equal to zero.
I've tried various combinations - but this is where i'm at at the mo..
ArticleObject *A = [self.articleArray objectAtIndex:indexPath.section];
NSInteger imglength = [A.arImage length];
if([A.arRec isEqual: #"rec"] ) && (imglength !=Nil){
return 195;
}
else return 50;
I get an expected identifier error on the (imglength comparison - as in this screen shot
Can anyone shed any light for me please?
There are several things you should change:
ArticleObject *A = self.articleArray[indexPath.section];
NSInteger imglength = [A.arImage length];
if (imglength && [A.arRec isEqualToString:#"rec"]) {
return 195;
} else {
return 50;
}
Don't use Nil (or nil) with primitive types.
Your parentheses are messed up:
if([A.arec isEqualToString:#"rec"] && (imglengyb !=Nil))
^--------------//here
Maybe a better way would be:
if([A.arec isEqualToString:#"rec"] && [[A.arImage length] != 0])

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

Resources