Using NSNumberFormatter to Display Extremely Large Numbers - ios

I'm working on a calculator app for the iPhone.
I've encountered an issue using NSNumberFormatter when displaying results of calculations that are greater than 15 digits.
For example, the result of the calculation 111,111,111,111,111 x 2 = 222,222,222,222,222 (correct). However, the result of the calculation 1,111,111,111,111,111 x 2 = 2,222,222,222,220 (wrong!).
Is there a limit to how many digits that can be displayed using NSNumberFormatter, or can someone tell me why the calculation result is not displaying correctly??
Thanks in advance!
Sample code:
double absResult = fabs(_result);
NSLog(#"_result = %f", _result);
//_result is the result of the calculation that will be placed onto displayLabel below
// _result = 2,222,222,222,222,222 for the calculation 1,111,111,111,111,111 x 2
NSNumberFormatter *displayString = [[NSNumberFormatter alloc]init];
//** Adds zero before decimal point **
[displayString setMinimumIntegerDigits:1];
[displayString setUsesGroupingSeparator:YES];
[displayString setGroupingSeparator:#","];
[displayString setGroupingSize:3];
[displayString setMinimumFractionDigits:2];
[displayString setMaximumFractionDigits:2];
NSString *resultString = [displayString stringFromNumber:[NSNumber numberWithDouble: _result]];
self.displayLabel.text = resultString;

This has nothing to do with NSNumberFormatter's capabilities but only with the precision of double floating point numbers. The double type uses 8 bytes to store 53 bit of mantissa and 11 bit exponent. See the Wikipedia article.
The 53 bit mantissa is too small to exactly represent 222,222,222,222,222, so the double is set to the closest possible representation.
If you want more precision you should try NSDecimalNumber which is better suited for a calculator app anyway.

Related

NSNumberFormatter only formats up to 14 significant digits

I saw some code trying to format lat long using the following NSNumberFormatter.
NSNumberFormatter * sut = [[NSNumberFormatter alloc] init];
[sut setMaximumFractionDigits:20]; // also tried set to 15 or 16, not working too.
[sut setMaximumSignificantDigits:20];
NSLocale *enUSPOSIXLocale = [[NSLocale alloc]initWithLocaleIdentifier:#"en_US_POSIX"];
[sut setLocale:enUSPOSIXLocale];
NSString *expected = #"1.299129258067496"; // 16 significants digits
NSString *actual = [sut stringFromNumber:#(1.299129258067496)];
[[actual should] equal:expected];// Failed to pass, the actual is #"1.2991292580675"
Although in this case, we may not need to use the NSNumberFormatter to get the correct result, I'm wondering why NSNumberFormatter only returns string of up to 14 significant digits.
It only shows 14 decimal places because the double type rounds at 15 decimal places. This worked for me because you can set the number of decimal places shown.
[NSString stringWithFormat:#"%.20f", 1.299129258067496]
Just make sure the number of decimal places does not exceed the number otherwise the program makes up numbers to fill the rest. Hope this helps.

NSNumber is not the same as its floatValue

I came up strange behaviour of NSNumber.
basically my code gets json from API call:
id json_response = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingMutableLeaves
error:&error];
and stored as NSDictionary
then I got NSNumber from it and using NSNumberFormatter to convert it to a string, but...
NSNumber * avgNumber = [[_tableDataArray objectAtIndex:index ]
objectForKey:kTrendsKeyAvgScoreFloat];
here is formatting output:
NSNumberFormatter * formatter = [[NSNumberFormatter alloc]init];
[formatter setDecimalSeparator:#","];
[formatter setMaximumFractionDigits:1];
[formatter setMinimumFractionDigits:0];
[formatter setGroupingSeparator:#"."];
[formatter setGroupingSize:3];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSString * retString = [formatter stringFromNumber:number];
I got strange behaviour when NSNumber has value of 1.05
(__NSCFNumber *) avgNumber = 0x0000000155923800 (float)1.050000
But with code above it was printing (instead of expected 1.1):
1
However when I checked float value
float avg = [avgNumber floatValue];
it turns out that
Printing description of avg:
(double) avg = 1.0499999523162842
Even if I try to round it to 2 decimal points
avg = roundf(avg * 100); // here 105
avg = avg / 100; // and here is 1.0499999523162842 again
However if I test the code and put 1.05 manually inside NSDictionary everything works as expected.
If someone could explain why is that? And how to preserve every time proper display?
The problem is that a value of 1.05 cannot be represented exactly by a floating point number. This is only possible for values that are sums of (positive or negative) powers of 2. One can, thus, exactly represent values like 1 (=2^0) or 1.03125 (= 2^0 + 2^-5), but the best approximation to 1.5 is in your case 1.0499999523162842.
Now when you initialize an NSNumberFormatter, and do no set its roundingMode property, its default value is kCFNumberFormatterRoundHalfEven, which means the number will be rounded „towards the nearest integer, or towards an even number if equidistant.“
So if your number is 1.0499999523162842, it will be rounded to 1.0, and this is output as 1.

NSNumberFormatter glitch

I have a strange problem. It only seems to present on a tester's iPhone 5s.
It works correctly on an iPhone 5, 6 and 6 plus, all running the latest iOS (8.3).
This is the code
-(NSString *) commaString:(double)number
{
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
[numberFormatter setGroupingSize:3];
[numberFormatter setMaximumSignificantDigits:9];
NSString *numberAsString = [numberFormatter stringFromNumber:[NSNumber numberWithDouble: number]];
return numberAsString;
}
The application is a calculator and is usually presenting correctly like this
It is usually showing correctly like this
but on this iPhone 5s it is showing like this
I thought the setMaximumSignificantDigits would have nipped this in the bud, but it's still showing the same. Could this be some strange localisation thing? I don't think it is as his iPhone 6 it is showing correctly also.
Thanks
Luke
If your goal is to simply have commas every three characters, all you need to do is:
NSNumberFormatter * numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
NSNumber * numberFromString = [numberFormatter numberFromString:currentTextWithoutCommas];
NSString * formattedNumberString = [numberFormatter stringFromNumber:numberFromString];
If you're finding that there's an issue with localizing the formatter, then set secondaryGroupingSize to 3. As written in the docs for NSNumberFormatter:
Some locales allow the specification of another grouping size for larger numbers. For example, some locales may represent a number such as 61, 242, 378.46 (as in the United States) as 6,12,42,378.46. In this case, the secondary grouping size (covering the groups of digits furthest from the decimal point) is 2.
Also, you can check out Formatting Numbers on iOSDeveloperTips for a brief, high-level overview.

Round an NSString containing a decimal to two decimal digits

Given the NSString "1.625", I want to round this to "1.63".
How in the world do I do that?
This is what i have now:
NSString *rateString = [NSString stringWithFormat:#"%.2f", [#"1.63" doubleValue]];
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
item.rate = [f numberFromString:rateString];;
However, doubleValue converts 1.625 to 1.6249999999
So when I round it to two decimal digits with #"%.2f", I end up with 1.62!
If you wanna round to the nearest hundredths, multiply by 100, increment by .5 and divide by 100. Then get the floor of that value.
double rate = [#"1.625" doubleValue];
double roundedNumber = floor(rate * 100 + 0.5) / 100;
NSString *rateString = [NSString stringWithFormat:#"%.2f", roundedNumber];
NSLog(#"rate: %#", rateString);
Running this then outputting the result:
2015-01-13 15:41:08.702 Sandbox[22027:883332] rate: 1.63
If you need high precision what you really need is NSDecimalNumberclass maybe coupled with NSDecimalNumberHandler if don't need to configure all details, or NSDecimalNumberBehaviors if need absolute control. This is the quickest solution to keep 2 decimal digits (the 'scale' value in handler init):
NSDecimalNumberHandler *handler = [[NSDecimalNumberHandler alloc]initWithRoundingMode:NSRoundBankers
scale:2
raiseOnExactness:NO
raiseOnOverflow:NO
raiseOnUnderflow:NO
raiseOnDivideByZero:NO];
[NSDecimalNumber setDefaultBehavior:handler];
NSString *string = #"1.63";
NSDecimalNumber *number = [NSDecimalNumber decimalNumberWithString:string];
NSDecimalNumber docs:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDecimalNumber_Class/index.html
NSDecimalNumberHandler docs:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSDecimalNumberHandler_Class/index.html
NSDecimalNumberBehaviors docs:
https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Protocols/NSDecimalNumberBehaviors_Protocol/index.html

"isnan" doesn't seem to work

Simply trying to catch non numeric input
Read MANY items . Tried decimalDigitCharacterSet (found it hard to believe that something that starts with the word "decimal" doesn't contain a decimal). Tried mutable character set to add the decimal. Been working to include "10.5" with "96" and still exclude "abc".
the following code produces "IS a number" no matter what I put in textbox1
double whatTheHey;
whatTheHey = _textBox1.text.doubleValue;
if isnan(whatTheHey) {
_textBox2.text = #"NOT a number > ";
}
if (!isnan(whatTheHey)) {
_textBox2.text = #"IS a number > ";
}
10.5 , 99 , qwerty all yield "IS a number"
This seems like a heck of a lot of work just to catch non numeric input.
Does anybody have any blatantky simple examples of working code to catch non numeric but accept numbers with decimal in them?
NaN does not literally mean "anything that is not a number". It is the name of a specific value — namely one floats can have after certain indeterminate operations such as dividing zero by zero or taking the square root of a negative number. See the Wikipedia entry for more history.
To actually parse the numeric value of a string, you probably want to look into NSNumberFormatter:
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber *a = [numberFormatter numberFromString:#"10.5"];
NSNumber *b = [numberFormatter numberFromString:#"96"];
NSNumber *c = [numberFormatter numberFromString:#"abc"];
NSLog(#"a: %#, b: %#, c: %#", a, b, c);
Yields:
a: 10.5, b: 96, c: (null)
A simpler (if less flexible) solution that meets your specific criteria might be:
BOOL isNumber(NSString *aString){
return [aString integerValue] || [aString floatValue];
}
But if you're writing for iOS (or OS X), you really ought to get comfortable with the NSFormatters. They'll make your life a lot easier.
to check wether a string is numeric or not use the following piece of code.
NSString *newString = #"11111";
NSNumberFormatter *nf = [NSNumberFormatter new];
BOOL isDecimal = [nf numberFromString:newString] != nil;
Try this. I think it should do what you need:
- (BOOL)isStringNumeric:(NSString *)input {
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
NSNumber *number = [numberFormatter numberFromString:input];
return (number != nil);
}

Resources