In Swift, how would you create an NSNumberFormatter that would preserve trailing zeros after a decimal (12.000) while also generating a number appropriate for the current locale?
My current code and example output:
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.locale = NSLocale.currentLocale()
formatter.maximumFractionDigits = 10
var doubleNumString = "12.0"
println(formatter.numberFromString(doubleNumString)) //in English it prints 12, want it to print 12.0
var doubleNumString = "12.000"
println(formatter.numberFromString(doubleNumString)) //prints 12, want it to print 12.000
var doubleNumString = "12.125"
println(formatter.numberFromString(doubleNumString)) //prints 12.125 as expected
var doubleNumString = "1234"
println(formatter.numberFromString(doubleNumString)) //prints 1,234 as expected
I've already coded it such that if the string ends in a decimal ("12.") then it won't use this formatter to generate the number and will instead just display the number then the decimal (but I will need to improve that because some languages read right to left).
One solution would be to check if the string contains a period and if so, check if all digits that follow it are 0, and if so then don't run it through the number formatter and instead run only the int value through the formatter then append/prepend the decimal followed by the appropriate number of 0's.
Is there a better/cleaner solution?
As mentioned by Martin R, you can set the minimumFractionDigits and maximumFractionDigits to the same number which will enforce that many fraction digits always be displayed. To know how many to display you need to take a substring after the decimal to the end and count its elements. To know whether or not all of the fraction digits are 0's, I created a helper method that converts that substring to a number and if it equals 0 then you know they were all 0's.
Unfortunately you need to convert the string to a localized number using a couple different NSNumberFormatters based on the original string number. So if it does contain a decimal and everything after it is a 0 then you need to create a different formatter, convert the string to a number, then convert that number to a string in order to display it respecting the user's locale. Otherwise you can just use your original number formatter.
This function takes care of your requirement. pass same for & from locale (e.g. en_US)
+ (NSString*) stringForString:(NSString*) string forLocale:(NSString*) toLocaleCode fromLocal:(NSString*) fromLocaleCode {
NSLocale *fromLocale = [[NSLocale alloc] initWithLocaleIdentifier:fromLocaleCode];
NSNumberFormatter *sourceFormatter = [[NSNumberFormatter alloc] init];
[sourceFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[sourceFormatter setUsesGroupingSeparator:NO];
[sourceFormatter setLocale:fromLocale];
NSNumber *localizedNumber = [sourceFormatter numberFromString:string];
if (!localizedNumber) {
return string;
}
NSLocale *toLocale = [[NSLocale alloc] initWithLocaleIdentifier:toLocaleCode];
NSNumberFormatter *destinationFormatter = [[NSNumberFormatter alloc] init];
[destinationFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
[destinationFormatter setUsesGroupingSeparator:NO];
[destinationFormatter setLocale:toLocale];
NSString *localizedString = [destinationFormatter stringFromNumber:localizedNumber];
//add the zeros which were dropped because of the sourceDecimalString number conversion e.g. 0.20 is converted to 0.2
if (localizedString.length < string.length) {
NSRange rangeOfDecimal = [string rangeOfString:sourceFormatter.decimalSeparator];
if (rangeOfDecimal.location != NSNotFound) {
NSString* sourceDecimalString = [string substringFromIndex:rangeOfDecimal.location];
rangeOfDecimal = [localizedString rangeOfString:destinationFormatter.decimalSeparator];
if (rangeOfDecimal.location != NSNotFound) {
NSString* destinationDecimalString = [localizedString substringFromIndex:rangeOfDecimal.location];
if (destinationDecimalString.length < sourceDecimalString.length) {
int difference = sourceDecimalString.length - destinationDecimalString.length;
int toalDecimalDigits = (destinationDecimalString.length - 1) + difference; //-1 to remove '.'
destinationFormatter.minimumFractionDigits = toalDecimalDigits;
destinationFormatter.maximumFractionDigits = toalDecimalDigits;
localizedString = [destinationFormatter stringFromNumber:localizedNumber];
}
}
else{//this indicates no decimal separator in the return string
int toalDecimalDigits = (sourceDecimalString.length - 1); //-1 to remove '.'
destinationFormatter.minimumFractionDigits = toalDecimalDigits;
destinationFormatter.maximumFractionDigits = toalDecimalDigits;
localizedString = [destinationFormatter stringFromNumber:localizedNumber];
}
}
}
return localizedString;
}
Related
So right now I have the following code:
- (NSString*)convertToLocalCurrencyFormat:(NSDecimalNumber*)result {
NSNumberFormatter* formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterCurrencyStyle;
formatter.currencyCode = self.comparisonCurrency;
formatter.usesSignificantDigits = YES;
return [formatter stringFromNumber:result];
}
When I pass in an NSDecimalNumber* containing 678071967196719797153475347466.94627863, it gets formatted to ¥678,072,000,000,000,000,000,000,000,000 (with the currencyCode set to JPY). If I leave out the formatter.usesSignificantDigits = YES line, then it gets formatted to ¥678,071,967,196,719,797,153,475,347,467, closer, but still dropping the decimal and following values.
However, when I pass in 6780.0416000000012517376, it's formatted correctly to ¥6,780.04 with the significant digits line. It gets formatted to ¥6,780 without the significant digits line.
I know that NSNumberFormatter can take in any NSNumber as a parameter, but can only deal with values as precise as doubles, leaving NSDecimalNumber with no errors and incorrect results.
How can I format NSDecimalNumbers with currency codes without loss of precision?
Thanks
Try setting the minimum fraction digits instead:
formatter.minimumFractionDigits = 2;
HTH
This seems like a super basic question, but I just can't seem to find the answer anywhere :-( I am able to do this in Objective C, but I am getting stuck in Swift.
What I need to do:
Take an Integer value
Format it into a localized string
Inject the value into another string using the stringWithFormat equivalent method (since the other string is localized as well, which is not shown in simplified examples below)
How it's easily done in Objective C -- this works:
// points is of type NSNumber *
NSNumberFormatter *formatter = [NSNumberFormatter new];
formatter.locale = [NSLocale currentLocale];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
NSString *ptsString = [formatter stringFromNumber:points];
NSString *message = [NSString stringWithFormat:#"You've earned %# points", ptsString];
My best attempt at doing this in Swift -- compiler error on last line:
// points is of type Int
let formatter = NSNumberFormatter()
formatter.locale = NSLocale.currentLocale()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
let ptsString = formatter.stringFromNumber(points)!
let message = String(format: "You've earned %# points", arguments: ptsString)
I'm getting the following error in Xcode on that last line:
"Cannot convert value of type 'String' to expected argument type '[CVarArgType]'"
(In my actual code, the message into which I want to insert the points value is itself localized as well, but I have simplified this example, as I'm getting the exact same error in both cases.)
What am I missing here..?
Thanks so much for any help,
Erik
You need to wrap the arguments in a collection. Like this:
let message = String(format: "You've earned %# points", arguments: [ptsString])
You can also use this method:
let message = "You've earned \(ptsString) points"
Additionally you can create an extension method to do this:
extension String {
func format(parameters: CVarArgType...) -> String {
return String(format: self, arguments: parameters)
}
}
Now you can do this:
let message = "You've earned %# points".format("test")
let message2params = "You've earned %# points %#".format("test1", "test2")
Sometimes, you need a little more control - so if you need to have leading zeros, you could use 'stringWithFormat' just like in objective-C
let ptsString = String(format: "%02d", points)
Is there anyway in objective-c to determine if a currency uses a decimal point (regardless of declared NSNumber type)?
I have multiple locales and I use NSNumberFormatter (based on locale) to set string currency string style, however before-hand I would like to know if the selected locale currency uses a decimal point.
[_cf setLocale:
[NSLocale localeWithLocaleIndentifier:[NSString stringWithFormat:#"%#",locale]]]];
[cf setNumberStyle:NSNUmberFormatterCurrencyStyle];
NSString *value = [cf stringFromNumber:price];
return value;
After creating the NSNumberFormatter with currency style, ask the formatter how many fraction digits it has.
NSInteger maxFractionDigits = cf.maximumFractionDigits;
if (maxFractionDigits == 0) {
// this currency is an integer, not a decimal
}
One example where this is true is the Japanese Yen (¥).
I have a problem with comparison two decimal values.
I have a text field that contains number like 0.123456 and NSNumber that contains 0.000001.
Maximum fraction digits of both is 6. Minimum - 0
I've tried to do it like that:
NSNumberFormatter *decimalFormatter = [[NSNumberFormatter alloc] init];
[decimalFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
[decimalFormatter setMaximumFractionDigits:6];
double sum = [[decimalFormatter numberFromString:self.summTextField.text] doubleValue];
if (self.minSum != nil) {
if (sum < [self.minSum doubleValue]) {
return NO;
}
}
But i have a problem, that sometimes 0.123456 = 0,123455999... or 0,123456000000...01
For example #0.000001 doubleValue < #0.000001 doubleValue - TRUE.
How can I compare to NSNumber with a fractional part, to be sure that it will be correct?
Thanks in advance.
Create extension to decimal for rounding
extension Decimal {
func rounded(toDecimalPlace digit: Int = 2) -> Decimal {
var initialDecimal = self
var roundedDecimal = Decimal()
NSDecimalRound(&roundedDecimal, &initialDecimal, digit, .plain)
return roundedDecimal
}
}
let value1 = Decimal(2.34999999).rounded(toDecimalPlace: 4)
let value2 = Decimal(2.34999989).rounded(toDecimalPlace: 4)
print(value1.isEqual(to: value2))
this results in TRUE
You can round your value, if you worried about fractional part...
Something like this:
-(double)RoundNormal:(double) value :(int) digit
{
value = round(value * pow(10, digit));
return value / pow(10, digit);
}
And then compare it.
You can simply put the test otherwise if you do not want to bother much
if(abs(x-y) < 0.0001)
This should solve it:
NSNumberFormatter *decimalFormatter = [[NSNumberFormatter alloc] init];
[decimalFormatter setNumberStyle: NSNumberFormatterDecimalStyle];
[decimalFormatter setMaximumFractionDigits:6];
[decimalFormatter setMinimumFractionDigits:6];
[formatter setRoundingMode:NSNumberFormatterRoundHalfUp];
[formatter setRoundingIncrement:[NSNumber numberWithDouble:0.000001]]
Use the NSDecimalNumber class - see the guide Number and Values Programming Topics
This is how NSDecimal numbers are compared in iOS:
if ( [x compare:y] == NSOrderedSame ){
// If x is equal to y then your code here..
}
if([x compare:y] == NSOrderedDescending){
// If x is descendant to y then your code here..
}
if([x compare:y] == NSOrderedAscending){
// If x is ascendant to y then your code here..
}
Is there an API for this? Or maybe a better way of doing it?
Here's what I'm trying to acomplish:
In is a numeric string. Out, is a NSDecimalNumber
// this one is for US:
in: 1 out: 0.01
in: 12 out: 0.12
in: 123 out: 1.12
// a diferent locale might have a diferent maximumFractionDigits, like 1
in: 1 out: 0.1
in: 12 out: 1.2
in: 123 out: 12.3
// other locales might have 0, or 3 fraction digits.
Here's how I have it:
// Clear leading zeros
NSNumber *number = [formatter numberFromString:numericString];
numericString = [formatter stringFromNumber:number];
if (maximumFractionDigits == 0) {
return [NSDecimalNumber decimalNumberWithString:numericString];
}
else if (numericString.length <= _currencyFormatter.maximumFractionDigits) {
NSString *zeros = #"";
for (NSInteger i = numericString.length; i < maximumFractionDigits ; i++) {
zeros = [zeros stringByAppendingString:#"0"];
}
return [NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:#"0.%#%#",zeros,numericString]];
} else {
NSString *decimalString = [NSString stringWithFormat:#"%#.%#",
[_rateInput substringToIndex:numericString.length - maximumFractionDigits],
[_rateInput substringFromIndex:numericString.length - maximumFractionDigits]];
return [NSDecimalNumber decimalNumberWithString: decimalString];
}
While this does seem to work, I was wondering if there is an API for this, or a more simple, less error prone way of doing it?