How to Round a Double to X Decimal Places in Swift

How to Round a Double to X Decimal Places in Swift

Written by

in

Quick answer: Multiply the value by 10 to the power of the places you want, call rounded(), then divide back. For display only, use String(format: “%.3f”, value) or the .formatted() API instead of changing the stored value.

Why this happens

A Double is a binary floating-point number. It cannot represent most decimal fractions exactly, so 1.543 is stored as the closest binary value, and printing the raw Double shows every digit of that approximation — which is why you see 1.543240952039... instead of a tidy number.

That is also why dividing totalWorkTime by 60 / 60 gives you a long tail. The arithmetic is correct; the value simply has more precision than you want to show. Rounding is a presentation decision, not a data problem, so the fix depends on whether you need a new Double or just a shorter string.

One more thing to keep in mind: because the stored value is an approximation, a rounded Double can still print with a stray digit in rare cases. If exact decimal behaviour matters — money, prices, measurements you compare for equality — use Decimal instead of Double.

How to fix it

1. Round in place with multiply, rounded(), divide

The direct approach works for any number of places. Multiply by the power of ten, round to a whole number, then divide back:

let totalWorkTimeInHours = totalWorkTime / 3600
let rounded = (totalWorkTimeInHours * 1000).rounded() / 1000
print(rounded) // 1.543

The result is still a Double, so you can keep doing arithmetic with it. Note that totalWorkTime / 60 / 60 and totalWorkTime / 3600 are equivalent — the second is just shorter.

rounded() defaults to .toNearestOrAwayFromZero, so 1.2345 at three places becomes 1.235. If you need a different tie-breaking rule, pass an explicit rule:

let value = 1.2345
let down = (value * 1000).rounded(.down) / 1000 // 1.234
let even = (value * 1000).rounded(.toNearestOrEven) / 1000

2. Wrap it in a reusable extension

Hard-coding 1000 everywhere gets unreadable fast. A small extension keeps call sites clean and works for any precision:

import Foundation

extension Double {
    /// Returns the value rounded to the given number of decimal places.
    func rounded(toPlaces places: Int) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded() / divisor
    }
}

let hours = totalWorkTime / 3600
print(hours.rounded(toPlaces: 3)) // 1.543

pow comes from Foundation, so keep the import Foundation line. The older Swift 2 spelling of this helper used round(...) and an argument label without toPlaces:; in Swift 3 and later the free function was replaced by the rounded() method on the value itself.

3. Format only for display

If the number is only being shown to a person, do not round the value at all — format it. This keeps full precision for calculations and gives you control over trailing zeros:

let hours = totalWorkTime / 3600
print(String(format: "%.3f", hours)) // 1.543

In SwiftUI, the format style API does the same job without a formatter object:

Text(hours, format: .number.precision(.fractionLength(3)))

For locale-aware output — decimal comma in some regions, grouping separators, currency symbols — use NumberFormatter:

let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 3
formatter.maximumFractionDigits = 3
print(formatter.string(from: NSNumber(value: hours)) ?? "")

String(format:) and NumberFormatter are not interchangeable with the arithmetic approach: they produce text, not a Double. Choose based on what the next line of code needs.

4. Use Decimal when exactness matters

For money and anything you compare for equality, binary floating point will eventually bite you. Decimal is base-10 and rounds predictably:

import Foundation

var input = Decimal(string: "1.543240952039")!
var result = Decimal()
NSDecimalRound(&result, &input, 3, .plain)
print(result) // 1.543

The third argument is the scale (number of decimal places) and the fourth is the rounding mode — .plain rounds half away from zero, .bankers rounds half to even. Store prices and totals as Decimal and convert to Double only at the boundary where an API demands it.

How to prevent it

  • Decide early whether you need a rounded Double or a formatted String; mixing the two leads to double-rounding bugs.
  • Never compare rounded Double values with == for business logic — compare with a tolerance, or use Decimal.
  • Keep display formatting in the view layer, not in your model, so calculations keep full precision.
  • Use Decimal for currency, prices and anything that must add up exactly.
  • Prefer one shared extension or format style over scattered * 1000 / 1000 expressions.

FAQ

Why does my rounded Double still print extra digits?

Because the rounded result is itself a binary floating-point number, and some decimal values have no exact binary representation. The value is as close as Double can get; format it for display if you need a clean string.

Does String(format: "%.3f", x) round or truncate?

It rounds to the requested number of digits. It also pads with trailing zeros, so 1.5 becomes 1.500 — which is often what you want in a UI and rarely what you want in a calculation.

Can I round to negative places, like the nearest hundred?

The extension above handles it: passing -2 gives a divisor of 0.01, so 1234.0.rounded(toPlaces: -2) returns 1200.0. It is a convenient side effect of using pow rather than a hard-coded multiplier.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.