Is there any advantage to using a NumberFormatter over simply casting?
For example 1 and 2 below both seem to do the same job, but casting seems to be easier to read so why would the NumberFormatter often be used in code samples I've found?
var displayValue: Double? {
get {
if let text = display.text,
// 1 let value = NumberFormatter().number(from: text)?.doubleValue
// 2 let value = Double(text)
}
Huge difference. If you want something to handle:
Thousand-digit separator and decimal places: one million is commonly written as 1,000,000 in the US, 1.000.000 in France, and 10,00,000 in India.
Currency symbol: $100 or £100 or €100
Negative numbers in the accounting style, for example -100 is written as (100) (and often in red too)
Percentage: 5% → 0.05
Word spelling: "one hundred six" → 106
etc... then use NumberFormatter.
Related
I want to align numbers by digits in table rows. For example:
___123.4
-5 678.9
That is, so that tens are under tens, units under units, a fractional number under a fractional number.
To convert a Number to a String, I use the numberStringFormatter function.
func numberStringFormat(_ number: Double) -> String {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 1
numberFormatter.groupingSeparator = " "
let result = numberFormatter.string(from: NSNumber(value: number))
return result ?? ""
}
This function sets the fractional format, determines the number of decimal places, and groups the numbers before the decimal point by digits.
But if the number is an integer, without fractions, or a digit after a floating point after rounding it turns out to be 0, then the formatted string looks like this, for example, 123
And then these numbers in the rows of the table are shifted and it turns out like this:
----123
5 678.9
That is, the fractional number on the bottom row is under the integer number on the top row.
In my opinion, I can solve this task if I force the Number to always show 0 after converting to a String.
I tried googling but couldn't find an answer to this question.
Maybe someone has already encountered such situations and can suggest a possible solution, or at least in what direction to move?
Or, perhaps there is some other solution without forcing 0 to be shown, but simply aligning the characters vertically?
Any ideas are welcome. I really appreciate your help.
Update: A greate solution from HangarRash.
minimumFractionDigits = 1
I am using String(format:) to convert a Float. I thought the number would be rounded.
Sometimes it is.
String(format: "%.02f", 1.455) //"1.46"
Sometimes not.
String(format: "%.02f", 1.555) //"1.55"
String(round(1.555 * 100) / 100.0) //"1.56"
I guess 1.55 cannot be represented exactly as binary. And that it becomes something like 1.549999XXXX
But NumberFormatter doesn't seem to cause the same problem... Why? Should it be preferred over String(format:)?
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
if let string = formatter.string(for: 1.555) {
print(string) // 1.56
}
Reference to the problem (to use String (format :) to round a decimal number) can be found in the answers (or more often comments) to these questions: Rounding a double value to x number of decimal places in swift and How to format a Double into Currency - Swift 3. But the problem it covers (math with FloatingPoint) has been dealt with many times on SO (for all languages).
String(format:) does not have the function of rounding a decimal number (even if it is unfortunately proposed in some answers) but of formatting it (as its name suggests). This formatting sometimes causes a rounding. That is true. But we have to keep in mind a problem that the number 1.555 is... not worth 1.555.
In Swift, Double and Float, that conform to the FloatingPoint protocol respect the IEEE 754 specification. However, some values cannot be exactly represented by the IEEE 754 standard.
In the same way that you can't represent a third exactly in a (finite) decimal expansion, there are lots of numbers which look simple in decimal, but which have long or infinite expansions in a binary expansion." (source)
To be convinced of this, we can use The Float Converter to convert between the decimal representation of numbers (like "1.02") and the binary format used by all modern CPUs (IEEE 754 floating point). For 1.555, the value actually stored in float is 1.55499994754791259765625
So the problem does not come from String (format :). For example, we can try another way to round to the thousandth and we find the same problem. :
round (8.45 * pow (10.0, 3.0)) / pow (10.0, 3.0)
// 8.449999999999999
That is how it is : "Binary floating point arithmetic is fine so long as you know what's going on and don't expect values to be exactly the decimal ones you put in your program".
So the real question is : is this really a problem for you to use ? It depends on the app. Generally if we convert a number into a String by limiting its precision (by rounding), it is because we consider that this precision is not useful to the user. If this is the kind of data we're talking about, then it's okay to use a FloatingPoint.
However, to format it it may be more relevant to use a NumberFormatter. Not necessarily for its rounding algorithm, but rather because it allows you to locate the format :
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.locale = Locale(identifier: "fr_FR")
formatter.string(for: 1.55)!
// 1,55
formatter.locale = Locale(identifier: "en_US")
formatter.string(for: 1.55)!
// 1.55
Conversely, if we are in a case where precision matters, we must abandon Double / Float and use Decimal. Still to keep our rounding example, we can use this extension (which may be the best answer to the question "Rounding a double value to x number of decimal places in swift ") :
extension Double {
func roundedDecimal(to scale: Int = 0, mode: NSDecimalNumber.RoundingMode = .plain) -> Decimal {
var decimalValue = Decimal(self)
var result = Decimal()
NSDecimalRound(&result, &decimalValue, scale, mode)
return result
}
}
1.555.roundedDecimal(to: 2)
// 1.56
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
What is the easiest way to convert the fractional part of a float decimal (the part on the right) to a whole number integer.
For example:
0.25 converts to 25
0.09 converts to 9
0.90 converts to 90
I've tried several ways, including converting the float to a string and extracting the fraction, but for some reason it leaves off any trailing zeros. For example 0.90 would convert to a string as 0.9.
Here is an example:
let a = 0.90
let fractionalPart = a.truncatingRemainder(dividingBy: 1.0)
let modifiedFractionalPart = Int(fractionalPart * 100.0)
let string = String(modifiedFractionalPart)
// prints 90
If you aren't allowed to multiply by 100.0, meaning you don't actually have to limit your fractional part to two decimal places, rather you need to have the whole part after the . then use the following:
let a = 0.09017
let fractionalPart = String(a).components(separatedBy: ".")[1] // "09017"
Then if you have to convert it to an Int just do:
let fractionalPartInt = Int(fractionalPart) // 09017
Apple's Foundation Framework provides a way to do this:
let numberFormatter = NumberFormatter()
numberFormatter.multiplier = 100
print(numberFormatter.string(from: 0.25))
Specific documentation for NumberFormatter is also available
The advantage of using the NumberFormatter is that it is:
More modular -- if you need to change the conversion factor, just change the multiplier
More expressive -- having self-documenting code is extremely useful when viewing an old project
I'd like to round my values to the closest of 5 cent for example:
5.31 -> 5.30
5.35 -> 5.35
5.33 -> 5.35
5.38 -> 5.40
Currently I'm doing it by getting the decimal values using:
let numbers = 5.33
let decimal = (numbers - rint(numbers)) * 100
let rounded = rint(numbers) + (5 * round(decimal / 5)) / 100
// This results in 5.35
I was wondering if there's a better method with fewer steps because sometimes numbers - rint(numbers) is giving me a weird result like:
let numbers = 12.12
let decimal = (numbers - rint(numbers)) * 100
// This results in 11.9999999999999
Turns out..it's really simple
let x: Float = 1.03 //or whatever value, you can also use the Double type
let y = round(x * 20) / 20
It's really better to stay away from floating-point for this kind of thing, but you can probably improve the accuracy a little with this:
import Foundation
func roundToFive(n: Double) -> Double {
let f = floor(n)
return f + round((n-f) * 20) / 20
}
roundToFive(12.12) // 12.1
I will use round function and NSNumberFormatter also but slightly different algorithm
I was thinking about using % but I changed it to /
let formatter = NSNumberFormatter()
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
//5.30
formatter.stringFromNumber(round(5.31/0.05)*0.05)
//5.35
formatter.stringFromNumber(round(5.35/0.05)*0.05)
//5.35
formatter.stringFromNumber(round(5.33/0.05)*0.05)
//5.40
formatter.stringFromNumber(round(5.38/0.05)*0.05)
//12.15
formatter.stringFromNumber(round(12.13/0.05)*0.05)
Depending on how you are storing your currency data, I would recommend using a dictionary or an array to look up the original cents value, and return a pre-computed result. There's no reason to do the calculations at all, since you know that 0 <= cents < 100.
If your currency is a string input, just chop off the last couple of digits and do a dictionary lookup.
round_cents = [ ... "12":"10", "13":"15", ... ]
If your currency is a floating point value, well, you have already discovered the joys of trying to do that. You should change it.
If your currency is a data type, or a fixed point integer, just get the cents part out and do an array lookup.
...
round_cents[12] = 10
round_cents[13] = 15
...
In either case, you would then do:
new_cents = round_cents[old_cents]
and be done with it.