Search code examples
objective-ciosnsnumberformatternsdecimalnumber

NSNumberFormatter, NSDecimalNumber and Scientific Notation


I'm having a serious dispute with NSNumberFormatter, and even after going through its extensive documentation, I haven't quite been able to wrap my head around a pretty straightforward issue that I encountered. I hope you guys can help me out.

What I have: an NSDecimalNumber representing a calculation result, displayed in a UITextField
What I need: Scientific notation of that result.

What I'm doing:

-(void)setScientificNotationForTextField:(UITextField*)tf Text:(NSString*)text {

NSString* textBefore = text;

// use scientific notation, i.e. NSNumberFormatterScientificStyle
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
//[formatter setGeneratesDecimalNumbers:YES];
[formatter setNumberStyle:NSNumberFormatterScientificStyle];
NSDecimalNumber* number = (NSDecimalNumber*)[formatter numberFromString:text];
tf.text = [number descriptionWithLocale:[[Utilities sharedUtilities] USLocale]];

NSString* textAfter = tf.text;
// DEBUG
NSLog(@"setScientificNotation | text before = %@, text after = %@", textBefore, textAfter);

[formatter release];
}

What happens:
A certain result may be 0.0099. textBefore will hold that correct value. If I don't tell the formatter to generate decimal numbers (commented out in the above snippet), it will create an NSNumber from an NSDecimalNumber which creates a false result and turns textAfterinto 0.009900000000000001 - a rounding error due to the reduced precision of NSNumber over NSDecimalNumber.
If I do tell the NumberFormatter to generate decimals, it will still create the wrong result . And what's more, where before it would insert the exponent notation (e.g. "1.23456e-10"), it would now generate (and thus display) the full decimal number, which is not what I want.
Again, I'd like to have the formatter use NSDecimalNumber so it doesn't falsify results plus have exponent notation where necessary.


Am I using the class wrong? Did I misinterpret the documentation? Can someone explain why this happens and how I can create the behavior I want? I will of course continue researching and update if I find anything.


Solution

  • You can't just cast an NSNumber to an NSDecimalNumber and expect it to work. If your number is not too complex, you can ditch NSNumberFormatter and try using this instead:

    NSDecimalNumber* number = [NSDecimalNumber decimalNumberWithString:text];
    

    That will give you an actual NSDecimalNumber instance, with its precision.

    Unfortunately, setGeneratesDecimalNumbers: doesn't work properly. It's a known bug. If your number is too complex to work with decimalNumberWithString:, you're probably out of luck with Apple's APIs. Your only options are either parsing the string manually into something NSDecimalNumber can understand or performing some post-processing on the imprecise value given to you by NSNumberFormatter.

    Finally, if you really want a number in scientific notation, why not just use the number formatter you just used? Just call stringFromNumber: to get the formatted value.