NumberFormatter return the wrong number - swift

I test the NumberFormatter to get the number from priceWithCurrency.
If price bigger than $70 NumberFormatter converted the wrong number.
lazy var currencyFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "USD"
return formatter
}()
let price = "$71.9"
currencyFormatter.number(price)
//71.90000000000001

If you do the same thing with 71.8, it will work. This is just because 71.9 can't be represented precisely in floating point numbers ( due to the finitude of bits )
Use integers ( price in cents ) or decimal numbers is the only issue to deal correctly with prices. Check the Decimal and NSDecimalNumber classes to see what you can do with this.
It is specially recommended if you do some computations on prices, ( If you pay 10$ with two friends in cash, two will pay 3.33, and one will pay 3.34 - so depending of your context, divide by 3 might not be enough)
let number = currencyFormatter.number(from: price) ?? NSNumber(value: 0)
var value = number.decimalValue
var roundedValue: Decimal = 0
// Right way: ( choose your precisions - 3 decimals here):
NSDecimalRound(&roundedValue, &value, 3, .bankers)
print(number)
print(value)
print(roundedValue)
71.90000000000001
71.90000000000001
71.9
If you just need to log your prices, simply use Int(price*100)/100
It will do the job
If you need more... Good luck :)
Edit
Following the excellent remark of #Nicholas Rees, I add this variation:
currencyFormatter.generatesDecimalNumbers = true
let number = (currencyFormatter.number(from: price) as? NSDecimalNumber) ?? NSDecimalNumber(value: 0)
var value = number.decimalValue
var roundedValue: Decimal = 0
// Right way: ( choose your precisions - 3 decimals here):
NSDecimalRound(&roundedValue, &value, 3, .bankers)
print(number)
print(value)
print(roundedValue)
There, the result in the same when logged, but I suppose the internal format of the 'value' is correct
Another approach is to remove currency and create decimal from string:
print(Decimal(string: "71.9") ?? 0)
71.9

Related

String number with limited precision fraction digits in Swift

What would be the fastest way to convert a String number "1234.5678" to an NSNumber with precision -> 1234.56 and back to String "1234.56".
let numberFormatter = NumberFormatter()
numberFormatter.maximumFractionDigits = 2
numberFormatter.string(from: numberFormatter.number(from: "1234.534234")!)
This code does not look that beautiful. Any ideas?
You can use the new formatted method and specify the number of precision fraction length to two:
let decimal = Decimal(
sign: .plus,
exponent: -4,
significand: 12345678
) // 1234.5678
decimal.formatted(.number.precision(.fractionLength(2))) // "1,234.57"
decimal.formatted(.number.grouping(.never).precision(.fractionLength(2))) // "1234.57"
decimal.formatted(.number.grouping(.never).rounded(rule: .towardZero).precision(.fractionLength(2))) // "1234.56"
Alternatively if you are only interested in the string regardless of any numeric modification like rounding you can strip the unwanted characters with Regular Expression
let string = "1234.5678"
let trimmedString = string.replacingOccurrences(of: "(\\d+\\.\\d{2})\\d.",
with: "$1",
options: .regularExpression)

How to convert Exponential Value to Decimal Value

let balance = "2.477854178281608e-06"
// I have to convert this exponential value in Decimal
/* Already tried the below-mentioned solution, but not working */
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let finalNumber = numberFormatter.number(from: balance)
print(finalNumber!)
Value is printing "2.477854178281608e-06\n"
Any help will be appreciated.
let balance = "2.477854178281608e-06"
// I have to convert this exponential value in Decimal
This is not an exponential value. This is a string that represents a number using exponential format. You seem to want a string representing the same number in a different format. The important thing here is that neither string is "the value." The value is the same regardless of representation (or approximately the same if the representation is limited).
So first you need the value represented by the string. To do that, convert it to a Double.
let value = Double(balance)!
Now, you say you want to convert that to a string in decimal format (I assume you mean 0.000...). So you need a formatter:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 20
let string = numberFormatter.string(for: value)!
print(string) // 0.00000247785417828161
You'll note that this value is slightly different than the previous value. That's because there are rounding errors when dealing with values this small.
If all of these base-10 digits are important, you can work with the Decimal type rather than Double. This avoids decimal/binary rounding, but is less convenient and slower for some kinds of math. If this is a type of currency that is expressed in base-10 units (which is basically all of them), you always want to work with Decimal and never with Double.
let balance = "2.477854178281608e-06"
let value = Decimal(string: balance)!
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 21
let string = numberFormatter.string(for: value)!
print(string) // 0.000002477854178281608

Code is automatically rounding value

I am trying to use a small decimal value as a double but the Mac app I am writing is automatically rounding the value up or down. Here is my code after a button is tapped to confirm the value change:
self.desiredReturns = self.desiredReturnTextField.doubleValue
print ("val is \(self.desiredReturnTextField.doubleValue) ** \(self.desiredReturns)")
It prints val is 0.0 ** 0.0 when I input 0.0001 but when I input 0.001 it prints normally as val is 0.001 ** 0.001.
Am I doing something wrong here? Why does this happen with small numbers?
Here is how I display it as a percentage in my UI and it also prints as 0
func formatPercent(value: Double) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.percent
numberFormatter.maximumFractionDigits = 4
let result = numberFormatter.string(from: NSNumber(value: value))
return result!
}
self.formatPercent(value: self.desiredReturns)
The reason this was happening was that there was a number formatter associated to my text field. the number formatter style was set to decimal and that rounds values to 3 decimal places.

How to properly handle comparison between double values in Swift?

I know similar question has been asked but I still could not figured out the solution.
I am getting double value like this.
let priceUsdInt = (price as NSString).doubleValue
And I want to compare this value to 1.00 so:
if priceUsdInt > 1.00 {
let priceUsdCur = priceUsdInt.currencyUS
finalPriceUsdCur = priceUsdCur
} else {
let priceUsdCur = priceUsdInt.currencyUS6
finalPriceUsdCur = priceUsdCur
}
This always bring two decimal results. Even-though value is way lower then 1.00.
Basically, what I want to achieve is if value is lesser then 1.00 then show it until six decimals i.e. 0.123456 when converted to currency format, if not show only two decimals after it i.e. 1.23.
Thank you for your time.
This demonstrates showing a 6 digit precision in currency formatting when the value from string coverted to double value is below 1.0 and 2 digit precision when its above 1.0
let belowOne = ".12023456"
let belowDoubleVal = Double(belowOne) ?? 0.0
let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currency
if belowDoubleVal < 1.0 {
// this handles the 6 digit precision you require when value is below 1.0
currencyFormatter.minimumFractionDigits = 6
}
// old way using NSSNumber
// let belowOneString = currencyFormatter.string(from: NSNumber(value: belowDoubleVal))
// you can pass the double value to formatter using .string(for: Any)
// thanks for pointing this out by Leo Dabus
let belowOneString = currencyFormatter.string(for: belowDoubleVal)

No scientific notation and rounding with double

I want to round a number to 6 places and I don't want it to be displayed using scientific notation. I am using this code to round the decimal to 6 places but if the value is really small, it is still displaying it using scientific notation. I know that I can use the number formatter (displayed below as well) to remove the scientific notation but if I do that then it is returns a string so I cannot round a string to a certain number of decimal places. If I do the rounding first, that still doesn't work in all scenarios. It still shows some numbers without rounding. What is the best way to achieve this? It should work in all different scenarios where numbers are infinitely long or repeating
extension Double {
// Rounds the double to decimal places value
func rounded(toPlaces places:Int) -> Double {
let divisor = pow(10.0, Double(places))
return (self * divisor).rounded() / divisor
}
}
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
numberFormatter.number(from:"")
I tried to use this code
let currentValue = 1/2.3344 //answer is 0.42837559972584...
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = NumberFormatter.Style.decimal
numberFormatter.maximumFractionDigits = 6
guard let finalNum = numberFormatter.number(from: String(describing: currentValue)) else {return nil}
text = String (describing: finalNum)
I want it to display 0.428375. The value rounded to 6 decimal places.
When the current value is this: let currentValue = 1/233442 which is 0.00000428371... I want it to display 0.000004. The decimal rounded to 6 decimal places and not in scientific notation.
When the current value is this: let currentValue = 1/2334429 I want it to display 0 because that is the value rounded to 6 decimal places.
The number(from: someString) method converts a string to a number,
you want it the other way around:
let currentEntry = 1/2.3344
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 6
guard let text = numberFormatter.string(for: currentEntry) else {
// ...
}
print(text) // 0.428376
Note that your String(describing:) conversions only hide the problem.
You really should avoid String(describing:) (even if the compiler suggests
it as a Fix-it!), it almost never does what you need.