How to prevent currency with decimal rounds in Swift - ios

Seems to be trivial but I couldn't figure out how to prevent the currency value from Rounding in Swift.
Below is my code:
let halfYearlyPrice = 71.99
var perMonthPrice = (halfYearlyPrice as Double) / 6.0
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.init(identifier: "en-US")
if let formattedPrice = currencyFormatter.string(from: perMonthPrice as NSNumber) {
print("formattedPrice: ", formattedPrice)
print("\(formattedPrice) / month")
}
The output is
formattedPrice: $12.00
$12.00 / month
I'm wondering how can I ensure the formattedPrice is 11.99?
Thanks in advance.

While you should use Decimal to more accurately represent base-10 values, using a double isn't the root cause of your problem here.
The default roundingMode of a NumberFormatter is .halfEven, which is going to round 11.998 up to 12.00. In fact any value >= 11.995 will end up as 12.00.
You need to set the rounding mode to .down.
let halfYearlyPrice = Decimal(71.99)
var perMonthPrice = halfYearlyPrice / 6
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.init(identifier: "en-US")
currencyFormatter.roundingMode = .down
if let formattedPrice = currencyFormatter.string(from: perMonthPrice as NSNumber) {
print("formattedPrice: ", formattedPrice)
print("\(formattedPrice) / month")
}
Output:
formattedPrice: $11.99
$11.99 / month
You will get this result even if you don't use Decimal, but rounding errors can accrue if you perform numerous currency operations using floating point values rather than Decimal.

Related

Swift 5: Format Float Number for zero negative number

I want to format a negative number and be left with only two decimal places.
But when the number is a negative -0.00 I want it to be unsigned. For example:
let negativeNumber = -0.008
let result = 0.00
I try do this with numberFormatter but i get this result -0.00, and i want this result 0.00
This is the formatter i use:
static func formatValue(_ value: Double, currency: Currency? = nil, options: [FormatterOption] = [], locale: Locale = Locale.current) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = locale
let defaultOptions: [FormatterOption] = [.minFractionDigits(2),
.maxFractionDigits(currency?.decimals ?? 2),
.roundingMode(.down)]
var completeDecimals = false
for option in defaultOptions + options {
switch option {
case .minFractionDigits(let minValue):
if let decimals = currency?.decimals, minValue > decimals {
continue
}
formatter.minimumFractionDigits = minValue
case .maxFractionDigits(let maxValue):
if let decimals = currency?.decimals, maxValue > decimals {
continue
}
formatter.maximumFractionDigits = maxValue
case .roundingMode(let mode):
formatter.roundingMode =
}
let formattedNumber = formatter.format(from: value)
return formattedNumber
}

Swift NumberFormatter appends Unicode spaces to currency strings even when the currency symbol is blank

In iOS 13 it appears that in certain Locales, NumberFormatter appends a Unicode NO-BREAK SPACE or NARROW NO-BREAK SPACE to a string representation of the number, even when NumberFormatter's currencySymbol property is set to "" (blank).
Here's an example of this:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
numberFormatter.currencySymbol = ""
numberFormatter.locale = Locale(identifier: "uk_US (current)")
let numberString = numberFormatter.string(from: NSNumber(value: 1))!
print ("-->\(numberString)<--")
Does anyone know a way of suppressing that appended space, short of having to filter it out of every converted string in other code?
You can set your own positive and negative formats to avoid such cases. Note that you should always set the locale before setting the formats and there is no need to initialize a NSNumber object from your value, you can use Formatter's string(for:) method:
let numberFormatter = NumberFormatter()
numberFormatter.locale = Locale(identifier: "uk_US")
numberFormatter.numberStyle = .currency
numberFormatter.positiveFormat = "##0.00"
numberFormatter.negativeFormat = "-##0.00"
let value: Double = 1
let numberString = numberFormatter.string(for: value) ?? "" // "1,00"
print ("-->\(numberString)<--") // "-->1,00<--\n"
For locales where there might not have fraction digits:
let numberFormatter = NumberFormatter()
// numberFormatter.locale = Locale(identifier: "ja_JP")
numberFormatter.numberStyle = .currency
let digits = numberFormatter.minimumFractionDigits
let zeros = digits == 0 ? "" : "." + String(repeating: "0", count: digits)
numberFormatter.positiveFormat = "##0" + zeros
numberFormatter.negativeFormat = "-##0" + zeros
let value: Double = 1
let numberString = numberFormatter.string(for: value) ?? "" // "1"
print ("-->\(numberString)<--") // "-->1<--\n"

convert number formate with separator ,

How to convert number formate with separated by ,
Example
Resultant String
"38963.26" value into "38,863.26"
"1013321.22" Value into "10,13,321.22"
//MARK:- Seperator number formate 1000 - 1,000
extension Formatter {
static let withSeparator: NumberFormatter = {
let formatter = NumberFormatter()
formatter.groupingSeparator = ","
formatter.numberStyle = .decimal
return formatter
}()
}
extension BinaryFloatingPoint {
var formattedWithSeparator: String {
return Formatter.withSeparator.string(for: self) ?? ""
}
}
let resultValue = StringValue.CGFloatValue()?.formattedWithSeparator
print("Resultant", resultValue!)
CGFloatVaule default method for converting String to floatValue.
same like String.floatValue.
For value "38963.26" is gives resultant value "38,963.262" I wonder why its like that extra 2 in decimal.
print("38963.26".CGFloatValue()!.formattedWithSeparator)
Output "38,863.262"
Either set a proper (that uses this style for formatting) locale or set both decimal and grouping separator on your formatter instance since not all Locale might use the same separators.
Then you also need to set max number of fraction digits if you are using float (for some reason this isn't needed for Double)
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en_US")
formatter.maximumFractionDigits = 2
if let str = formatter.string(for: Float(numberString)) {
print(str)
}
let formatter = NumberFormatter()
formatter.groupingSeparator = ","
formatter.decimalSeparator = "."
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
if let str = formatter.string(for: Float(numberString)) {
print(str)
}
Both yields
38,963.26
Refer this:-
how to add commas to a number in swift?enter link description here
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let numberString = "38963.26"
if let number = Double(numberString),
let formattedNumber = numberFormatter.string(from: NSNumber(value: number)) {
let formattedString = "\(formattedNumber)"
print(formattedString)
}
There are three issues:
The use of explicit grouping separator.
Forcing the thousands separator to “,” is counterproductive. If the device is in a locale where that’s already the default, you don’t need to specify it. And if the device is in a locale where it’s not default, it will be exceedingly confusing for a user who is not used to seeing “,” thousandths separators. And worse, if you were to override the grouping separator, you should also override the decimal separator, too. If this is for a string to be presented in the user interface, I would suggest not touching the grouping separator (or any of those properties):
static let decimal: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}()
If you really want to force use , for grouping separator and . for decimal separator (even though the user might be in a locale where that might be exceeding confusing were you to present that in the user interface), you should go ahead and change the locale to whatever you want, e.g.
static let decimal: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "en_US") // or "en_IN" or whatever
return formatter
}()
Decimal places.
If you always want two decimal places, then:
static let decimal: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
By setting minimum fraction digits, you’re ensured that 42.1 will show up as “42.10”. By setting maximum fraction digits, you’re ensured that 42.33333 will show up as “42.33”.
Issues resulting from conversion of string to binary floating point and back to string.
The fundamental problem with the decimal places is that you’re converting a string to a binary floating point number. If you use Double, you’ll capture more significant digits than you will with Float/CGFloat, but you’re still introducing rounding errors as you convert a string to a binary floating point representation. Float starts introducing conversion errors at the 7th significant decimal digit. Double defers the conversion errors to the 15th significant decimal digit.
You might consider using a numeric format that will capture up to 38 digits without rounding issues, namely Decimal. Thus:
extension NumberFormatter {
static let decimal: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
// specify locale and/or fraction digits as needed
return formatter
}()
}
extension String {
func decimalFormatted() -> String? {
return Decimal(string: self).flatMap { NumberFormatter.decimal.string(for: $0) }
}
}
And then
let string = "38963.26"
let foo = string.decimalFormatted() ?? ""

How to convert string to currency without rounding up?

I am using following code to convert string to US currency. However, I could not figure out how to disable round up.
For example, if the string value is "0.000012" code converts it to "$0.00".
The extension I am using it is from this SO answer:
The way I use:
print(Formatter.currency.locale) // "en_US (current)\n"
print(priceUsdInt.currency) // "$1.99\n"
To control rounding (accept 6 digits after point in this case) add formatter.maximumFractionDigits = 6; to your Formatter extension
extension Formatter {
static let currency = NumberFormatter(style: .currency)
static let currencyUS: NumberFormatter = {
let formatter = NumberFormatter(style: .currency)
formatter.locale = Locale(identifier: "en_US")
formatter.maximumFractionDigits = 6;
return formatter
}()
static let currencyBR: NumberFormatter = {
let formatter = NumberFormatter(style: .currency)
formatter.locale = Locale(identifier: "pt_BR")
formatter.maximumFractionDigits = 6;
return formatter
}()
}
0.000012 is should NOT convert to 0.01 , that would be wrong.
If you want to set up a different format you can change the decimal points
String(format: "%0.3f", string)

Only show decimal portion of Double with the decimal point

I'm trying to only show the decimal portion of a number but it keeps printing the number with the decimal point:
for number 223.50:
changeAmountLabel.text = "\(balance.truncatingRemainder(dividingBy: 1))"
this will print .5, but what needs to be printed is 50, is there a way to do this?
You can do this by using a NumberFormatter.
Try this:
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 3
let number = 223.50
if let str = formatter.string(from: NSNumber(value: number)) as? String {
if let range = str.range(of: ".") {
let value = str.substring(from: range.upperBound)
print(value) // 50
}
}
Update for Swift 4:
if let str = formatter.string(for: number) {
if let range = str.range(of: ".") {
let value = str[range.upperBound...]
print(value) // 50
}
}
You just need to floor your balance and subtract the result from it, next multiply it by 100. After that you can format the result as needed:
let balance = 223.50
let cents = (balance - floor(balance)) * 100
let formatted = String(format: "%.0f", cents) // "50"

Resources