Dynamicaly rounding numbers in Objective-C - objective-c

I know that there are many different questions about this sort of topic on SO already, but I couldn't find a way to tailor them all to my specific needs.
What I have is a floating point number that gets sent to me through the network that I need to convert and graph out to the screen. The numbers can range from 5.2, 285.159, 294729172.258, -10734.112, etc. What I would like to do is get the value used to round from one digit below the most significant digit.
Example:
5.2 = 5
285.159 = 300
294729172.258 = 300000000
-10734.112 = -11000
Any advice that can be used to help guide me would be greatly appreciated.

Here's my solution:
int roundMostSignificant(float input)
{
NSNumber *number = [NSNumber numberWithFloat:input];
static NSNumberFormatter *formatter = nil;
if (!formatter)
{
formatter = [NSNumberFormatter new];
[formatter setMinimumSignificantDigits:1];
[formatter setMaximumSignificantDigits:1];
[formatter setUsesSignificantDigits:YES];
}
return [[formatter numberFromString:[formatter stringFromNumber:number]] intValue];
}
Yes, this uses objects, but I think that this will be your best bet in the long run, as it handles rounding, parsing, etc. for you.

There is a NSDecimalNumber and NSDecimalNumberHandler classes which does just that. You can define to which precision and to which direction the numbers should be rounded.
Simple example might be:
NSDecimalNumber *dn = [NSDecimalNumber decimalNumberWithMatnissa:294729172258 exponent:-3 isNegative:NO];
NSDecimalNumberHandler *dnh = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:-6 raiseOnExactness:NO raiseOnOverflow:NO raiseOnUnderflow:NO raiseOnDivideByZero:YES];
NSDecimalNumber *rounded = [dn decimalNumberByRoundingAccordingToBehavior:dnh];
This would probably work for your biggest number.

Related

Large (but representable) integers get parsed as doubles by NSNumberFormatter

I've been using the following method to parse NSString's into NSNumber's:
// (a category method on NSString)
-(NSNumber*) tryParseAsNumber {
NSNumberFormatter* formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
return [formatter numberFromString:self];
}
And I had tests verifying that this was working correctly:
test(#"".tryParseAsNumber == nil);
...
test([(#NSUIntegerMax).description.tryParseAsNumber isEqual:#NSUIntegerMax]);
...
The max-value test started failing when I switched to testing on an iPhone 6, probably because NSUInteger is now 64 bits instead of 32 bits. The value returned by the formatter is now the double 1.844674407370955e+19 instead of the uint64_t 18446744073709551615.
Is there a built-in method that succeeds exactly for all int64s and unsigned int64s, or do I have to implement one myself?
+ [NSNumber numberWithLongLong:]
+ [NSNumber numberWithUnsignedLongLong:]
Have you tried these?
EDIT
I'm not at all certain what it is you'd ultimately do with your instances of NSNumber, but consider that NSDecimalNumber seems to do exactly what you want:
NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:#"18446744073709551615"];
NSLog(#"%#", decNum);
which yields:
2014-09-21 15:11:25.472 Test[1138:812724] 18446744073709551615
Here's another thing to consider: NSDecimalNumber "is a" NSNumber, as it's a subclass of the latter. So it would appear that, whatever you can do with NSNumber, you can do with NSDecimalNumber.
trudyscousin's answer allowed me to figure it out.
NSDecimalNumber decimalNumberWithString: is capable of parsing with full precision, but it lets some bad inputs by (e.g. "88ffhih" gets parsed as 88). On the other hand, NSNumberFormatter numberFromString: always detects bad inputs but loses precision. They have opposite weaknesses.
So... just do both. For example, here's a method that should parse representable NSUIntegers but nothing else:
+(NSNumber*) parseAsNSUIntegerElseNil:(NSString*)decimalText {
// NSNumberFormatter.numberFromString is good at noticing bad inputs, but loses precision for large values
// NSDecimalNumber.decimalNumberWithString has perfect precision, but lets bad inputs through sometimes (e.g. "88ffhih" -> 88)
// We use both to get both accuracy and detection of bad inputs
NSNumberFormatter* formatter = [NSNumberFormatter new];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
if ([formatter numberFromString:decimalText] == nil) {
return nil;
}
NSNumber* value = [NSDecimalNumber decimalNumberWithString:decimalText];
// Discard values not representable by NSUInteger
if (![value isEqual:#(value.unsignedIntegerValue)]) {
return nil;
}
return value;
}

Unexpected behaviour formatting very large decimal numbers in ObjC

I am running into unexpected behaviour formatting very large numbers in ObjC using the NSNumberFormatter.
It seems that the number formatter rounds decimals (NSDecimalNumber) after the fifteenth digit regardless of fraction digits.
The below test fails on values 1,3 and 5.
Two requests:
Any suggestions on alternative code would be greatly appreciated?
I assume the issue is happening due to the usage of a hard-coded digit limit in NSNumberFormatter?
The post here lists a workaround without sufficient description if the problem. Also our application (banking sector) runs across multiple countries and we link the formatting to the user's locale as configured in the backend. This workaround would imply that we write our own number formatter to handle the requirement. Something I do not want to do.
- (void)testFormatterUsingOnlySDK {
NSDecimalNumber *value1 = [NSDecimalNumber decimalNumberWithMantissa: 9423372036854775808u exponent:-3 isNegative:YES];
NSDecimalNumber *value2 = [NSDecimalNumber decimalNumberWithMantissa: 9999999999999990u exponent:-3 isNegative:YES];
NSDecimalNumber *value3 = [NSDecimalNumber decimalNumberWithMantissa: 9999999999999991u exponent:-3 isNegative:YES];
NSDecimalNumber *value4 = [NSDecimalNumber decimalNumberWithMantissa: 99999999999999900u exponent:-4 isNegative:YES];
NSDecimalNumber *value5 = [NSDecimalNumber decimalNumberWithMantissa: 11111111111111110u exponent:-4 isNegative:YES];
NSNumberFormatter *formatter = [[[NSNumberFormatter alloc] init] autorelease];
formatter.allowsFloats = YES;
formatter.maximumFractionDigits = 3;
[self assertStringAreEqualWithActual:[formatter stringFromNumber:value1] andExpeted: #"-9423372036854775.808"];
[self assertStringAreEqualWithActual:[formatter stringFromNumber:value2] andExpeted: #"-9999999999999.99"];
[self assertStringAreEqualWithActual:[formatter stringFromNumber:value3] andExpeted: #"-9999999999999.991"];
[self assertStringAreEqualWithActual:[formatter stringFromNumber:value4] andExpeted: #"-9999999999999.99"];
[self assertStringAreEqualWithActual:[formatter stringFromNumber:value5] andExpeted: #"-1111111111111.111"];
}
- (void)assertStringAreEqualWithActual:(NSString *)actual andExpeted:(NSString *)expected {
STAssertTrue([expected isEqualToString:actual], #"Expected %# but got %#", expected, actual);
}
Unfortunately, NSNumberFormatter doesn't work correctly with NSDecimalNumber.
The problem (very probably) is that the first thing it does is calling doubleValue on the number it wants to format.
See also NSDecimalNumber round long numbers
After many tries with NSNumberFormatter, I have created my own formatter, it's actually very easy:
Handle NaN.
Round using roundToScale:
Get stringValue
Check if negative, remove leading -
Find decimal point (.)
Localize decimal point ([locale objectForKey:NSLocaleDecimalSeparator])
Add grouping separators ([locale objectForKey:NSLocaleGroupingSeparator])
If negative, add leading - or put the number into parenthesis if you are formatting currency.
Done.
You should compile your own NSNumberFormatter from this open source code, changing the prefix. This should allow you to debug into the formatting and to understand why this is happening. Worst case you can submit a patch to Apple.
http://code.google.com/p/cocotron/source/browse/Foundation/NSNumberFormatter.m?r=7542c3a7ef0ef75479e6154a75d304113f5a9738
You've set maximumFractionDigits to two. All of the failing tests have three fraction digits in the expected value. Either the expectation or the code needs to change to match. If I make this change:
formatter.maximumFractionDigits = 3;
then all of your test cases are met.

iTunes Search API, finding Free prices

When searching the iTunes store, I've hit a little problem. I'm trying to change the text of my label to "FREE" when the price is 0.00, as opposed to displaying the price. The problem is, my comparison of the two NSDecimalNumbers fails. Here's where I am currently at.
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterCurrencyStyle];
[formatter setCurrencyCode:self.searchResult.currency];
NSString *price = [formatter stringFromNumber:self.searchResult.price];
NSDecimalNumber *free = [[NSDecimalNumber alloc] initWithFloat:0.00f];
NSLog(#"Price:%#",price);
NSLog(#"Free:%#",free);
NSLog(#"self.searchResult.price: %#", self.searchResult.price);
if (self.searchResult.price == free) {
NSString *freeText = [NSString stringWithFormat:#"FREE"];
self.priceLabel.text = freeText;
}
else {
self.priceLabel.text = price;
}
NSLog(#"priceLabel:%#", self.priceLabel.text);
What's really weird is that the Console even says that both free and del.searchResult.price are the same:
2012-11-24 20:01:16.178 StoreSearch[1987:c07] Price:$0.00
2012-11-24 20:01:16.178 StoreSearch[1987:c07] Free:0
2012-11-24 20:01:16.178 StoreSearch[1987:c07] self.searchResult.price: 0
2012-11-24 20:01:16.179 StoreSearch[1987:c07] priceLabel:$0.00
I'm a little confused by this to be frank. Any help would be appreciated. If you could explain why this has happened so that I can learn not to do it again, then I'd be even more grateful!
Regards,
Mike
you got NSNumber and another NSNumber (NSDecimalNumber) and you do a pointer equal check you either need need to call isEqual or compare the primitive float values
== with objects doesnt compare the value but the object pointer (the memory address)
if(searchResult.price.floatValue == free.floatValue)
note that a float is inprecise and generally should not be used for any calculation or comparison. It is ok in this case though

How to round a float to 2 decimal places?

This is my algorithm to find out the speed of my game.
self.speed=.7-self.score/50;
Now how can I make self.speed round to 2 decimal places?
Note: my answer assumes you only care about the number of decimals for the purpose of displaying the value to the user.
When you setup your NSNumberFormatter to format the number into a string for display, setup the formatter with a maximum of two decimal places.
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setNumberStyle:NSNumberFormatterDecimalStyle];
[formatter setMaximumFractionDigits:2];
NSString *formattedNumber = [formatter stringFromNumber:#(self.speed)];
You have the option of using the setRoundingMode: method if you need a specific round method.
BTW - you shouldn't use a string format for this because it doesn't take the user's locale into account to format the number properly.
floats are handled in IEEE754 format, you can't directly decide how many decimal places will be used.You can directly decide how many bits will be used, or indirectly round the las part of the number doing this:
NSString* str=[NSString stringWithFormat: #"%.2f", number];
number= atof([str UTF8String]);
But like maddy pointed, you only need to round/truncate the unwanted decimal digits only when presenting the number to the user, so you could only use the %.2f format specifier when printing it, or use a formatter.
self.speed = (int)(self.speed * 100 + 0.5) / 100.0;
if you want to have that as a string:
NSString *speedString = [NSString stringWithFormat: #"%.2f", self.speed];

Converting NSString to NSNumber results in too many digits and strange rounding

When converting an NSString, which contains standard decimal numbers with two digits (e.g. 8.20) to a NSNumber, I get (from time to time) extra digits and a strange rounding behavior when logging the result via NSLog or saving it in Core Data (as float or double), e.g. 8.20 -> 8.199999999999999.
This is the code I am using to convert the numbers:
NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
[numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[numberFormatter setMaximumFractionDigits:5];
NSNumber *num = [numberFormatter numberFromString:str];
I do not understand why the conversion to NSNumber messes the number up. What is wrong with my code?
This is just how float and double behaves in C/Objective-C (and many other languages). For example, when you type into python 8.0, the result would be 8.000000000001. I recommend using NSScanner to convert them into primitive number types (double, float).
Why would you use NSNumberFormatter to convert string to float, it would be an overkill,
To convert it just use
NSNumber *num = [NSNumber numberWithFloat:[str floatValue]];
I have encounter the problem when I use NSNumber to save the doublevalue
of 8.28 always show the 8.2799999999...,I guess it cause by a computer numerical precision
Try this code.
+ (NSString *)dealWithDouble:(double)doubleValue {
double d2 = doubleValue;
NSString *d2Str = [NSString stringWithFormat:#"%lf", d2];
NSDecimalNumber *num = [NSDecimalNumber decimalNumberWithString:d2Str];
NSString *str2D = [num stringValue];
return str2D;
}
Don't use floatValue. floatValue only gives 24 bit of precision. doubleValue gives 53 bits of precision. If you use numbers over a million dollars for example, floatValue cannot give you any values that are closer than six cent apart. ($1,000,000 followed by $1,000,000.06 etc. )
The rule is: Don't use float unless you know a reason why you should use float and not double.