Handling money in software is a task that requires precision, especially when dealing with fractions of currency like dollars and cents. One common mistake that developers make is using floating-point data types, such as Double
, to represent monetary values.
This post explains why this is problematic and what alternatives you should use.
Avoid Floating-Point for Monetary Values
The Problem with Floating-Point Arithmetic
Floating-point values, including the Double
type in Swift, should be avoided when working with currency amounts that have fractions. The fundamental issue is that floating-point types cannot represent certain decimal values exactly due to their binary nature.
Example Showing Unexpected Results
Consider the following example where you want to store 0.1 dollars:
var balance: Double = 0.0
for _ in 1...10{
balance += 0.1
}
for _ in 1...10{
balance -= 0.1
}
print(balance) // Outputs: 2.7755575615628914e-17
Here, the expected value is 0.0 but we get 2.7755575615628914e-17 since value 0.1 is not stored precisely as 0.1 but as an approximation (e.g., 0.10000000149…). This small difference can have significant consequences, especially when performing multiple arithmetic operations.
Loss of Significance
When performing a series of arithmetic operations using floating-point numbers, you may encounter a problem known as loss of significance. This occurs when the precision errors from approximations accumulate, leading to larger errors that can affect the outcome of your calculations.
Let’s consider a scenario where you repeatedly add 0.1 dollars to an account balance:
var balance: Double = 0.0
for _ in 1...100 {
balance += 0.1
}
print(balance) // Outputs: 9.9999999999999999
Instead of getting the expected 10.0, the result is slightly off due to accumulated floating-point errors. This can be particularly troublesome in financial applications where accuracy is critical.
The Correct Approach: Use Decimal or NSDecimalNumber
To avoid the pitfalls of floating-point arithmetic, you should use the Decimal
type in Swift, which is designed for precise decimal arithmetic. The Decimal
type can represent numbers exactly as they appear, making it suitable for financial calculations.
Example: Using Decimal
Here’s how you can correctly handle monetary values using Decimal
:
let myBalance: Decimal = 12.333
let result = myBalance / 3
print(result) // Outputs: 4.111
In this example, the division operation yields the expected result with no loss of precision.
Converting Double to Decimal
If you need to work with a value initially stored as a Double
, you can convert it to a Decimal
using NSNumber
:
let doubleValue: Double = 12.333
let decimalValue: Decimal = NSNumber(floatLiteral: doubleValue).decimalValue
let result = decimalValue / 3
print(result) // Outputs: 4.111
By converting to Decimal
, you can ensure that your calculations are accurate and free from the issues associated with floating-point arithmetic.
Conclusion
When it comes to monetary calculations, accuracy is paramount. The floating-point types, including Double
, should be avoided due to their inherent imprecision. Instead, use Decimal
or NSDecimalNumber
to ensure that your financial calculations are accurate and reliable. This small change can save you from potential bugs and errors in your applications, especially those dealing with money.