Swift's Decimal precision issue
NSDecimalNumber
(and its overlay type Decimal
) can represent
... any number that can be expressed as
mantissa x 10^exponent
wheremantissa
is a decimal integer up to 38 digits long, andexponent
is an integer from –128 through 127.
So decimal fractions (with up to 38 decimal digits) can be represented
exactly, but not arbitrary numbers. In particular 1/24 = 0.416666666...
has infinitely many decimal digits (a repeating decimal) and cannot be
represented exactly as a Decimal
.
Also there is no precision difference between Decimal
and NSDecimalNumber
. That becomes apparent if we print the difference
between the actual result and the "theoretical result":
let dec24 = Decimal(integerLiteral: 24)
let dec1 = Decimal(integerLiteral: 1)
let decResult = dec1/dec24*dec24
print(decResult - dec1)
// -0.00000000000000000000000000000000000016
let dn24 = NSDecimalNumber(value: 24)
let dn1 = NSDecimalNumber(value: 1)
let dnResult = dn1.dividing(by: dn24).multiplying(by: dn24)
print(dnResult.subtracting(dn1))
// -0.00000000000000000000000000000000000016
How can I initialize Decimal without losing precision in Swift
The problem is that all floating point literals are inferred to have type Double
, which results in a loss of precision. Unfortunately Swift can't initialise floating point literals to Decimal
directly.
If you want to keep precision, you need to initialise Decimal
from a String literal rather than a floating point literal.
let decimalA = Decimal(string: "3.24")!
let double = 3.24
let decimalC: Decimal = 3.0 + 0.2 + 0.04
print(decimalA) // Prints 3.24
print(double) // Prints 3.24
print(decimalC) // Prints 3.24
Bear in mind this issue only happens with floating point literals, so if your floating point numbers are generated/parsed in runtime (such as reading from a file or parsing JSON), you shouldn't face the precision loss issue.
Swift lose precision in decimal formatting
Using the new FormatStyle seems to generate the correct result
let format = Decimal.FormatStyle
.number
.precision(.fractionLength(0...2))
let text = "89806.9"
let value = try! format.parseStrategy.parse(text)
Below is an example parsing a currency using the currency code from the locale
let currencyFormat = Decimal.FormatStyle.Currency
.currency(code: Locale.current.currencyCode!)
.precision(.fractionLength(0...2))
let amount = try! currencyFormat.parseStrategy.parse(text)
Swedish example:
let text = "89806,9 kr"
print(amount)
89806.9
Another option is to use the new init for Decimal that takes a String and a FormatStyle.Currency (or a Number or Percent)
let amount = try Decimal(text, format: currencyFormat)
and to format this value we can use formatted(_:)
on Decimal
print(amount.formatted(currencyFormat))
Output (still Swedish):
89 806,9 kr
Rounding a double value to x number of decimal places in swift
You can use Swift's round
function to accomplish this.
To round a Double
with 3 digits precision, first multiply it by 1000, round it and divide the rounded result by 1000:
let x = 1.23556789
let y = Double(round(1000 * x) / 1000)
print(y) /// 1.236
Unlike any kind of printf(...)
or String(format: ...)
solutions, the result of this operation is still of type Double
.
EDIT:
Regarding the comments that it sometimes does not work, please read this: What Every Programmer Should Know About Floating-Point Arithmetic
Large decimal number formatting using NumberFormatter in Swift
You could create a Decimal
explicitly to work around the mentioned bug
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
if let decimalNumber = Decimal(string: "123456789123456789123"), let str = formatter.string(from:decimalNumber as NSNumber) {
print(decimalNumber)
print(str)
}
Related Topics
How to Remove Items from an Array When Deselecting a Row in a UItableview
Completion Handler in For/In Loop - Swift
Swift-Animate Cashapelayer Stroke Color
Nssavepannel - How to Restrict User to Only Save One One Set Directory
Multi-Users Chat Room Data Structure in Firebase Database
Using UIviewrepresentable to Wrap Marqueelabel View
How to Access Camera with Swiping Gesture
What's The How to Access a Swift Package Item from Objective-C
Swift 3 Cocoa: Use Quicklook to Preview File in Os X
Nsdocumentcontroller.Opendocument Not Allowing Selection of Custom File Type
Transparent Sticky Header UI Collectionview Don't Show Cells Underneath
Mac App Disappears When Window Is Closed Then Another App Is Selected
Nsundomanager: Capturing Reference Types Possible
Swift Custom Response Serializer Is Returning Images at Random