Adding commas in to extended numbers in Swift - ios

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

Related

Problem with Swift Extensions and NumberFormatters

I'm trying to format the text field of a label such that its text will only print values with a certain number of significant digits. I'm using the extension functionality of Swift and the NumberFormatter class but, while the code complies correctly with no errors, the functionality I want (i.e. a maximum of 6 significant digits) is not being implemented.
Here's my extension code:
extension Double {
func formatNumbers() -> String {
let numberFormat = NumberFormatter()
let number = NSNumber(value: self)
numberFormat.usesSignificantDigits = true
numberFormat.minimumFractionDigits = 0
return String(numberFormat.string(from: number) ?? "")
}
}
And here's when I call the extension method:
ConsoleValue.text! = "\(tempResult.formatNumbers())"
where ConsoleValue is a UILabel and tempResult is a Double var.
Can someone help me with what I'm doing wrong?
To set the maximum number of significant digits, use the maximumSignificantDigits property :
numberFormat.maximumSignificantDigits = 6
According to this wikipedia article, significant figures are :
All non-zero digits are significant: 1, 2, 3, 4, 5, 6, 7, 8, 9.
Zeros between non-zero digits are significant: 102, 2005, 50009.
Leading zeros are never significant: 0.02, 001.887, 0.000515.
In a number with a decimal point, trailing zeros (those to the right of the last non-zero digit) are significant: 2.02000, 5.400, 57.5400.
In a number without a decimal point, trailing zeros may or may not be significant. More information through additional graphical symbols
or explicit information on errors is needed to clarify the
significance of trailing zeros.

What should I set in maximumFractionDigits to minimize data loss?

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

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

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))
}

Resources