NSDecimalNumber issues - ios

In my iOS swift application, I receive some json from the web which contains some double values which represent currency. It looks like this:
[{"Amount": 5.0},{"Amount":-26.07},{"Amount": 4}, ...etc]
I cast these as Doubles and then try to feed these values as a Swift "Double" into the NSDecimalNumber's constructor like this:
let amount = NSDecimalNumber(double: amountAsDouble)
I'm running into problems with this approach because very frequently the NSDecimalNumber I created will contain a different number that goes 16 places passed the decimal point.
let amount = NSDecimalNumber(double: -15.97)
println(amount)
this returns -15.970000000000004096
I don't want this, I want -15.97.
Thanks,

A Double is stored with 18 decimal digits, you can't do anything about that, it's how it works.
Read here: http://en.wikipedia.org/wiki/Double-precision_floating-point_format
However, at the time of displaying the value on the screen, you can use NSNumberFormatter like this:
let amountInDouble: Double = -15.970000000000004096
let formatter = NSNumberFormatter()
formatter.numberStyle = .DecimalStyle
formatter.roundingIncrement = 0.01
formatter.maximumFractionDigits = 2
let amountAsString = formatter.stringFromNumber(NSNumber(double: amountInDouble))
if let amountAsString = amountAsString {
println(amountAsString) // -15.97
}

I recently went through this for myself. I ended up using an NSNumberFormatter to get the proper decimal places.
let currFormatter = NSNumberFormatter()
currFormatter.numberStyle = .DecimalStyle
currFormatter.roundingIncrement = 0.01
currFormatter.minimumFractionDigits = 2
currFormatter.maximumFractionDigits = 2
let doubleAmount = currFormatter.numberFromString(amountAsDouble) as NSNumber!
let amount = doubleAmount as Double
println(amount)

Here's a tip: If you use NSJSONSerializer, numbers with decimal points are actually turned into NSDecimalNumber for you. NSDecimalNumber is a subclass of NSNumber. So what you are doing: You've got a perfectly fine NSDecimalNumber, round the value to double, and try to turn the double back into an NSDecimalNumber. Just check that what you have is indeed an NSDecimalNumber, and do no conversion if it is.

This is because the intermediate double representation is causing problems.
You should take the values from your dictionary as NSString objects and use the + decimalNumberWithString: method to convert without losing precision. In swift:
let amount = NSDecimalNumber(string: amountAsString)

let amount = NSDecimalNumber.init(value: -15.97)
let roundValue = amount.rounding(accordingToBehavior: NSDecimalNumberHandler(roundingMode: .bankers, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false))
print(roundValue)

Related

"%.2f" is rounding my number up instead of cutting

I have the following number
7.9775609756097534
and I'm using the code below to only show two decimals
let formatted = String(format: "Angle: %.2f", angle)
the problem is that the result is:
7.98
instead of
7.97
For cases such as yours we use NumberFormatter. It is a class designed to do what you need and more. For your case it should be enough to use the following:
let numberFormatter = NumberFormatter()
numberFormatter.roundingMode = .down
numberFormatter.minimumFractionDigits = 2
numberFormatter.maximumFractionDigits = 2
numberFormatter.string(from: 1.236)
This now locks fraction digits to be exactly 2. By increasing minimum fraction digits more "0" may be appended as in 0.10 may become 0.100. Maximum fraction digits will simply restrict up to what point the number will be displayed.
There are other options as well such as making 1234567.89 show as 1.234.567,89 which is really nice for users that are used to such formatting.
Alternatively, you can do bit string manipulation
let numberInFloat:Float = 7.9775609756097534
let numberInString: String = String(format: "%f", numberInFloat)
let numberParts = numberInString.components(separatedBy: ".")
print(String(format: "Output: %#.%#", String(numberParts[0]), String(numberParts[1].prefix(2))))
Output: 7.97

Swift NumberFormatter formatting all values to 3 decimal places?

I have a large array of doubles, which have a varying number of decimal places, such as:
[11307.3, 1025.64, 1.27826, 1676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604]
I am attempting to format the number so there are commas every thousand and the decimal places are not formatted to a specific number such as 3dp. The array should look like
[11,307.3, 1,025.64, 1.27826, 1,676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604]
I have tried doing this by defining NumberFormatter as such:
let numberFormatter = NumberFormatter()
and then choosing decimal for style:
numberFormatter.numberStyle = NumberFormatter.Style.decimal
The values in the array are display in a table view, and when a user taps on for example the 2nd cell, in a new screen the value 1,025.64 would be displayed.
I used this code to do that:
var formattedPrice = numberFormatter.string(from: NSNumber(value:coinPriceDouble!))
self.coinPriceLbl.text = "\(coinTitleText!): \(Cryptocoin.instance.fiatSymbol)\(formattedPrice!)"
This works perfect for any value that does not have more than 3 decimal places.
If the user chose the 3rd value in the array, it would display 1.278 not 1.27826.
Is there any way to format these values with commas but not force them to a specific number of decimal places?
As vadian said, NumberFormatter is highly customisable.
Just play around its properties, like (you need to customise based on your needs):
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 3
Here the explanation for NumberFormatter's maximumFractionDigits property and related.
Here instead a blog that explains all the related aspects of NumberFormatter A Guide to NSNumberFormatter.
EDIT
Put the following code in a Playground and observe the result:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 3
let formattedNumbers = [11307.3, 1025.64, 1.27826, 1676.46, 0.584175, 183.792, 1.02237, 13.649, 0.472665, 127.604].flatMap { number in
return numberFormatter.string(from: number)
}
print(formattedNumbers)
Link: https://stackoverflow.com/a/27571946/6655075 .
This solved my problem. As I had 3 values displaying, each from a different array, I would end up formatting all 3 whereas I only wanted to format 1 array.
extension Double {
static let twoFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
var formatted: String {
return Double.twoFractionDigits.string(for: self) ?? ""
}
}
I removed
var formattedPrice = numberFormatter.string(from: NSNumber(value:coinPriceDouble!))
And simply used
self.coinPriceLbl.text = "\(coinTitleText!): \(Cryptocoin.instance.fiatSymbol)\(coinPriceDouble!.formatted)"
Edit: As Dávid Pásztor mentioned, I only want to add the comma separator to the values which need it while still maintaining the precision of each value down to the last decimal value.
You could try setting the maximum fraction digits to a largish number.
numberFormatter.maximumFractionDigits = 15

display as a number instead of exponential number

I have a slider in my app. I am using the value of slider to calculate another value in my code. Here is the code.
let P = slider1.value
let mulValue = 1.00
let i = P * mulValue
print("i:", i)
Slider min value = 0 , Slider Max value = 9,999,999. If the slider value is more than 1,000,000 the value of i is displayed as exponential number.
For example, value of "i" printed: 1.55058e+06 in console for P = 1,550,580
I want the full number to be printed instead of exponential number. How do I correct this issue?
As per the comments below, I tried using NSNumberFormatter(). But there resulted number is rounded. I do not want my "i" to be rounded. Below is code snippet I used, Please correct me if I'm wrong. Thanks !!
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
let finalNumber = numberFormatter.numberFromString(String(i))
print("fin num:", finalNumber)
Thanks for all your inputs.
There is slight different in the usage of NSNumberFormatter()
The following code worked for my code perfectly.
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = NSNumberFormatterStyle.DecimalStyle
let finalNumber = numberFormatter.stringFromNumber(i)!
print("fin:", finalNumber)
I used stringFromNumber instead of numberFromString

String to Double conversion loses precision in Swift

I want to covert a string to double and keep the same value:
let myStr = "2.40"
let numberFormatter = NSNumberFormatter()
numberFormatter.locale = NSLocale(localeIdentifier: "fr_FR")
let myDouble = numberFormatter.numberFromString(myStr)?.doubleValue ?? 0.0
myDouble is now
Double? 2.3999999999999999
So how to convert "2.40" to exact be 2.40 as Double ??
Update:
Even rounding after conversion does not seem to work
I don't want to print, I want to calculate and it's important that the number should be correct, it's Money calculation and rates
First off: you don't! What you encountered here is called floating point inaccuracy. Computers cannot store every number precisely. 2.4 cannot be stored lossless within a floating point type.
Secondly: Since floating point is always an issue and you are dealing with money here (I guess you are trying to store 2.4 franc) your number one solution is: don't use floating point numbers. Use the NSNumber you get from the numberFromString and do not try to get a Double out of it.
Alternatively shift the comma by multiplying and store it as Int.
The first solutions might look something like:
if let num = myDouble {
let value = NSDecimalNumber(decimal: num.decimalValue)
let output = value.decimalNumberByMultiplyingBy(NSDecimalNumber(integer: 10))
}

iOS-Charts Float to Integer YAxis

I'm building a chart using iOS-charts
I'm trying to convert the floats into int, but iOS-charts only allows for Floats in the data entry:
let result = ChartDataEntry(value: Float(month), xIndex: i)
Does anyone know the method for making sure only ints are used?
For Swift 3.0 and Charts 3.0 you need to enable and set granularity for the axis.
Example:
barChart.leftAxis.granularityEnabled = true
barChart.leftAxis.granularity = 1.0
You just need to adjust the NSNumberFormatter...
Example:
yAxis.valueFormatter = NSNumberFormatter()
yAxis.valueFormatter.minimumFractionDigits = 0
NSNumberFormatter is a very powerful class, you can do much much more with it. :-)
This worked for me. However, the y-axis labels will round when displaying small numbers, so if you are charting, say 1 or 2, the chart will display 0 twice. (See here: Force BarChart Y axis labels to be integers?)
let numberFormatter = NSNumberFormatter()
numberFormatter.generatesDecimalNumbers = false
chartDataSet.valueFormatter = numberFormatter
// Converting to Int for small numbers rounds weird
//barChartView.rightAxis.valueFormatter = numberFormatter
//barChartView.leftAxis.valueFormatter = numberFormatter
You can try this one, it worked nicely for me
let formatter = NSNumberFormatter()
formatter.numberStyle = .NoStyle
formatter.locale = NSLocale(localeIdentifier: "es_CL")
BarChartView.leftAxis.valueFormatter = formatter
Swift4.2 ; Xcode 10.0 :
let numberFormatter = NumberFormatter()
numberFormatter.generatesDecimalNumbers = false
barChart.leftAxis.valueFormatter = DefaultAxisValueFormatter.init(formatter: numberFormatter)

Resources