Reputation: 323
I'm trying to properly reduce scale, formatting a float value and returning it as a String in Swift.
For example:
let value: Float = 4.8962965
// formattedFalue should be 4.90 or 4,90 based on localization
let formattedValue = value.formatNumber()
Here is what I did:
extension Float {
func reduceScale(to places: Int) -> Float {
let multiplier = pow(10, Float(places))
let newDecimal = multiplier * self // move the decimal right
let truncated = Float(Int(newDecimal)) // drop the fraction
let originalDecimal = truncated / multiplier // move the decimal back return originalDecimal
}
func formatNumber() -> String {
let num = abs(self)
let numberFormatter = NumberFormatter()
numberFormatter.usesGroupingSeparator = true
numberFormatter.minimumFractionDigits = 0
numberFormatter.maximumFractionDigits = 2
numberFormatter.roundingMode = .up
numberFormatter.numberStyle = .decimal
numberFormatter.locale = // we take it from app settings
let formatted = num.reduceScale(to: 2)
let returningString = numberFormatter.string(from: NSNumber(value: formatted))!
return "\(returningString)"
}
}
But when I use this code I get 4.89 (or 4,89 depending on the localization) instead of 4.90 (or 4,90) as I expect.
Thanks in advance.
Upvotes: 0
Views: 238
Reputation:
Foundation
has a better API now.
In-place, you can use .number
:
value.formatted(.number
.precision(.fractionLength(2))
.locale(locale)
)
But it's only available on specific types. For an extension for more than one floating-point type, you'll need to use the equivalent initializer instead:
extension BinaryFloatingPoint {
var formattedTo2Places: String {
formatted(FloatingPointFormatStyle()
.precision(.fractionLength(2))
.locale(locale)
)
}
}
let locale = Locale(identifier: "it")
(4.8962965 as Float).formattedTo2Places // 4,90
Upvotes: 0
Reputation: 323
Solved by following Sulthan's comments:
remove that
reduceScale
method which is not necessary and it will probably work as expected. You are truncating the decimal to4.89
which cannot be rounded any more (it is already rounded). – Sulthan 6 hours ago
That's because you have specified
minimumFractionDigits = 0
. If you always want to display two decimal digits, you will have to setminimumFractionDigits = 2
. – Sulthan 5 hours ago
Upvotes: 1
Reputation: 86661
You get 4.89 because reduceScale(to:)
turns the number into 4.89
(actually, probably 4.89000something
because 4.89 cannot be expressed exactly as a binary floating point). When the number formatter truncates this to two decimal places, it naturally rounds it down.
In fact, you don't need reduceScale(to:)
at all because the rounding function of the number formatter will do it for you.
Also the final string interpolation is unnecessary because the result of NumberFormatter.string(from:)
is automatically bridged to a String?
Also (see comments below by Dávid Pásztor and Sulthan) you can use string(for:)
to obviate the NSNumber
conversion.
This is what you need
import Foundation
extension Float {
func formatNumber() -> String {
let num = abs(self)
let numberFormatter = NumberFormatter()
numberFormatter.usesGroupingSeparator = true
numberFormatter.minimumFractionDigits = 0
numberFormatter.maximumFractionDigits = 2
numberFormatter.roundingMode = .up
numberFormatter.numberStyle = .decimal
numberFormatter.locale = whatever
return numberFormatter.string(for: num)!
}
}
let value: Float = 4.8962965
// formattedFalue should be 4.90 or 4,90 based on localization
let formattedValue = value.formatNumber() // "4.9"
Upvotes: 2