How to get the correct value and type of NSNumber? - objective-c

I am creating an NSNumber from string by check if the string is float or int using NSNumberFormatter. I want to have a method to return what type the NSNumber holds and the correct value. It works for int, but not working for decimal numbers.
NSNumber *n;
enum CFNumberType _numberType;
- (instancetype)initWithString:(NSString *)string {
self = [super init];
if (self) {
NSNumberFormatter *formatter = [NSNumberFormatter new];
decimalPattern = #"\\d+(\\.\\d+)";
if ([Utils matchString:string withPattern:decimalPattern]) {
// Float
formatter.numberStyle = NSNumberFormatterDecimalStyle;
_numberType = kCFNumberDoubleType;
n = [formatter numberFromString:string];
} else {
// Integer
formatter.numberStyle = NSNumberFormatterNoStyle;
_numberType = kCFNumberIntType;
n = [formatter numberFromString:string];
}
self = [self initWithNumber:n];
}
return self;
}
- (CFNumberType)value {
CFNumberType num; // How to determine the type of num?
CFNumberGetValue((CFNumberRef)n, _numberType, &num);
return num;
}
- (CFNumberType)numberType {
return _numberType;
}
In the value method, if I specify float num, then it works for floating point. But I cannot use float because I want to make it work for int as well. How to do this?

I have found a solution for this. First check if the number is double and call the appropriate value method.
- (BOOL)isDouble {
if (_numberType == kCFNumberFloatType || _numberType == kCFNumberFloat32Type || _numberType == kCFNumberFloat64Type || _numberType == kCFNumberDoubleType) {
return YES;
}
return NO;
}
- (double)doubleValue {
if ([self isDouble]) {
return [n doubleValue];
}
return 0.0;
}
- (int)intValue {
if (![self isDouble]) {
return [n intValue];
}
return 0;
}

Related

Verify whether an input is an number and whether a number has been repeated or not?

i know there have been a few questions on this game, but mine has a few more instructions so it makes it a bit more difficult. I am almost done, I just have a couple more things to complete. my code is as follows:
- (NSString *)output {
NSMutableString *resultOutput = [[NSMutableString alloc] init];
int secretNumber= arc4random_uniform(10);
int numberChosen;
int attempt=0;
NSMutableArray<NSNumber*> *myArray = [[NSMutableArray alloc] init];
printf("Guess a Number\n");
printf("\n");
scanf("%d", &numberChosen);
while (numberChosen != secretNumber) {
if (numberChosen < secretNumber) {
printf("Too Low\n");
printf("Guess Again\n");
scanf("%d", &numberChosen);
attempt++;
}
else if (numberChosen > secretNumber) {
printf("Too high\n");
printf("Guess Again\n");
scanf("%d", &numberChosen);
attempt++;
}
else if (numberChosen ==secretNumber) {
attempt++;
break;
}
[myArray addObject:[NSNumber numberWithInt:numberChosen]];
}
if (numberChosen == secretNumber) {
NSLog(#"\nGood job, you guessed in %i tries", attempt);
}
return resultOutput;
}
My output should be:
Guess a number:
> 12
Too Low!
Guess a number:
> 65
Too High!
Guess a number:
> 65
Already Guessed!
Guess a number:
> asdf
Not a number! Try again!
Guess a number:
> 42
You got it! You took 3 attempts!
I got as far as creating the loop that verifies whether you're higher or lower and how many tries it took. What I am stuck on now is verifying whether the input was a letter, and if so to display on the console that it is not valid. I am also having trouble with verifying whether a number has already been inputted and displaying a message saying that this number has already been guessed.
Help would be appreciated, thanks!
Use the return value of scanf.
From the documentation
RETURN VALUE
This function returns the number of input items assigned. This can be
fewer than provided for, or even zero, in the event of a matching failure.
Zero indicates that, although there was input available, no conversions
were assigned; typically this is due to an invalid input character,
such as an alphabetic character for a `%d' conversion. The value EOF is
returned if an input failure occurs before any conversion such as an end-of-file
occurs. If an error or end-of-file occurs after conversion has
begun, the number of conversions which were successfully completed is
returned.
Edit:
If you use the return value you have to empty the buffer in case letters have been entered.
This is an implementation of all conditions. Rather than an array a set is used to avoid duplicates:
int secretNumber = arc4random_uniform(10);
int numberChosen = 0;
int returnValue = -1;
int attempt = 0;
NSMutableSet<NSNumber*> *set = [[NSMutableSet alloc] init];
printf("Guess a Number\n");
printf("\n");
while (numberChosen != secretNumber) {
returnValue = scanf("%d", &numberChosen);
attempt++;
if (numberChosen == secretNumber) {
break;
}
else if ([set containsObject:#(numberChosen)]) {
printf("Already Guessed!\n");
}
else if (returnValue == 0) {
printf("Not a number!\n");
int c;
while((c = getchar()) != '\n' && c != EOF);
}
else if (numberChosen < secretNumber) {
printf("Too Low\n");
[set addObject:#(numberChosen)];
}
else if (numberChosen > secretNumber) {
printf("Too high\n");
[set addObject:#(numberChosen)];
}
printf("Guess Again\n");
printf("\n");
}
NSLog(#"\nGood job, you guessed in %i tries", attempt);
Where possible, use Objective-C types so you can take advantage of built-in classes such as a number formatter to parse input and an NSArray to record prior guesses. Use methods to organize the code so the game algorithm is clear.
#property(strong,nonatomic) NSMutableArray *priorGuesses;
- (NSNumber *)promptForNumber {
char cstring[256];
NSLog(#"Guess a Number\n");
scanf("%s", cstring); // note that input exceeding 256 chars will write past this buffer
NSString *string = [NSString stringWithCString:cstring encoding:NSUTF8StringEncoding];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
return [formatter numberFromString:string];
}
- (BOOL)isUnique:(NSNumber *)number {
if ([self.priorGuesses containsObject:number]) return NO;
[self.priorGuesses addObject:number];
return YES;
}
- (void)game {
BOOL gameOn = YES;
self.priorGuesses = [NSMutableArray array];
NSInteger secretNumber= arc4random_uniform(10);
NSNumber *guess = #(-1);
while (gameOn) {
guess = [self promptForNumber];
if (guess && [self isUnique:guess]) {
NSInteger guessInt = [guess intValue];
if (guessInt == secretNumber) {
// notice that there's no need to count attempts in a separate var
NSLog(#"Good job, you guessed in %ld tries", self.priorGuesses.count);
gameOn = NO;
} else if (guessInt > secretNumber) {
NSLog(#"Too high");
} else { // by implication, guessInt < secretNumber
NSLog(#"Too low");
}
} else {
NSLog((guess)? #"Already guessed\n" : #"Not a number\n");
}
}
}

Check if string is palindrome in objective c

I'm trying to check if a string is palindrome or not using objective c. I'm new to programming without any experience in other programming languages so bear with me please. I get stuck at my if condition I want it to say that if the first position in the string is equal to the last one the string is a palindrome.
What im a doing wrong?
int main (int argc, const char * argv[])
{
NSString *p = #"121" ;
BOOL palindrome = TRUE;
for (int i = 0 ; i<p.length/2+1 ; i++)
{
if (p[i] != p [p.Length - i - 1])
palindrome = false;
}
return (0);
}
You're trying to use an NSString as an NSArray (or probably, like a C string), which won't work. Instead, you need to use the NSString method characterAtIndex: to get the character to test.
Apart from the unbalanced braces, accessing a character from NSString is more complicated than using array notation. You need to use the method characterAtIndex: You can optimise your code, by breaking out of the loop if a palindrome is impossible and taking the length call outside of the for loop.
NSString *p = #"121";
NSInteger length = p.length;
NSInteger halfLength = (length / 2);
BOOL isPalindrome = YES;
for (int i = 0; i < halfLength; i++) {
if ([p characterAtIndex:i] != [p characterAtIndex:length - i - 1]) {
isPalindrome = NO;
break;
}
}
It may be desirable to check case insensitively. To do this, make the string be all lowercase before looping, using the lowercaseString method.
As pointed out by Nikolai in the comments, this would only work for strings containing 'normal' unicode characters, which is often not true — such as when using UTF8 for foreign languages. If this is a possibility, use the following code instead, which checks composed character sequences rather than individual characters.
NSString *p = #"121";
NSInteger length = p.length;
NSInteger halfLength = length / 2;
__block BOOL isPalindrome = YES;
[p enumerateSubstringsInRange:NSMakeRange(0, halfLength) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
NSRange otherRange = [p rangeOfComposedCharacterSequenceAtIndex:length - enclosingRange.location - 1];
if (![substring isEqualToString:[p substringWithRange:otherRange]]) {
isPalindrome = NO;
*stop = YES;
}
}];
var str: NSString = "123321"
var length = str.length
var isPalindrome = true
for index in 0...length/2{
if(str.characterAtIndex(index) != str.characterAtIndex(length-1 - index)){
print("\(index )not palindrome")
isPalindrome = false
break
}
}
print("is palindrome: \(isPalindrome)")
As it seems there's no answer yet that handles composed character sequences correctly I'm adding my two cents:
NSString *testString = #"\u00E0 a\u0300"; // "à à"
NSMutableArray *logicalCharacters = [NSMutableArray array];
[testString enumerateSubstringsInRange:(NSRange){0, [testString length]}
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop)
{
[logicalCharacters addObject:substring];
}];
NSUInteger count = [logicalCharacters count];
BOOL isPalindrome = YES;
for (NSUInteger idx = 0; idx < count / 2; ++idx) {
NSString *a = logicalCharacters[idx];
NSString *b = logicalCharacters[count - idx - 1];
if ([a localizedCaseInsensitiveCompare:b] != NSOrderedSame) {
isPalindrome = NO;
break;
}
}
NSLog(#"isPalindrome: %d", isPalindrome);
This splits the string into an array of logical characters (elements of a string that a normal user would call a "character").
#import Foundation;
BOOL isPalindrome(NSString * str)
{
if (!str || str.length == 0) return NO;
if (str.length == 1) return YES;
for(unsigned i = 0; i < str.length / 2; ++i)
if ([str characterAtIndex:i] != [str characterAtIndex:str.length - i - 1]) return NO;
return YES;
}
int main() {
#autoreleasepool {
NSLog(#"%s", isPalindrome(#"applelppa") ? "YES" : "NO");
} return 0;
}
Recursive
- (BOOL)isPaliRec:(NSString*)str :(int)start :(int)end{
if(start >= end)
return YES;
else if([str characterAtIndex:start] != [str characterAtIndex:end])
return NO;
else
return [self isPaliRec:str :++start :--end];
}
Non Recursive
- (BOOL)isPali:(NSString*)str{
for (int i=0; i<str.length/2; i++)
if([str characterAtIndex:i] != [str characterAtIndex:(str.length-i-1)])
return NO;
return YES;
}
you can call:
NSString *str = #"arara";
[self isPaliRec:str :0 :(int)str.length-1];
[self isPali:str];
Swift 3:
// Recursive
func isPaliRec(str: String, start: Int = 0, end: Int = str.characters.count-1) -> Bool {
if start >= end {
return true
} else if str[str.index(str.startIndex, offsetBy: start)] != str[str.index(str.startIndex, offsetBy: end)] {
return false
} else {
return isPaliRec(str: str, start: start+1, end: end-1)
}
}
// Non Recursive
func isPali(str: String) -> Bool {
for i in 0..<str.characters.count/2 {
let endIndex = str.characters.count-i-1
if str[str.index(str.startIndex, offsetBy: i)] != str[str.index(str.startIndex, offsetBy: endIndex)] {
return false
}
}
return true
}
// Using
let str = "arara"
isPaliRec(str: str)
isPali(str: str)
Also, you can use swift 3 methods like a string extension... It's more elegant. extension sample
NSString *str=self.txtFld.text;
int count=str.length-1;
for (int i=0; i<count; i++) {
char firstChar=[str characterAtIndex:i];
char lastChar=[str characterAtIndex:count-i];
NSLog(#"first=%c and last=%c",firstChar,lastChar);
if (firstChar !=lastChar) {
break;
}
else
NSLog(#"Pailndrome");
}
We can also do this using NSRange like this...
enter code NSString *fullname=#"123321";
NSRange rangeforFirst=NSMakeRange(0, 1);
NSRange rangeforlast=NSMakeRange(fullname.length-1, 1);
BOOL ispalindrome;
for (int i=0; i<fullname.length; i++) {
if (![[fullname substringWithRange:rangeforFirst] isEqualToString:[fullname substringWithRange:rangeforlast]]) {
NSLog(#"not match");
ispalindrome=NO;
return;
}
i++;
rangeforFirst=NSMakeRange(i, 1);
rangeforlast=NSMakeRange(fullname.length-i-1, 1);
}
NSLog(#"no is %#",(ispalindrome) ? #"matched" :#"not matched");
NSString *str1 = #"racecar";
NSMutableString *str2 = [[NSMutableString alloc] init];
NSInteger strLength = [str1 length]-1;
for (NSInteger i=strLength; i>=0; i--)
{
[str2 appendString:[NSString stringWithFormat:#"%C",[str1 characterAtIndex:i]]];
}
if ([str1 isEqual:str2])
{
NSLog(#"str %# is palindrome",str1);
}
-(BOOL)checkPalindromeNumber:(int)number{
int originalNumber,reversedNumber = 0,remainder;
originalNumber=number;
while (number!=0) {
remainder=number%10;
reversedNumber=(reversedNumber*10)+remainder;
number=number/10;
}
if (reversedNumber==originalNumber) {
NSLog(#"%d is Palindrome Number",originalNumber);
return YES;
}
else{
NSLog(#"%d is Not Palindrome Number",originalNumber);
return NO;
}
}

Objective-C: multiple if and return statements with least amount of code

This is a question on how to code this efficiently with as little as code possible. It already works but I need to incoporate the paramenter numberOfShapes. That if 1 returns ■, if 2 ■■, if 3 ■■■ etc...
I could do some extra if statemets and extra return statements. If Square->if number = 1> return ■, if number >2 return ■■ etc.. But that is a whole lot of code for something very simple.
What is the best way of coding this with the least amount of code?
- (NSString *)getShape: (NSNumber *)shape numberOfShapes: (NSNumber *)number
{
if ([shape isEqualToNumber:[NSNumber numberWithInt:SQUARE]]) return #"■";
if ([shape isEqualToNumber:[NSNumber numberWithInt:CIRCLE]]) return #"●";
if ([shape isEqualToNumber:[NSNumber numberWithInt:TRIANGLE]]) return #"▲";
return #"?";
}
Largely a question of taste unless you have some really heavy performance requirements.
One way to do it could be to set up a dictionary with the map from numbers to glyphs.
Assuming that you have a static variable glyphs, initialize it in the class' initialize method:
static NSDictionary *glyphs;
+ (void)initialize
{
glyphs = #{
#(SQUARE):#"■",
#(CIRCLE):#"●",
#(TRIANGLE):#"▲"
};
}
Then all you have to do is:
- (NSString *)shapeForNumber:(NSInteger)shape
{
NSString *glyph = [glyphs objectForKey: [NSNumber numberWithInteger: shape]];
return glyph ? glyph : #"?";
}
- (NSString *)getShape: (NSNumber *)shape numberOfShapes: (NSNumber *)number
{
unsigned shapeInt = [shape unsignedIntValue];
if (shapeInt >= 3)
return #"?";
NSString *shapeStr = [#"■●▲" substringWithRange:NSMakeRange(shapeInt, 1)];
// Add autorelease here, if using MRR...
NSMutableString *result = [[NSMutableString alloc] init];
unsigned numberInt = [number unsignedIntValue];
for (unsigned i = 0; i < numberInt; i++)
[result appendString:shapeStr];
return result;
}
I don't see the point of using NSNumber objects to pass parameters like this, as they can't do anything that a simple NSUInteger or unsigned can, and are more expensive to use.
Is there a demonstrated performance problem with the code? Otherwise, I think a switch is pretty clear. Or, fewer lines and O(1) is ...
// declare this earlier
static NSArray *shapeChars = #[ #"■", #"●" /* etc. */ ];
// then
return [shapeChars objectAtIndex:[shape intValue]];
Enter the concept of loops. Also, why do you use NSNumbers for this? Plain old ints are good enough.
- (NSString *)getShape:(int)shape numberOfShapes:(int)number
{
if (shape == SQUARE]) return [self shapeRepeated:#"■" nTimes:number];
if (shape == CIRCLE]]) return [self shapeRepeated:#"●" nTimes:number];
if (shape == TRIANGLE]]) return [self shapeRepeated:#"▲" nTimes:number];
return #"?";
}
- (NSString *)shapeRepeated:(NSString *)shape nTimes:(int)n
{
return [#"" stringByPaddingToLength:n withString:shape startingAtIndex:0];
}
- (NSString *)getShape: (NSNumber *)shape numberOfShapes: (NSNumber *)number
{
switch([number intValue]): {
case SQUARE: return #"■";
case CIRCLE: return #"●";
case TRIANGLE: return #"▲";
default: return #"?";
}
}
To incorporate "number":
- (NSString *)getShape: (NSNumber *)shape numberOfShapes: (NSNumber *)number
{
NSString* result = nil;
switch([number intValue]): {
case SQUARE: result = #"■"; break;
case CIRCLE: result = #"●"; break;
case TRIANGLE: result= #"▲"; break;
default: result = #"?";
}
NSString* realResult = #"";
for (int i = 0; i < number; i++) {
realResult = [realResult stringByAppendingString:result];
}
return realResult;
}
You could neaten it up by using a char value for result, but I'm too lazy.

NSUSerDefaults with NSMutableArray sort by desc order

I'm breaking my head on why descending order sort is not working with the following code. I wanted to limit by top 5 scores and other logic. The scores would look like this: 22/30, 12/18, 34/38, 23/32 etc. I added/removed SortDescriptor to sort by descending order and it seems to work for the first 3 items but then is not sorting properly. Can somebody help?
- (NSMutableArray*) method1:(NSString *) mode byScore: (NSString *) score
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *array = [[defaults objectForKey:mode]mutableCopy];
if (!array)
{
array = [[NSMutableArray alloc] init];
}
NSLog(#"The content of array is%#", array);
if ([array count] < 5)
{
if (![array containsObject:score])
{
[array addObject:score];
// Need to sort here. But not too sure this is the right place
NSLog(#"The content of the sorted array upto 5 is%#", array);
}
}
else
{
if (![array containsObject:score])
{
if ([array lastObject] < score)
{
[array addObject:score];
// Need to sort here before I remove the last object
[array removeLastObject];
NSLog(#"The content of the sorted array else is%#",array);
}
}
}
[defaults setObject:array forKey:mode];
[defaults synchronize];
// I want the array in NSUserDefaults to be sorted in desc order
// don't know what to return here ==> the array object or the defaults object cast to NSMutableArray?
}
Helper function
static NSComparisonResult CompareFloats( float a, float b )
{
if ( a < b ) { return NSOrderedAscending ; }
else if ( a > b ) { return NSOrderedDescending ; }
return NSOrderedSame ;
}
Category on NSString
#implementation NSString (Stuff)
-(float)floatValueForFraction
{
NSArray * components = [ self componentsSeparatedByString:#"/" ] ;
return [ components[0] floatValue ] / [ components[1] floatValue ] ;
}
#end
Your method:
- (void)addScore:(NSString*)score forMode:(NSString*)mode
{
NSUserDefaults * defaults = [ NSUserDefaults standardUserDefaults ] ;
NSArray * scores = [ defaults objectForKey:mode ] ;
scores = scores ? [ scores arrayByAddingObject:score ] : #[ score ] ;
scores = [ scores sortedArrayUsingComparator:^(NSString * a, NSString * b){
return CompareFloats( [ a floatValueForFraction ], [ b floatValueForFraction ] ) ;
}]
if ( scores.count > 5 ) { scores = [ scores subarrayWithRange:(NSRange){ .length = 5 } ] ; }
[ default setObject:scores forKey:mode ] ;
}
If you want the updated high scores after calling this method, just use [ [ NSUserDefaults standardUserDefaults ] objectForKey:<mode> ]. It's better to have your methods just do one thing.
One approach to sorting array:
First define a block getNumeratorAndDenominatorFromScoreString as follows:
BOOL (^getNumeratorAndDenominatorFromScoreString)(NSString *, NSInteger *, NSInteger *) = ^(NSString *scoreString, NSInteger *numeratorOut, NSInteger *denominatorOut) {
BOOL res = NO;
NSArray *components = [scoreString componentsSeparatedByString:#"/"];
if (components &&
[components count] == 2) {
res = YES;
if (numeratorOut) {
NSNumber *numeratorNumber = [components objectAtIndex:0];
*numeratorOut = [numeratorNumber integerValue];
}
if (denominatorOut) {
NSNumber *denominatorNumber = [components objectAtIndex:1];
*denominatorOut = [denominatorNumber integerValue];
}
}
return res;
};
Then use this block together with -[NSArray sortedArrayUsingComparator] to sort array:
NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
NSComparisonResult res = NSOrderedSame;
NSString *score1 = (NSString *)obj1;
NSString *score2 = (NSString *)obj2;
NSInteger numerator1, denominator1, numerator2, denominator2;
BOOL res1, res2;
res1 = getNumeratorAndDenominatorFromScoreString(score1, &numerator1, &denominator1);
res2 = getNumeratorAndDenominatorFromScoreString(score2, &numerator2, &denominator2);
if (res1
&& res2) {
CGFloat value1 = ((CGFloat)numerator1)/((CGFloat)denominator1);
CGFloat value2 = ((CGFloat)numerator2)/((CGFloat)denominator2);
if (value1 > value2) {
res = NSOrderedDescending;
} else if (value1 < value2) {
res = NSOrderedAscending;
}
}
return res;
}];
This will order array from least to greatest. To order from greatest to least, just replace
if (value1 > value2) {
res = NSOrderedDescending;
} else if (value1 < value2) {
res = NSOrderedAscending;
}
with
if (value1 > value2) {
res = NSOrderedAscending;
} else if (value1 < value2) {
res = NSOrderedDescending;
}
A readable structure for this method would be, in [mostly not] pseudocode
- (void)addScoreToHighscores:(NSString *)score withMethod:(NSString *)mode
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *currentHighscores = [defaults arrayForKey:mode];
if (!currentHighscores) currentHighscores = [NSArray array];
if (![currentHighscores containsObject:score]) {
currentHighscores = [currentHighscores arrayByAddingObject:score];
//sort currentHighscores: adapt the above code so that we have
BOOL (^getNumeratorAndDenominatorFromScoreString)(NSString *, NSInteger *, NSInteger *) = //as above
NSArray *newHighscores = [currentHighscores sortedArrayUsingComparator:^(id obj1, id obj2) {
//as above
}];
//truncate newHighscores
if ([newHighscores count] > 5) {
newHighscores = [newHighscores subarrayWithRange:NSMakeRange(0,5)];
}
[defaults setObject:newHighscores forKey:mode];
} else {
//since score is already in currentHighscores, we're done.
return;
}
}
If you need to screen out scores the strings for which are not equal but the evaluations of the fractions for which are equal (#"1/2" and #"5/10"), you'll need to be more clever.
Here is the full code sketched out above:
- (void)addScoreToHighscores:(NSString *)score withMethod:(NSString *)mode
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *currentHighscores = [defaults arrayForKey:mode];
if (!currentHighscores) currentHighscores = [NSArray array];
if (![currentHighscores containsObject:score]) {
currentHighscores = [currentHighscores arrayByAddingObject:score];
//sort currentHighscores: adapt the above code so that we have
BOOL (^getNumeratorAndDenominatorFromScoreString)(NSString *, NSInteger *, NSInteger *) = ^(NSString *scoreString, NSInteger *numeratorOut, NSInteger *denominatorOut) {
BOOL res = NO;
NSArray *components = [scoreString componentsSeparatedByString:#"/"];
if (components &&
[components count] == 2) {
res = YES;
if (numeratorOut) {
NSNumber *numeratorNumber = [components objectAtIndex:0];
*numeratorOut = [numeratorNumber integerValue];
}
if (denominatorOut) {
NSNumber *denominatorNumber = [components objectAtIndex:1];
*denominatorOut = [denominatorNumber integerValue];
}
}
return res;
};
NSArray *newHighscores = [currentHighscores sortedArrayUsingComparator:^(id obj1, id obj2) {
NSComparisonResult res = NSOrderedSame;
NSString *score1 = (NSString *)obj1;
NSString *score2 = (NSString *)obj2;
NSInteger numerator1, denominator1, numerator2, denominator2;
BOOL res1, res2;
res1 = getNumeratorAndDenominatorFromScoreString(score1, &numerator1, &denominator1);
res2 = getNumeratorAndDenominatorFromScoreString(score2, &numerator2, &denominator2);
if (res1
&& res2) {
CGFloat value1 = ((CGFloat)numerator1)/((CGFloat)denominator1);
CGFloat value2 = ((CGFloat)numerator2)/((CGFloat)denominator2);
if (value1 > value2) {
res = NSOrderedDescending;
} else if (value1 < value2) {
res = NSOrderedAscending;
}
}
return res;
}];
//truncate newHighscores
if ([newHighscores count] > 5) {
newHighscores = [newHighscores subarrayWithRange:NSMakeRange(0,5)];
}
[defaults setObject:newHighscores forKey:mode];
} else {
//since score is already in currentHighscores, we're done.
return;
}
}

Absolute Value (abs) of NSDecimalNumber without loss of precision

I need to get the absolute value of an NSDecimalNumber without loss of precision. I can't seem to find a way to do this without converting to decimal or float (with a loss of precision). Is there a method to do this?
You can use the built-in functions of the NSDecimalNumber class to compare the number to zero, then multiply by -1 as appropriate. For example:
- (NSDecimalNumber *)abs:(NSDecimalNumber *)num {
if ([num compare:[NSDecimalNumber zero]] == NSOrderedAscending) {
// Number is negative. Multiply by -1
NSDecimalNumber * negativeOne = [NSDecimalNumber decimalNumberWithMantissa:1
exponent:0
isNegative:YES];
return [num decimalNumberByMultiplyingBy:negativeOne];
} else {
return num;
}
}
Since this method works solely with NSDecimalNumbers, there's no loss of precision.
There is a minor error in the answer: negativeOne should be a pointer. Here is a revised answer:
- (NSDecimalNumber *)abs:(NSDecimalNumber *)num {
if [myNumber compare:[NSDecimalNumber zero]] == NSOrderedAscending) {
// Number is negative. Multiply by -1
NSDecimalNumber *negativeOne = [NSDecimalNumber decimalNumberWithMantissa:1
exponent:0
isNegative:YES];
return [num decimalNumberByMultiplyingBy:negativeOne];
} else {
return num;
}
}
And here is a version suitable to be used as a category on NSDecimalNumber:
- (NSDecimalNumber *)abs {
if ([self compare:[NSDecimalNumber zero]] == NSOrderedAscending) {
// Number is negative. Multiply by -1
NSDecimalNumber *negativeOne = [NSDecimalNumber decimalNumberWithMantissa:1
exponent:0
isNegative:YES];
return [self decimalNumberByMultiplyingBy:negativeOne];
} else {
return self;
}
}
If you do not want to multiply it, zero subtracting the value will do the trick.
- (NSDecimalNumber *)abs:(NSDecimalNumber *)num {
if ([num compare:[NSDecimalNumber zero]] == NSOrderedAscending) {
// negative value
return [[NSDecimalNumber zero] decimalNumberBySubtracting:num];
} else {
return num;
}
}
Swift 4.0 variant:
extension NSDecimalNumber {
func absoluteValue() -> NSDecimalNumber {
if self.compare(NSDecimalNumber.zero) == .orderedAscending {
let negativeValue = NSDecimalNumber(mantissa: 1, exponent: 0, isNegative: true)
return self.multiplying(by: negativeValue)
} else {
return self
}
}
}
usage
let sum = NSDecimalNumber(value: -123.33)
sum.absoluteValue()
Swift 5
You can use the magnitude property of Decimal:
extension NSDecimalNumber {
var absValue: Self {
.init(decimal: decimalValue.magnitude)
}
}
let number = NSDecimalNumber(string: "-123.456")
let absNumber = number.absValue // 123.456