What should I set in maximumFractionDigits to minimize data loss? - ios

Introduction
I noticed the NumberFormatter#maximumFractionDigits default is 3.
I have confirmed:
import Foundation
let nf = NumberFormatter()
nf.numberStyle = .decimal
print(nf.maximumFractionDigits) //=> 3
nf.string(for: Decimal(string: "100.1111111")) //=> "100.111"
I have tried to set Int.max
I set Int.max to maximumFractionDigits:
import Foundation
let nf = NumberFormatter()
nf.numberStyle = .decimal
nf.maximumFractionDigits = Int.max
nf.string(for: Decimal(string: "100.1111111")) // => "100"
Why!? become "100"!?
In my research
I read Foundation > NSNumberFormatter > NumberFormatter source code:
open var maximumFractionDigits: Int
I have confirmed maximumFractionDigits data type is Int.
Question
How to set max into maximumFractionDigits?
I want to show a server response without loss, as much as possible.
of course, a server response is String in json. But all most calculation in ios app with Decimal from the String. So this goal is to convert Decimal to String for UILabel.
Q1. nf.maximumFractionDigits = Int.max. Why loss data? this is bug on NumberFormatter?
Q2. How to set max into maximumFractionDigits correct?
Goal
I want to minimize data loss.

Q1. nf.maximumFractionDigits = Int.max. Why loss data? this is bug on NumberFormatter?
When not clearly documented, every Int parameter may have a limitation depending on the implementation details. If you passed a value exceeding this limitation, a runtime error might cause crash, or might be silently ignored, all such things depend on the implementation detail.
As far as I tested, the maximum number you can set to maximumFractionDigits is the same value with Int32.max.
let nf = NumberFormatter()
nf.numberStyle = .decimal
nf.maximumFractionDigits = Int(Int32.max)+1
print(nf.string(for: Decimal(string: "123.45678901234567890123456789012345678"))!)
//->123
nf.maximumFractionDigits = Int(Int32.max)
print(nf.string(for: Decimal(string: "123.45678901234567890123456789012345678"))!)
//->123.45678901234567890123456789012345678
You can call it a bug, but, the maximum significant digits which NumberFormatter can handle is 38-digit, of Decimal. Who want to make a precise definition for values more than millions of times bigger than expected practical values?
Q2. How to set max into maximumFractionDigits correct?
As noted above, the significant digits held in Decimal is 38. You can write something like this:
let nf = NumberFormatter()
nf.numberStyle = .decimal
nf.usesSignificantDigits = true
nf.maximumSignificantDigits = 38
print(nf.string(for: Decimal(string: "123.45678901234567890123456789012345678"))!)
//->123.45678901234567890123456789012345678

Related

Adding commas in to extended numbers in Swift

I'm wanting to add commas in to break up numbers within my iOS application.
For example:
Change 1000 into 1,000
Change 10,000 into 10,000
Change 100000 into 100,000
And so on...
What is the most efficient way of doing this, and safe-guarding against numbers post decimal point too?
So for example,
1000.50 should return 1,000.50
My numbers at the moment are Ints, Doubles and Floats - so not sure if I need to manipulate them before or after converting to Strings.
Any feedback would be appreciated.
The Foundation framework (which is shared between iOS and MacOS) includes the NumberFormatter class, which will do exactly what you want. You'd configure a number formatter to include a groupingSeparator. (Note that different countries use different grouping separators, so you might want to set the localizesFormat flag to allow the NumberFormatter to change the separator character based on the user's locale.
Here is some sample code that will generate strings with comma thousands separators and 2 decimal places:
let formatter = NumberFormatter()
// Set up the NumberFormatter to use a thousands separator
formatter.usesGroupingSeparator = true
formatter.groupingSize = 3
//Set it up to always display 2 decimal places.
formatter.alwaysShowsDecimalSeparator = true
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
// Now generate 10 formatted random numbers
for _ in 1...10 {
// Randomly pick the number of digits
let digits = Double(Int.random(in:1...9))
// Generate a value from 0 to that number of digits
let x = Double.random(in: 1...(pow(10, digits)))
// If the number formatter is able to output a string, log it to the console.
if let string = formatter.string(from:NSNumber(value:x)){
print(string)
}
}
Some sample output from that code:
356,295,901.77
34,727,299.01
395.08
37,185.02
87,055.35
356,112.91
886,165.06
98,334,087.81
3,978,837.62
3,178,568.97

"%.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

NSDecimalNumber issues

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)

Resources