Objective-C - How to get sum of Boleans? - ios

Through the code below, I can get an output such as :
0
1
1
What I want is to output the sum of these booleans values, in my case the result will be : 2 because we have 0+1+1
The code [Update] :
-(void)markers{
CLLocationCoordinate2D tg = CLLocationCoordinate2DMake(location.latitude, location.longitude);
GMSCoordinateBounds *test = [[GMSCoordinateBounds alloc]initWithPath:path];
BOOL test3 = [test containsCoordinate:tg];
{
if (test3 == 1)
{
marker.position = CLLocationCoordinate2DMake(location.latitude, location.longitude);
}else if (test3 == 0)
{
marker.position = CLLocationCoordinate2DMake(0, 0);
}
}
}
}

Rather than sum BOOLs, which is counterintuitive, loop over whatever you are using to get the BOOL values, and if you get YES, increment a counter. This will be the number of YESs that you have.
If you have an array of BOOLs, you could just filter the array with a predicate to get the YES values and the length of the resulting array is the number of YESs that you have.
Edited to add code samples following OP comments
Incrementing a counter
NSUInteger numberOfBools = 0;
CLLocationCoordinate2D tg = CLLocationCoordinate2DMake(location.latitude, location.longitude);
GMSCoordinateBounds *test = [[GMSCoordinateBounds alloc]initWithPath:path];
if ([test containsCoordinate:tg1]) { ++numberOfBools; }
if ([test containsCoordinate:tg2]) { ++numberOfBools: }
... // other tests here;
// numberOfBools now contains the number of passing tests.
Edited Again, after the full code was added
// snipped code above here
// This is where you add the counter and initialise it to 0
NSUInteger numberOfBools = 0;
for (NSDictionary *dictionary in array)
{
// Snip more code to this point
BOOL test3 = [test containsCoordinate:tg];
{
if (test3)
{
// This is where you increment the counter
++numberOfBools;
// Snip more code to the end of the for block
}
// Now numberOfBools shows how many things passed test3

int sum = (test3 ? 1 : 0) + (testX ? 1 : 0) + (testY ? 1 : 0);
And not so weird variant:
#define BOOL_TO_INT(val) ((val) ? 1 : 0)
int sum = BOOL_TO_INT(test3) + BOOL_TO_INT(testX) + BOOL_TO_INT(testY);

You can just add BOOLs since bools are just integers. e.g. :
int sum = 0;
CLLocationCoordinate2D tg = CLLocationCoordinate2DMake(location.latitude, location.longitude);
GMSCoordinateBounds *test = [[GMSCoordinateBounds alloc]initWithPath:path];
BOOL test3 = [test containsCoordinate:tg];
//Obtain bolean values :
BOOL testX = /* other test */;
BOOL testY = /* other test */;
sum = test3 + testX + testY
This is a bad idea however, as BOOLs aren't necessarily 1 or 0. They are 0 and not 0
BOOL is just a typedef-ed char: typedef signed char BOOL; YES and NO are 1 and 0, but BOOL variable = 2 is perfectly valid
For example:
- (int) testX
{
if(inState1) return 1;
if(inState2) return 2;
else return 0;
}
BOOL textXResult = [self testX]; //Might return 2, this is still equivalent to YES.
The best solution is to iterate your BOOLs and instead count the number of YESes.

Another way to do this is to assume that if any one value is false, then the entire array is false, so, loop over the array until a false value is found, then break:
BOOL retval = true; //return holder variable
/*'boolsNumArray' is an NSArray of NSNumber instances, converted from BOOLs:
//BOOL-to-NSNumber conversion (makes 'boolsNumArray' NSArray, below)!
'[myNSArray addObject:[NSNumber numberWithBool:yourNextBOOL]]'
*/
for(NSNumber* nextNum in boolsNumArray) {
if([nextNum boolValue] == false) {
retval = false;
break;
}
}
return retval;

Related

Converting very large NSDecimal to string eg. 400,000,000,000 -> 400 T and so forth

I am making a game that requires me to use very large numbers. I believe I am able to store very large numbers with NSDecimal. However, when displaying the numbers to users I would like to be able to convert the large number to a succinct string that uses characters to signify the value eg. 100,000 -> 100k 1,000,000 -> 1.00M 4,200,000,000 -> 4.20B and so forth going up to extremely large numbers. Is there any built in method for doing so or would I have to use a bunch of
NSDecimalCompare statements to determine the size of the number and convert?
I am hoping to use objective c for the application.
I know that I can use NSString *string = NSDecimalString(&NSDecimal, _usLocale); to convert to a string could I then do some type of comparison on this string to get the result I'm looking for?
Use this method to convert your number into a smaller format just as you need:
-(NSString*) suffixNumber:(NSNumber*)number
{
if (!number)
return #"";
long long num = [number longLongValue];
int s = ( (num < 0) ? -1 : (num > 0) ? 1 : 0 );
NSString* sign = (s == -1 ? #"-" : #"" );
num = llabs(num);
if (num < 1000)
return [NSString stringWithFormat:#"%#%lld",sign,num];
int exp = (int) (log(num) / 3.f); //log(1000));
NSArray* units = #[#"K",#"M",#"G",#"T",#"P",#"E"];
return [NSString stringWithFormat:#"%#%.1f%#",sign, (num / pow(1000, exp)), [units objectAtIndex:(exp-1)]];
}
Some sample examples:
NSLog(#"%#",[self suffixNumber:#99999]); // 100.0K
NSLog(#"%#",[self suffixNumber:#5109999]); // 5.1M
Source
Solved my issue: Can only be used if you know that your NSDecimal that you are trying to format will only be a whole number without decimals so make sure you round when doing any math on the NSDecimals.
-(NSString *)returnFormattedString:(NSDecimal)nsDecimalToFormat{
NSMutableArray *formatArray = [NSMutableArray arrayWithObjects:#"%.2f",#"%.1f",#"%.0f",nil];
NSMutableArray *suffixes = [NSMutableArray arrayWithObjects:#"k",#"M",#"B",#"T",#"Qa",#"Qi",#"Sx",#"Sp",#"Oc",#"No",#"De",#"Ud",#"Dud",#"Tde",#"Qde",#"Qid",#"Sxd",#"Spd",#"Ocd",#"Nvd",#"Vi",#"Uvi",#"Dvi",#"Tvi", nil];
int dick = [suffixes count];
NSLog(#"count %i",dick);
NSString *string = NSDecimalString(&nsDecimalToFormat, _usLocale);
NSString *formatedString;
NSUInteger characterCount = [string length];
if (characterCount > 3) {
NSString *trimmedString=[string substringToIndex:3];
float a;
a = 100.00/(pow(10, (characterCount - 4)%3));
int remainder = (characterCount-4)%3;
int suffixIndex = (characterCount + 3 - 1)/3 - 2;
NSLog(#"%i",suffixIndex);
if(suffixIndex < [suffixes count]){
NSString *formatSpecifier = [formatArray[remainder] stringByAppendingString:suffixes[suffixIndex]];
formatedString= [NSString stringWithFormat:formatSpecifier, [trimmedString floatValue] / a];
}
else {
formatedString = #"too Big";
}
}
else{
formatedString = string;
}
return formatedString;
}

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

SceneKit : Extract data from SCNGeometryElement

I am using SceneKit, and I have an issue:
How can I extract the data from a SCNGeometryElement object ?
I use this method :
- (void)geometryElements:(SCNNode *)node {
for (int indexElement = 0; indexElement < node.geometry.geometryElementCount; indexElement++) {
SCNGeometryElement *currentElement = [node.geometry geometryElementAtIndex:indexElement];
NSLog(#"\n");
NSLog(#"bytes per index : %d", currentElement.bytesPerIndex);
NSLog(#"number element : %d", currentElement.primitiveCount);
NSLog(#"data lenght : %d", currentElement.data.length);
for (int indexPrimitive = 0; indexPrimitive < currentElement.primitiveCount; indexPrimitive++) {
int array[3];
memset(array, 0, 3);
[currentElement.data getBytes:&array range:NSMakeRange(indexPrimitive * 3, (currentElement.bytesPerIndex * 3))];
NSLog(#"currentelement : %d %d %d", array[0], array[1], array[3]);
}
}
The result is not good :
2015-04-10 15:10:25.183 IKTest[1234:244778] currentelement : 14539995 -1068223968 -379286778
2015-04-10 15:10:25.183 IKTest[1234:244778] currentelement : 14737374 -1068223968 -379286778
2015-04-10 15:10:25.183 IKTest[1234:244778] currentelement : 14934753 -1068223968 -379286778
Thanks in advance.
a few notes:
you should rely on geometryElement.primitiveType instead of hard coding 3 (unless you are sure that you're always dealing with SCNGeometryPrimitiveTypeTriangles)
it seems that the range's location does not take geometryElement.bytesPerIndex into account
your buffer is of size 3 * sizeof(int) but should be of size numberOfIndicesPerPrimitive * geometryElement.bytesPerIndex
As mnuages said, you should confirm primtive type and data type of index first.
Your code only work if index type is int.
Here is some code work for me. I only deals that geometry consisted of triangles.
void extractInfoFromGeoElement(NSString* scenePath){
NSURL *url = [NSURL fileURLWithPath:scenePath];
SCNScene *scene = [SCNScene sceneWithURL:url options:nil error:nil];
SCNGeometry *geo = scene.rootNode.childNodes.firstObject.geometry;
SCNGeometryElement *elem = geo.geometryElements.firstObject;
NSInteger componentOfPrimitive = (elem.primitiveType == SCNGeometryPrimitiveTypeTriangles) ? 3 : 0;
if (!componentOfPrimitive) {//TODO: Code deals with triangle primitive only
return;
}
for (int i=0; i<elem.primitiveCount; i++) {
void *idxsPtr = NULL;
int stride = 3*i;
if (elem.bytesPerIndex == 2) {
short *idxsShort = malloc(sizeof(short)*3);
idxsPtr = idxsShort;
}else if (elem.bytesPerIndex == 4){
int *idxsInt = malloc(sizeof(int)*3);
idxsPtr = idxsInt;
}else{
NSLog(#"unknow index type");
return;
}
[elem.data getBytes:idxsPtr range:NSMakeRange(stride*elem.bytesPerIndex, elem.bytesPerIndex*3)];
if (elem.bytesPerIndex == 2) {
NSLog(#"triangle %d : %d, %d, %d\n",i,*(short*)idxsPtr,*((short*)idxsPtr+1),*((short*)idxsPtr+2));
}else{
NSLog(#"triangle %d : %d, %d, %d\n",i,*(int*)idxsPtr,*((int*)idxsPtr+1),*((int*)idxsPtr+2));
}
//Free
free(idxsPtr);
}
}
As the original question has a Swift tag, I am posting my solution in Swift 5.
extension SCNGeometryElement {
/// Gets the `Element` vertices
func getVertices() -> [SCNVector3] {
func vectorFromData<UInt: BinaryInteger>(_ float: UInt.Type, index: Int) -> SCNVector3 {
assert(bytesPerIndex == MemoryLayout<UInt>.size)
let vectorData = UnsafeMutablePointer<UInt>.allocate(capacity: bytesPerIndex)
let buffer = UnsafeMutableBufferPointer(start: vectorData, count: primitiveCount)
let stride = 3 * index
self.data.copyBytes(to: buffer, from: stride * bytesPerIndex..<(stride * bytesPerIndex) + 3)
return SCNVector3(
CGFloat.NativeType(vectorData[0]),
CGFloat.NativeType(vectorData[1]),
CGFloat.NativeType(vectorData[2])
)
}
let vectors = [SCNVector3](repeating: SCNVector3Zero, count: self.primitiveCount)
return vectors.indices.map { index -> SCNVector3 in
switch bytesPerIndex {
case 2:
return vectorFromData(Int16.self, index: index)
case 4:
return vectorFromData(Int.self, index: index)
case 8:
return SCNVector3Zero
default:
return SCNVector3Zero
}
}
}
}
There might be a bit of an indentation problem here, but yes, it is a function inside another one. The objective is the same as the other answers. Create a buffer, define what types of numbers the buffer is going to handle, build the SCNVector3 and return the array.

Correct answer - comparing strings

I'm building my own alternative to the Levenshtein distance to train myself in objective-c. This class will get a few sentences, only 2 during initial testing and return how many percent correct the sentence is. How many percent matches with the "correct sentence".
Basically what it does is it gets 2 sentences, (only 2 words during initial testing really) and calculates. But I get some strange NSLog's.
If I enter 1989 and 199 it'll return 75% correct - which is the correct answer. However, if I enter "orange" and "oange" - it returns 50% correct when correct is 83% (right??)
This is the code calling the method:
-(void)compare {
// Take each and every sentence from the users source and check it against the other sources. If it contains 2 or more words/numbers that are equal, i'll get "investigated" further
NSMutableArray *userSentences = [[MyManager sharedManager] contentSentencesList];
NSMutableArray *serverSentences = [NSMutableArray arrayWithArray:getSentencesFromText(serverText)];
// Sample sentences
[userSentences insertObject:#"oange" atIndex:userSentences.count];
[serverSentences insertObject:#"orange" atIndex:serverSentences.count];
// For-statement integers
int i = 0;
int b = 0;
for (i = 0; i < userSentences.count; i++) {
// Check first sentence
NSString *userSentence;
NSString *serverSentence;
// Check similarity of the two sentences, get percent and add to centerPercent
for (b = 0; b < serverSentences.count; b++) {
// Compare sentences
userSentence = userSentences[i];
serverSentence = serverSentences[b];
// Compare sentences with custom class
// Initialize Distance class
SourcerDistance *wordDistance = [[SourcerDistance alloc] init];
// Create resultPercent integer and calculate it
int resultPercent = [wordDistance distanceBetween:userSentence serverSentence:serverSentence];
NSLog(#"%#%d", #"FinishViewController result: ", resultPercent);
// Add resultPercent to averagePrecent and increase averagPercentToDivide by 1
centerPercent = centerPercent + resultPercent;
centerPercentToDivide++;
}
// Set and display resultoppositeSpelling
// averagePercent = centerPercent / centerPercentToDivide;
// Use an integer to remove decimals
[self presentResult];
}
}
and this is the other class:
#import "SourcerDistance.h"
#implementation SourcerDistance
-(int)distanceBetween:(NSString *)userSentence serverSentence:(NSString *)serverSentence {
// Declare the 2 arrays containing all the words from the user's source and the developer's source
NSArray *developerSourceSentence = [self getWords:serverSentence];
NSArray *userSourceSentence = [self getWords:userSentence];
// Declare variables that'll be use for for-statements
int developerWordsLoop = developerSourceSentence.count;
int userWordsLoop = userSourceSentence.count;
// Declare variables required for matching (average of all words)
float centerPercent = 0; // This is for ALL words in total
float centerPercentToDivide = 0; // This is for all words in total
// Single-word variables
float totalCharacters = 0;
float matchingCharacters = 0;
float percentMatchingSingleWord = 0;
NSLog(#"%#%d", #"userSourceSentenceCount: ", userSourceSentence.count);
NSLog(#"%#%d", #"developerSourceSentenceCount: ", developerSourceSentence.count);
// Loop through all of the user words
for (userWordsLoop = 0; userWordsLoop < userSourceSentence.count; userWordsLoop++) {
// Loop through all of the developer words
for (developerWordsLoop = 0; developerWordsLoop < developerSourceSentence.count; developerWordsLoop++) {
// Declare variables that contain all the characters of the user- and developer-words
NSMutableArray *userCharacters = [self getCharacters:userSourceSentence[userWordsLoop]];
NSMutableArray *developerCharacters = [self getCharacters:developerSourceSentence[developerWordsLoop]];
// Compare characters
matchingCharacters = [self compareCharacters:userCharacters developerCharacters:developerCharacters];
// Set the variables
totalCharacters = developerCharacters.count;
percentMatchingSingleWord = matchingCharacters / totalCharacters * 100;
NSLog(#"%#%f", #"totalCharacters", totalCharacters);
NSLog(#"%#%f", #"matchingCharacters", matchingCharacters);
NSLog(#"%#%f", #"iterate", percentMatchingSingleWord);
centerPercent = centerPercent + percentMatchingSingleWord;
centerPercentToDivide++;
}
}
// Declare variables used with final result
float finalPercentFloat = 0;
int finalPercent = 0;
NSLog(#"%#%f", #"centerPercent: ", centerPercent);
NSLog(#"%#%f", #"centerPercentToDivide: ", centerPercentToDivide);
finalPercentFloat = centerPercent/centerPercentToDivide;
NSLog(#"%#%f", #"finalPercent: ", finalPercentFloat);
finalPercent = (int)finalPercentFloat;
return finalPercent;
}
-(float)compareCharacters:(NSMutableArray *)userCharacters developerCharacters:(NSMutableArray *)developerCharacters {
// Declare result variables and other required variables
float matchingCharacters;
int userCharactersLoop = 0;
int developerCharactersLoop = 0;
// Loop through all of the userCharacters
for (userCharactersLoop = 0; userCharactersLoop < userCharacters.count; userCharactersLoop++) {
// Loop through all of the developerCharacters
for (developerCharactersLoop = 0; developerCharactersLoop < developerCharacters.count; developerCharactersLoop++) {
// Match every character here
if ([userCharacters[userCharactersLoop] isEqualToString:developerCharacters[developerCharactersLoop]]) {
// Increase matchingCharacters
matchingCharacters++;
} else {
// Do nothing
}
}
}
// Return result variable
return matchingCharacters;
}
-(NSArray *)getWords:(NSString *)sentence {
// Get words of sentence from developer-source
NSArray *sentenceWords;
NSString *serverSentenceToRead = sentence;
sentenceWords = [serverSentenceToRead componentsSeparatedByCharactersInSet:
[NSCharacterSet characterSetWithCharactersInString:#":;.!? "]
];
// Return developer words
return sentenceWords;
}
-(NSMutableArray *)getCharacters:(NSString *)word {
NSMutableArray *wordCharacters = [[NSMutableArray alloc] initWithCapacity:[word length]];
for (int i=0; i < [word length]; i++) {
NSString *ichar = [NSString stringWithFormat:#"%c", [word characterAtIndex:i]];
[wordCharacters addObject:ichar];
}
// Return the characters of the word
return wordCharacters;
}
#end
NSLog:
2014-09-03 20:22:32.015 Sourcer[27532:60b] userSourceSentenceCount: 1
2014-09-03 20:22:32.017 Sourcer[27532:60b] developerSourceSentenceCount: 1
2014-09-03 20:22:32.018 Sourcer[27532:60b] totalCharacters6.000000
2014-09-03 20:22:32.018 Sourcer[27532:60b] matchingCharacters3.001519
2014-09-03 20:22:32.019 Sourcer[27532:60b] iterate50.025322
2014-09-03 20:22:32.020 Sourcer[27532:60b] centerPercent: 50.025322
2014-09-03 20:22:32.021 Sourcer[27532:60b] centerPercentToDivide: 1.000000
2014-09-03 20:22:32.021 Sourcer[27532:60b] finalPercent: 50.025322
2014-09-03 20:22:32.022 Sourcer[27532:60b] FinishViewController result: 50
2014-09-03 20:22:32.022 Sourcer[27532:60b] averagePercent (float): 50.000000
What am I doing wrong here? Is it possible for anyone to understand the code and help me find out what's wrong? There's something odd about this algorithm
Thanks a lot!
(I know I'm kinda reinventing the wheel, but I want to try :))
Sincerely,
Erik

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