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.
Related
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
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
I want to round a double down to 1 decimal place. For example if I have a double let val = 3.1915 I want to round this down to 3.1. Normal rounding functions will round it to 3.2 but I want to basically just drop the remaining decimal places. What is the best way to do this? Is there a native function for this? I know this is pretty straight forward to do but I want to know what the best way to do this would be where I am not using any kind of workaround or bad practices. This is not a duplicate of other rounding questions because I am not asking about about rounding, I am asking how to drop decimal places.
Similarly, if the value was 3.1215, it would also round to 3.1
Use the function trunc() (which stands for truncate) which will chop away the decimal portion without rounding. Specifically, multiply the Double value by 10, truncate it, then divide by 10 again. Then, to display using 1 decimal place, use String(format:):
let aDouble = 1.15
let truncated = trunc(aDouble * 10) / 10
let string = String(format: "%.1f", truncated
print(string)
(displays "1.1")
or, to process an entire array of sample values:
let floats = stride(from: 1.099, to: 2.0, by: 0.1)
let truncs = floats
.map { trunc($0 * 10) / 10 }
.map { String(format: "%.1f", $0) }
let beforeAndAfter = zip(floats, truncs)
.map { (float: $0.0, truncString: $0.1)}
beforeAndAfter.forEach { print(String(format: "%.3f truncated to 1 place is %#", $0.0, $0.1)) }
Outputs:
1.099 truncated to 1 place is 1.0
1.199 truncated to 1 place is 1.1
1.299 truncated to 1 place is 1.2
1.399 truncated to 1 place is 1.3
1.499 truncated to 1 place is 1.4
1.599 truncated to 1 place is 1.5
1.699 truncated to 1 place is 1.6
1.799 truncated to 1 place is 1.7
1.899 truncated to 1 place is 1.8
1.999 truncated to 1 place is 1.9
By your example I assume you meant you want to Truncate, if so using multiply and casting into Int then Dividing and casting back into Float/Double will do.
Example: 3.1915 -> 3.1
var val = 3.1915
let intVal:Int = Int(val*10)
val = Float(intVal)/10.0
print(val) //3.1
If you want more decimal places simply multiply and divide by 100 or 1000 instead.
Then if for any reason you want to use the round() function there is a overloaded variant that accepts a FloatingPointRoundingRule it will work like:
var val = 3.1915
val*=10 //Determine decimal places
val.round(FloatingPoint.towardZero) // .down is also available which differs in rounding negative numbers.
val*=0.1 //This is a divide by 10
print(val) //3.1
In practical usage I'd suggest making an extension or global function instead of writing this chunk every time. It would look something like:
extension Float {
func trunc(_ decimal:Int) {
var temp = self
let multiplier = powf(10,decimal) //pow() for Double
temp = Float(Int(temp*multiplier))/multiplier //This is actually the first example put into one line
return temp
}
}
And used:
var val = 3.1915
print(val.trunc(1)) //3.1
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)
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.