Objective C: Formatting numbers with "just enough" decimal places - objective-c

Is there a quick way to format numbers (e.g., when using appendFormat) so that only enough decimal places are shown? E.g., 4.23100 is shown as 4.231, while 5.0000 is shown as just 5.
Thanks for reading.

Use %g instead of %f
double n = 1234.5678;
NSString str = [NSString stringWithFormat:#"number is %g", n];

Use %g with the appropriate precision.
double number = 1234.123;
NSLog(#"Number equals: %.8g", number)
As referenced here, the %g conversion will default to 6 significant figures unless a maximum precision is specified. %.7g or larger will all give the desired float.

I couldn't find a built-in way to do this at first, so I built a quick function (below) if anyone is interested. That said, I believe %g (Thanks MSgambel, Roger Gilbrat) and the NSNumberFormatter class (Thanks Inafziger, David Nedrow) do this much more succinctly, so they're probably the better option!
+ (NSString *)trimmedFraction:(float)fraction{
int i=0;
float numDiff=0;
// Loop through possible decimal values (in this case, maximum of 6)
for (i=0;i<=6;i++){
// Calculate difference between fraction and rounded fraction
numDiff=round(fraction*pow(10,i))/pow(10,i)-fraction;
// Check if difference is less than half of the smallest possible value
if (fabsf(numDiff)<(0.5*pow(10,-6))){
break;
}
}
// Return string of truncated fraction
NSString *stringPattern=[NSString stringWithFormat:#"%%0.%df",i];
return [NSString stringWithFormat:stringPattern,fraction];
}
In the code you can specify the maximum number of decimal spaces (e.g., 6 in this case). Then you just send it a fraction and it returns a string with only the relevant decimals. For example, send it 4.231000 and it returns #"4.231", send it 5.000000 and it returns #"5". (Again, probably better to just use %g and NSNumberWithFormatter).

If you store the original values as strings instead of floating-point numbers, the question makes a lot of sense.
I handle this using a category on NSString:
+ (NSString *) stringForNotANumber {
static NSString *singleton;
if (!singleton) singleton = #"NaN";
return singleton;
}
- (NSUInteger) numberOfDecimalPlaces {
NSString *strValue = self;
// If nil, return -1
if (!strValue) return -1;
// If non-numeric, return -1
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
[f setMaximumFractionDigits:128];
NSNumber *numValue = [f numberFromString:strValue];
if (!numValue) return -1;
// Count digits after decimal point in original input
NSRange range = [strValue rangeOfString:#"."];
if (NSNotFound == range.location) return 0;
return [strValue substringFromIndex:range.location+1].length;
}
- (NSString *) plus:(NSString *) addend1 {
NSString *addend2 = self;
if (!addend1 || !addend2) return nil;
if (addend1 == [NSString stringForNotANumber] ||
addend2 == [NSString stringForNotANumber])
return [NSString stringForNotANumber];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSNumber *num1 = [f numberFromString:addend1];
NSNumber *num2 = [f numberFromString:addend2];
if (!num1 || !num2) return [NSString stringForNotANumber];
double sum = num1.doubleValue + num2.doubleValue;
[f setMinimumFractionDigits:MAX(addend1.numberOfDecimalPlaces,
addend2.numberOfDecimalPlaces)];
return [f stringFromNumber:[NSNumber numberWithDouble:sum]];
}

Related

Not seeing the decimal from a string when converted into NSDecimalNumer

I can't see to find the answer to this, but I have a string that has a decimal point in it, and when I try to convert it to a NSDecimalNumber I only get back the whole number, not the decimal or what would come after it. This is what I am trying:
someText.text = #"200.00";
tempAmountOwed = [NSDecimalNumber decimalNumberWithString:someText.text]; // gives me back 200
I can't seem to figure out if the decimalNumberWithString method is stripping out my decimal and ignoring what comes after it.
Thanks for the help!
You can use the method decimalNumberWithString: locale: method.
for eg:-
The code:
NSLog(#"%#", [NSDecimalNumber decimalNumberWithString:#"200.00"]);
NSLog(#"%#", [NSDecimalNumber decimalNumberWithString:#"200.00" locale:NSLocale.currentLocale]);
Gives following log:
200
200.00
Hope this Helps!
That's perfectly normal. If your decimal String doesn't contain fractions it won't print them. If you want to print them you can use a NSNumberFormatter or convert it to a float and print it with %.2f to do so:
NSString *text = #"200.00";
NSDecimalNumber *number = [NSDecimalNumber decimalNumberWithString:text];
NSLog(#"%#", number); //this will print "200"
//solution #1
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
numberFormatter.numberStyle = NSNumberFormatterDecimalStyle;
numberFormatter.minimumFractionDigits = 2;
NSLog(#"%#", [numberFormatter stringFromNumber:number]); //this will print "200.00"
//solution #2
CGFloat number = [text floatValue];
NSLog(#"%.2f", number); //this will print "200.00"

How to restrict number of fraction digits when parsing number from string?

I want to restrict the number of fraction digits a user is allowed to enter into a UITextField that only accepts (localized) numeric input.
Example with 4 fraction digits allowed:
Good: 42, 10.123, 12345.2345
Bad: 0.123456, 6.54321
Right now, I'm using NSNumberFormatter's numberFromString: in the UITextField delegate's textField:shouldChangeCharactersInRange:replacementString: to determine whether it's a legal numeric value.
Unfortunately, NSNumberFormatter seems to ignore maximumFractionDigits in numberFromString:. In tests using getObjectValue:forString:range:error: I had the same problem, and range also was the full length of the string afterwards (unless I start entering letters; then range indicates only the part of the string with digits):
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
formatter.maximumFractionDigits = 3;
formatter.roundingMode = NSNumberFormatterRoundHalfUp;
formatter.generatesDecimalNumbers = YES;
NSDecimalNumber* n = (NSDecimalNumber*)[formatter numberFromString:#"10.12345"];
NSLog(#"Number: %#", n.description); // expected: 10.123, but is: 10.12345
How to best restrict the number of fraction digits in user input?
after you get the unrestricted number, you can use stringWithFormat on that number to create a string with a certain number of decimal places.
eg.
double number = myTextField.text.doubleValue;
NSString *restrictedString = [NSString stringWithFormat:#"%.4f", number];
There are a few ways to do this, but the easiest is probably to split the string into two parts (you will have to localize the '.') and check the length of the second part, like this:
- (BOOL)LNNumberIsValid:(NSString *)string
{
NSArray *numArray = [string componentsSeparatedByString:#"."];
if ([numArray count] == 2)
if ([[numArray objectAtIndex:1] length] > 4)
return NO;
return YES;
}
// Tests
NSLog(#"42: %i", [self LNNumberIsValid:#"42"]); // 1
NSLog(#"10.123: %i", [self LNNumberIsValid:#"10.123"]); // 1
NSLog(#"12345.2345: %i", [self LNNumberIsValid:#"12345.2345"]); // 1
NSLog(#"0.123456: %i", [self LNNumberIsValid:#"0.123456"]); // 0
NSLog(#"6.54321: %i", [self LNNumberIsValid:#"6.54321"]); // 0
EDIT:
The problem with the code that you added to your question is that you are printing the description of the NSDecimalNumber, which is not localized or limited to the number of digits. The NSDecimalNumber itself stores everything that you give it, so you need to change the original string (like my example above) if you want to change that. However, once you have your NSDecimalNumber, you can use the same number formatter to convert it back to a string in the format that you like:
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
formatter.maximumFractionDigits = 3;
formatter.roundingMode = NSNumberFormatterRoundHalfUp;
formatter.generatesDecimalNumbers = YES;
NSDecimalNumber* n = (NSDecimalNumber*)[formatter numberFromString:#"10.12345"];
NSString *s = [formatter stringFromNumber:n];
NSLog(#"Number: %#", s); // expected: 10.123, and is: 10.123
The way I solved this is by checking the position of the decimal separator and making sure that the insertion either is before that position or the insertion would not exceed the maximum number of fraction digits.
Also, I check that the input of a new separator does not occur at a place that would lead to more then the allowed fraction digits and that not more than one separators can be inserted
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString *separator = self.numberFormatter.decimalSeparator;
if(string.length == 0) {
// Empty String means deletion, always possible
return YES;
}
// Check for valid characters (0123456789 + decimal Separator)
for (int i = 0; i < [string length]; ++i) {
unichar c = [string characterAtIndex:i];
if (![self.legalCharSet characterIsMember:c])
{
return NO;
}
}
// Checks if input is separator
if([string isEqualToString:separator]) {
// Check that separator insertion would not lead to more than 2 fraction digits and that not more than one separators are inserted
// (the MIN() makes sure that length - kMaxFractionDigits won’t be below 0 as length and location are NSUIntegers)
return range.location >= self.valueField.text.length - MIN(self.valueField.text.length,kMaxFractionDigits) && [self.valueField.text containsString:separator] == NO;
} else {
// Check if a separator is already included in the string
NSRange separatorPos = [self.valueField.text rangeOfString: separator];
if(separatorPos.location != NSNotFound) {
// Make sure that either the input is before the decimal separator or that the fraction digits would not exceed the maximum fraction digits.
NSInteger fractionDigits = self.valueField.text.length - (separatorPos.location + 1);
return fractionDigits + string.length <= kMaxFractionDigits || range.location <= separatorPos.location;
}
}
return YES;
}
The method may not be bullet proof but it should be sufficient for common text insertions.

Is there any method that can take "one" and return "1"?

I've looked at NSNumberFormatter, but that hasn't worked, so is there a way of parsing written numbers and turning them in to actual numbers?
Something like this would work (for positive whole numbers anyway). This is just a starting point, you would have to check to see that the words were correct and maybe ignore capitalization to make it more robust:
[self parseNumberWords:#"five two three"];
-(NSInteger)parseNumberWords:(NSString *)input {
NSArray *wordArray = [NSArray arrayWithObjects:#"zero",#"one",#"two",#"three",#"four",#"five",#"six",#"seven",#"eight",#"nine", nil];
NSArray *words = [input componentsSeparatedByString:#" "];
NSInteger num = 0;
NSInteger j =0;
for (NSInteger i = [words count]; i>0 ;i--) {
num = num + [wordArray indexOfObject:[words objectAtIndex:i-1]] * pow(10, j);
j = j+1;
}
NSLog(#"%ld",num);
return num;
}
NSNumberFormatter will get you some of the way there, via NSNumberFormatterSpellOutStyle. The basic formatting that NSNumber does will finish it off.
NSNumberFormatter * nf = [[NSNumberFormatter alloc] init];
[nf setNumberStyle:NSNumberFormatterSpellOutStyle];
NSString * numberWordString = #"three one two";
NSMutableString * digitString = [[NSMutableString alloc] init];
// Break up the input string at spaces and iterate over the result
for(NSString * s in [numberWordString componentsSeparatedByString:#" "]){
// Let the formatter turn each string into an NSNumber, then get
// the stringValue from that, which will be a digit.
[digitString appendString:[[nf numberFromString:s] stringValue]] ;
}
NSLog(#"%#", digitString); // prints "312"
Obviously, you'll have to put some work in to handle different input formats, lowercase, bad input (this will crash if nf fails to format -- it'll return nil which is an illegal argument to appendString:), etc.

Limiting both the fractional and total number of digits when formatting a float for display

I need to print a float value in area of limited width most efficiently. I'm using an NSNumberFormatter, and I set two numbers after the decimal point as the default, so that when I have a number like 234.25 it is printed as is: 234.25. But when I have 1234.25 I want it to be printed as: 1234.3, and 11234.25 should be printed 11234.
I need a maximum of two digits after the point, and a maximum of five digits overall if I have digits after the point, but it also should print more than five digits if the integer part has more.
I don't see ability to limit the total number of digits in NSNumberFormatter. Does this mean that I should write my own function to format numbers in this way? If so, then what is the correct way of getting the count of digits in the integer and fractional parts of a number? I would also prefer working with CGFLoat, rather than NSNumber to avoid extra type conversions.
You're looking for a combination of "maximum significant digits" and "maximum fraction digits", along with particular rounding behavior. NSNumberFormatter is equal to the task:
float twofortythreetwentyfive = 234.25;
float onetwothreefourtwentyfive = 1234.25;
float eleventwothreefourtwentyfive = 11234.25;
NSNumberFormatter * formatter = [[NSNumberFormatter alloc] init];
[formatter setUsesSignificantDigits:YES];
[formatter setMaximumSignificantDigits:5];
[formatter setMaximumFractionDigits:2];
[formatter setRoundingMode:NSNumberFormatterRoundCeiling];
NSLog(#"%#", [formatter stringFromNumber:[NSNumber numberWithFloat:twofortythreetwentyfive]]);
NSLog(#"%#", [formatter stringFromNumber:[NSNumber numberWithFloat:onetwothreefourtwentyfive]]);
NSLog(#"%#", [formatter stringFromNumber:[NSNumber numberWithFloat:eleventwothreefourtwentyfive]]);
Result:
2012-04-26 16:32:04.481 SignificantDigits[11565:707] 234.25
2012-04-26 16:32:04.482 SignificantDigits[11565:707] 1234.3
2012-04-26 16:32:04.483 SignificantDigits[11565:707] 11235
Code :
#define INTPARTSTR(X) [NSString stringWithFormat:#"%d",(int)X]
#define DECPARTSTR(X) [NSString stringWithFormat:#"%d",(int)(((float)X-(int)X)*100)]
- (NSString*)formatFloat:(float)f
{
NSString* result;
result = [NSString stringWithFormat:#"%.2f",f];
if ([DECPARTSTR(f) isEqualToString:#"0"]) return INTPARTSTR(f);
if ([INTPARTSTR(f) length]==5) return INTPARTSTR(f);
if ([result length]>5)
{
int diff = (int)[result length]-7;
NSString* newResult = #"";
for (int i=0; i<[result length]-diff-1; i++)
newResult = [newResult stringByAppendingFormat:#"%c",[result characterAtIndex:i]];
return newResult;
}
return result;
}
Testing it :
- (void)awakeFromNib
{
NSLog(#"%#",[self formatFloat:234.63]);
NSLog(#"%#",[self formatFloat:1234.65]);
NSLog(#"%#",[self formatFloat:11234.65]);
NSLog(#"%#",[self formatFloat:11234]);
}
Output :
2012-04-26 19:27:24.429 newProj[1798:903] 234.63
2012-04-26 19:27:24.432 newProj[1798:903] 1234.6
2012-04-26 19:27:24.432 newProj[1798:903] 11234
2012-04-26 19:27:24.432 newProj[1798:903] 11234
Here is how I implemented this in my code. I don't know how efficient it is, I hope not bad.
So I create a global NSNumberFormatter
NSNumberFormatter* numFormatter;
and initialize it somewhere:
numFormatter=[[NSNumberFormatter alloc]init];
Then I format number with the following function:
- (NSString*)formatFloat:(Float32)number withOptimalDigits:(UInt8)optimalDigits maxDecimals:(UInt8)maxDecimals
{
NSString* result;
UInt8 intDigits=(int)log10f(number)+1;
NSLog(#"Formatting %.5f with maxDig: %d maxDec: %d intLength: %d",number,optimalDigits,maxDecimals,intDigits);
numFormatter.maximumFractionDigits=maxDecimals;
if(intDigits>=optimalDigitis-maxDecimals) {
numFormatter.usesSignificantDigits=YES;
numFormatter.maximumSignificantDigits=(intDigits>optimalDigits)?intDigits:optimalDigits;
} else {
numFormatter.usesSignificantDigits=NO;
}
result = [numFormatter stringFromNumber:[NSNumber numberWithFloat:number]];
return result;
}
Is this a bug when using maximumFractionDigits and maximumSignificantDigits together on NSNumberForamtter on iOS 8?
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.maximumFractionDigits = 2;
formatter.maximumSignificantDigits = 3;
NSLog(#"%#", [formatter stringFromNumber:#(0.3333)]); // output 0.333 expected 0.33
It works fine if I only use maximumFractionDigits
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.maximumFractionDigits = 2;
NSLog(#"%#", [formatter stringFromNumber:#(0.3333)]); // output expected .33
NSNumberFormatter maximumFractionDigits and maximumSignificantDigits bug

How to convert an NSString into an NSNumber

How can I convert a NSString containing a number of any primitive data type (e.g. int, float, char, unsigned int, etc.)? The problem is, I don't know which number type the string will contain at runtime.
I have an idea how to do it, but I'm not sure if this works with any type, also unsigned and floating point values:
long long scannedNumber;
NSScanner *scanner = [NSScanner scannerWithString:aString];
[scanner scanLongLong:&scannedNumber];
NSNumber *number = [NSNumber numberWithLongLong: scannedNumber];
Thanks for the help.
Use an NSNumberFormatter:
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
f.numberStyle = NSNumberFormatterDecimalStyle;
NSNumber *myNumber = [f numberFromString:#"42"];
If the string is not a valid number, then myNumber will be nil. If it is a valid number, then you now have all of the NSNumber goodness to figure out what kind of number it actually is.
You can use -[NSString integerValue], -[NSString floatValue], etc. However, the correct (locale-sensitive, etc.) way to do this is to use -[NSNumberFormatter numberFromString:] which will give you an NSNumber converted from the appropriate locale and given the settings of the NSNumberFormatter (including whether it will allow floating point values).
Objective-C
(Note: this method doesn't play nice with difference locales, but is slightly faster than a NSNumberFormatter)
NSNumber *num1 = #([#"42" intValue]);
NSNumber *num2 = #([#"42.42" floatValue]);
Swift
Simple but dirty way
// Swift 1.2
if let intValue = "42".toInt() {
let number1 = NSNumber(integer:intValue)
}
// Swift 2.0
let number2 = Int("42')
// Swift 3.0
NSDecimalNumber(string: "42.42")
// Using NSNumber
let number3 = NSNumber(float:("42.42" as NSString).floatValue)
The extension-way
This is better, really, because it'll play nicely with locales and decimals.
extension String {
var numberValue:NSNumber? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter.number(from: self)
}
}
Now you can simply do:
let someFloat = "42.42".numberValue
let someInt = "42".numberValue
For strings starting with integers, e.g., #"123", #"456 ft", #"7.89", etc., use -[NSString integerValue].
So, #([#"12.8 lbs" integerValue]) is like doing [NSNumber numberWithInteger:12].
You can also do this:
NSNumber *number = #([dictionary[#"id"] intValue]]);
Have fun!
If you know that you receive integers, you could use:
NSString* val = #"12";
[NSNumber numberWithInt:[val intValue]];
Here's a working sample of NSNumberFormatter reading localized number NSString (xCode 3.2.4, osX 10.6), to save others the hours I've just spent messing around. Beware: while it can handle trailing blanks ("8,765.4 " works), this cannot handle leading white space and this cannot handle stray text characters. (Bad input strings: " 8" and "8q" and "8 q".)
NSString *tempStr = #"8,765.4";
// localization allows other thousands separators, also.
NSNumberFormatter * myNumFormatter = [[NSNumberFormatter alloc] init];
[myNumFormatter setLocale:[NSLocale currentLocale]]; // happen by default?
[myNumFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
// next line is very important!
[myNumFormatter setNumberStyle:NSNumberFormatterDecimalStyle]; // crucial
NSNumber *tempNum = [myNumFormatter numberFromString:tempStr];
NSLog(#"string '%#' gives NSNumber '%#' with intValue '%i'",
tempStr, tempNum, [tempNum intValue]);
[myNumFormatter release]; // good citizen
I wanted to convert a string to a double. This above answer didn't quite work for me. But this did: How to do string conversions in Objective-C?
All I pretty much did was:
double myDouble = [myString doubleValue];
Thanks All! I am combined feedback and finally manage to convert from text input ( string ) to Integer. Plus it could tell me whether the input is integer :)
NSNumberFormatter * f = [[NSNumberFormatter alloc] init];
[f setNumberStyle:NSNumberFormatterDecimalStyle];
NSNumber * myNumber = [f numberFromString:thresholdInput.text];
int minThreshold = [myNumber intValue];
NSLog(#"Setting for minThreshold %i", minThreshold);
if ((int)minThreshold < 1 )
{
NSLog(#"Not a number");
}
else
{
NSLog(#"Setting for integer minThreshold %i", minThreshold);
}
[f release];
I think NSDecimalNumber will do it:
Example:
NSNumber *theNumber = [NSDecimalNumber decimalNumberWithString:[stringVariable text]]];
NSDecimalNumber is a subclass of NSNumber, so implicit casting allowed.
What about C's standard atoi?
int num = atoi([scannedNumber cStringUsingEncoding:NSUTF8StringEncoding]);
Do you think there are any caveats?
You can just use [string intValue] or [string floatValue] or [string doubleValue] etc
You can also use NSNumberFormatter class:
you can also do like this code 8.3.3 ios 10.3 support
[NSNumber numberWithInt:[#"put your string here" intValue]]
NSDecimalNumber *myNumber = [NSDecimalNumber decimalNumberWithString:#"123.45"];
NSLog(#"My Number : %#",myNumber);
Try this
NSNumber *yourNumber = [NSNumber numberWithLongLong:[yourString longLongValue]];
Note - I have used longLongValue as per my requirement. You can also use integerValue, longValue, or any other format depending upon your requirement.
Worked in Swift 3
NSDecimalNumber(string: "Your string")
I know this is very late but below code is working for me.
Try this code
NSNumber *number = #([dictionary[#"keyValue"] intValue]]);
This may help you. Thanks
extension String {
var numberValue:NSNumber? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter.number(from: self)
}
}
let someFloat = "12.34".numberValue