Is there a simple way to do tiered coin rounding using standard Cocoa libraries? - cocoa-touch

In 2012, Canada abolished the 1¢ penny. To allow cash transactions to be processed at a 5¢ resolution, the following rounding policy was put into place:
The calculation of the Goods and Services Tax (GST) or Harmonized Sales Tax (HST) on purchases, whether for cash or non-cash transactions, will continue to be calculated to the penny and added to the price. It is only the total cash payment for the transaction that will be rounded.
From Royal Canadian Mint.
That is, all calculations should be done as accurately as possible and end up using standard "round half-up tie breaking to achieve hundredths resolution, but when it comes time to make the cash payment, further rounding to the nearest nickel is required.
For internationalization, I need to implement this. I also would like to re-use this for other places where, say, the final cost is rounded to 10¢, 25¢, etc. How do I accomplish this behavior in iOS, using NSDecimalNumbers?
I'd love if it worked like this on Obj-C:
finalCost = [total roundWithResolution:#0.05];
or this in Swift:
finalCost = total.roundWithResolution(0.05);
To clear up any ambiguity above, this is the behavior after having already rounded to the hundredths place using "round half-up" tie breaking:
5¢
$0.98 to $1.02 → $1.00
$1.03 to $1.07 → $1.05
$1.08 to $1.10 → $1.10
And so on
10¢
$0.96 to $1.05 → $1.00
$1.06 to $1.15 → $1.10
And so on
25¢
$0.88 to $1.12 → $1.00
$1.13 to $1.37 → $1.25
$1.38 to $1.62 → $1.50
$1.63 to $1.87 → $1.75
$1.88 to $2.12 → $2.00
And so on

Took me a night's worth of asking around and testing solutions, but I finally came up with this:
#implementation NSDecimalNumber (RoundWithResolution)
- (NSDecimalNumber *)roundWithResolution:(NSDecimalNumber *)resolution
{
NSDecimalNumber *step1 = [self decimalNumberByDividingBy:resolution];
NSDecimalNumber *step2 = [step1 round:0];
NSDecimalNumber *step3 = [step2 decimalNumberByMultiplyingBy:resolution];
NSDecimalNumber *step4 = [step3 round:2];
return step4;
}
- (NSDecimalNumber *)round:(NSUInteger)decimalPlaces
{
NSNumberFormatter *formatter = [NSNumberFormatter new];
formatter.roundingMode = NSNumberFormatterRoundHalfUp;
formatter.numberStyle = NSNumberFormatterNoStyle;
formatter.maximumFractionDigits = decimalPlaces;
return [NSDecimalNumber decimalNumberWithString:[formatter stringFromNumber:self]];
}
#end
This is based off this formula on Math.SE by kccu, Milo Brandt, and Eric Towers:

Related

NSNumberFormatter w/ currency and NSDecimalNumber; $9.95 == 9.949999999999999 [duplicate]

I have some string s that is locale specific (eg, 0.01 or 0,01). I want to convert this string to a NSDecimalNumber. From the examples I've seen thus far on the interwebs, this is accomplished by using an NSNumberFormatter a la:
NSString *s = #"0.07";
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[formatter setGeneratesDecimalNumbers:YES];
NSDecimalNumber *decimalNumber = [formatter numberFromString:s];
NSLog([decimalNumber stringValue]); // prints 0.07000000000000001
I'm using 10.4 mode (in addition to being recommended per the documentation, it is also the only mode available on the iPhone) but indicating to the formatter that I want to generate decimal numbers. Note that I've simplified my example (I'm actually dealing with currency strings). However, I'm obviously doing something wrong for it to return a value that illustrates the imprecision of floating point numbers.
What is the correct method to convert a locale specific number string to an NSDecimalNumber?
Edit: Note that my example is for simplicity. The question I'm asking also should relate to when you need to take a locale specific currency string and convert it to an NSDecimalNumber. Additionally, this can be expanded to a locale specific percentage string and convert it to a NSDecimalNumber.
Years later:
+(NSDecimalNumber *)decimalNumberWithString:(NSString *)numericString in NSDecimalNumber.
Based on Boaz Stuller's answer, I logged a bug to Apple for this issue. Until that is resolved, here are the workarounds I've decided upon as being the best approach to take. These workarounds simply rely upon rounding the decimal number to the appropriate precision, which is a simple approach that can supplement your existing code (rather than switching from formatters to scanners).
General Numbers
Essentially, I'm just rounding the number based on rules that make sense for my situation. So, YMMV depending on the precision you support.
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[formatter setGeneratesDecimalNumbers:TRUE];
NSString *s = #"0.07";
// Create your desired rounding behavior that is appropriate for your situation
NSDecimalNumberHandler *roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:2 raiseOnExactness:FALSE raiseOnOverflow:TRUE raiseOnUnderflow:TRUE raiseOnDivideByZero:TRUE];
NSDecimalNumber *decimalNumber = [formatter numberFromString:s];
NSDecimalNumber *roundedDecimalNumber = [decimalNumber decimalNumberByRoundingAccordingToBehavior:roundingBehavior];
NSLog([decimalNumber stringValue]); // prints 0.07000000000000001
NSLog([roundedDecimalNumber stringValue]); // prints 0.07
Currencies
Handling currencies (which is the actual problem I'm trying to solve) is just a slight variation on handling general numbers. The key is that the scale of the rounding behavior is determined by the maximum fractional digits used by the locale's currency.
NSNumberFormatter *currencyFormatter = [[NSNumberFormatter alloc] init];
[currencyFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4];
[currencyFormatter setGeneratesDecimalNumbers:TRUE];
[currencyFormatter setNumberStyle:NSNumberFormatterCurrencyStyle];
// Here is the key: use the maximum fractional digits of the currency as the scale
int currencyScale = [currencyFormatter maximumFractionDigits];
NSDecimalNumberHandler *roundingBehavior = [NSDecimalNumberHandler decimalNumberHandlerWithRoundingMode:NSRoundPlain scale:currencyScale raiseOnExactness:FALSE raiseOnOverflow:TRUE raiseOnUnderflow:TRUE raiseOnDivideByZero:TRUE];
// image s is some locale specific currency string (eg, $0.07 or €0.07)
NSDecimalNumber *decimalNumber = (NSDecimalNumber*)[currencyFormatter numberFromString:s];
NSDecimalNumber *roundedDecimalNumber = [decimalNumber decimalNumberByRoundingAccordingToBehavior:roundingBehavior];
NSLog([decimalNumber stringValue]); // prints 0.07000000000000001
NSLog([roundedDecimalNumber stringValue]); // prints 0.07
This seems to work:
NSString *s = #"0.07";
NSScanner* scanner = [NSScanner localizedScannerWithString:s];
NSDecimal decimal;
[scanner scanDecimal:&decimal];
NSDecimalNumber *decimalNumber = [NSDecimalNumber decimalNumberWithDecimal:decimal];
NSLog([decimalNumber stringValue]); // prints 0.07
Also, file a bug on this. That's definitely not the correct behavior you're seeing there.
Edit: Until Apple fixes this (and then every potential user updates to the fixed OSX version), you're probably going to have to roll your own parser using NSScanner or accept 'only' double accuracy for entered numbers. Unless you're planning to have the Pentagon budget in this app, I'd suggest the latter. Realistically, doubles are accurate to 14 decimal places, so at anything less than a trillion dollars, they'll be less than a penny off. I had to write my own date parsing routines based on NSDateFormatter for a project and I spent literally a month handling all the funny edge cases, (like how only Sweden has the day of week included in its long date).
See also Best way to store currency values in C++
The best way to handle currency is to use an integer value for the smallest unit of the currency, i.e. cents for dollars/euros, etc. You'll avoid any floating point related precision errors in your code.
With that in mind, the best way to parse strings containing a currency value is to do it manually (with a configurable decimal point character). Split the string at the decimal point, and parse both the first and second part as integer values. Then use construct your combined value from those.

NSLinguisticTagger Language is "und"

I'm trying to figure out the language of a string. If I pass it on from a variable it recognises the language as "und" but if I pass it on as
[tagger setString:[NSString stringWithFormat:#"Example 2 Three people have attached a rope around your belly and pull it with the indicated forces. The sketch isn’t true to scale and shows the situation froth above. a) Detennine with a drawing, in what direction you are pulled (assuming that you don’t put up any resistance) Choose a scale of l00N 2 Ion . b) What is the influence of the lengths of the ropes?"]];
it recognises the language correctly. The text is the same in both cases
//Recognize Language for Output
{
NSArray *tagschemes = [NSArray arrayWithObjects:NSLinguisticTagSchemeLanguage, nil];
NSLinguisticTagger *tagger = [[NSLinguisticTagger alloc] initWithTagSchemes:tagschemes options:0];
[tagger setString:[NSString stringWithFormat:#"%#", text]];
NSString *language = [tagger tagAtIndex:0 scheme:NSLinguisticTagSchemeLanguage tokenRange:NULL sentenceRange:NULL];
output = [NSString stringWithFormat:#"The language is %#\rand the following words were found:\r\r%#", language, output];
}
What kind of language is "und" and why doesn't it recoginze the language correctly as en?
From the docs for NSOrthography:
the tag und is used if a specific language cannot be determined.
The problem was that the text wasn't formatted properly. The original text in the "text" string looked like this:
2014-01-28 18:13:51.412 Tesseract[35357:70b] Old text: Example 2
Three people have attached a rope around your belly
and pull it with the indicated forces. The sketch isn’t
true to scale and shows the situation from above.
a) Detennine with a drawing, in what direction you are
pulled (assuming that you don’t put up any resistance)
Choose a scale of l00N 2 tom .
b) What is the influence of the lengths of the ropes?
If the text is reformatted using
text = [text stringByReplacingOccurrencesOfString:#"\n" withString:#" "];
it looks like this and NSLinguisticTagger will recognise the language correctly.
2014-01-28 18:13:51.412 Tesseract[35357:70b] Reformatted text: Example 2 Three people have attached a rope around your belly and pull it with the indicated forces. The sketch isn’t true to scale and shows the situation from above. a) Detennine with a drawing, in what direction you are pulled (assuming that you don’t put up any resistance) Choose a scale of l00N 2 tom . b) What is the influence of the lengths of the ropes?

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.
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.

NSNumberFormatter rounding to negative zero

I'm using NSNumberFormatter to format float values as integer strings, i.e. omitting the fractional part. I find it odd, that numbers in range (-0.5, 0) *open interval end up as -0. As this value will be displayed to the user, I think that negative zero is not appropriate. I have experimented with various combinations of numberStyle and roundingMode without success.
Is there a way to configure the NSNumberFormatter to output them as 0, or do I have to resort to manual correction for that range?
I had to do correct this myself. I am using the NSNumberFormatter to display temperature with default numberStyle — NSNumberFormatterNoStyle which rounds the numbers to the whole integer, roundingMode set to NSNumberFormatterRoundHalfUp. In the end I did intercept the values in problematic range and round it myself:
- (NSString *)temperature:(NSNumber *)temperature
{
float f = [temperature floatValue];
if (f < 0 && f > -0.5)
temperature = [NSNumber numberWithLong:lround(f)];
return [self.temperatureFormatter stringFromNumber:temperature];
}
No, there is no way to configure it to do that.
In "10.4 mode", NSNumberFormatter basically just wraps CFNumberFormatter (although it's not a direct toll-free-bridged wrapping). You can look at the list of Number Formatter Property Keys and it's pretty clear that there's nothing that will do what you want. (It may be possible in "10.0" mode; it would take a bit of trial and error to find out. But I doubt you want to use that.)
So pre-rounding (as Justin Boo suggests) is probably your best option.
You could, of course, post-process instead. Exactly what you want to do probably depends on whether you want to also render -0.00 as 0.00, what you want to happen for localizations that don't use "-0", etc. The simplest case would be as simple as this:
#interface NSNumberFormatter (NegativeZero)
- (NSString *)stringFromNumberNoNegativeZero:(NSNumber *)number;
#end
#implementation NSNumberFormatter (NegativeZero)
- (NSString *)stringFromNumberNoNegativeZero:(NSNumber *)number {
NSString *s = [self stringFromNumber:number];
if ([s isEqualToString:#"-0"]) return #"0";
return s;
}
#end
But if you want anything more complicated, it'll get more complicated.

Objective-C: format numbers to ordinals: 1, 2, 3, .. to 1st, 2nd, 3rd

In Objective C, is there any way to format an integer to ordinals
1 => "1st", 2 => "2nd" etc... that works for any language?
So if the user is French he will see "1er", "2ieme" etc..
Thanks a lot!
Edit:
This is for an iOs app
Have you taken a look at TTTOrdinalNumberFormatter which is in FormatterKit? It works great, and I'm pretty sure it's exactly what you're looking for.
Here's an example taken from the kit:
TTTOrdinalNumberFormatter *ordinalNumberFormatter = [[TTTOrdinalNumberFormatter alloc] init];
[ordinalNumberFormatter setLocale:[NSLocale currentLocale]];
[ordinalNumberFormatter setGrammaticalGender:TTTOrdinalNumberFormatterMaleGender];
NSNumber *number = [NSNumber numberWithInteger:2];
NSLog(#"%#", [NSString stringWithFormat:NSLocalizedString(#"You came in %# place!", nil), [ordinalNumberFormatter stringFromNumber:number]]);
Assuming you've provided localized strings for "You came in %# place!", the output would be:
* English: "You came in 2nd place!"
* French: "Vous êtes venu à la 2eme place!"
* Spanish: "Usted llegó en 2.o lugar!"
The solution is immediately available from NSNumberFormatter:
- (NSString *)getOrdinalStringFromInteger:(NSInteger)integer
{
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
[formatter setLocale:[NSLocale currentLocale]];
[formatter setNumberStyle:NSNumberFormatterOrdinalStyle];
return [formatter stringFromNumber:[NSNumber numberWithInteger:integer]];
}
You could use ICU, which includes a way of doing what you describe:
http://icu-project.org/apiref/icu4c/classRuleBasedNumberFormat.html
You don't say what context you're using Objective-C in, but if you're writing for Cocoa, ICU is actually present. However, reaching down to talk to it directly can be a bit tricky.
[edited to link to someone who actually seems to have figured out how to build ICU and link it]
How to build ICU so I can use it in an iPhone app?
You need a rule set for each language you want to support. Any language is asking too much: they are all wildly different. First, create a rule set class which holds the regular and the exception cases for a given language. That class needs a single method that takes a number and returns a string suffix (or the number plus the suffix.) Create rule set instances (statically) for each language you care about.
Then create a category on NSNumber that returns a suffix pulled from the appropriate rule set for whatever language the user needs (system locale, or some choice they make, or case by case.)
Each language has different rules, of course. For example, English is relatively complicated:
1st,
2nd,
3rd,
4th,
5th,
... 20th
and then it starts again at st, nd, rd, th... Unit 1s, 2s, 3s and 4s are always special cases. Zero is 'th' (zeroth, hundredth, millionth etc.)
French is different. 1er, then it's x ième all the way up. (These are usually abbreviated to just 're' and 'e', making French quite easy.)
Japanese gets very odd. Cardinal 1, 2, 3, 4: (ichi, ni, san, yon) becomes tsuichi, futsuka, mikka and yokka. Those aren't suffixes though: the numbers are named differently when they're used as ordinals. Luckily, because that's incredibly confusing, you can just stick a kanji 'kai' character (which looks like a box in box) after the number and everyone knows what you mean.
Swift:
func getOrdinalDegreeValue() -> String? {
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .ordinal
return formatter.string(from: NSNumber(value: 1)) // Number
}
1st