Округление Double в Swift: 5 лучших практик 2025

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Почему округление Double в Swift — это боль (и как с ней жить в 2025)

Каждый iOS-разработчик рано или поздно сталкивается с ситуацией: нужно показать пользователю цену товара 99.99, а в коде — 99.99000000000001. Знакомо? Добро пожаловать в мир округления Double в Swift — темы, которая собрала тысячи просмотров на Stack Overflow и продолжает будоражить умы даже в 2025 году.

Проблема не в Swift — проблема в самой природе чисел с плавающей точкой (IEEE 754). Double не может точно представить 0.1, как и 1/3 в десятичной системе. Но для пользователя это не оправдание. Ему нужны красивые цифры на экране.

В этой статье я покажу 5 проверенных способов округления Double в Swift, разберу их плюсы и минусы, и дам чёткий алгоритм выбора под вашу задачу. Поехали!

Метод 1: Foundation — round() + pow(10, n) — классика жанра

Самый очевидный способ, который приходит в голову: умножить число на 10^n, округлить, разделить обратно.

import Foundation

func roundDouble(_ value: Double, toPlaces places: Int) -> Double {
    let divisor = pow(10.0, Double(places))
    return round(value * divisor) / divisor
}

let price = 99.995
print(roundDouble(price, toPlaces: 2)) // 100.0 (банковское округление)

Плюсы:

  • Простота и читаемость
  • Работает «из коробки» — не нужны дополнительные импорты

Минусы:

  • Ошибки точности — из-за Double может получиться 99.99000000000001 вместо 99.99
  • Проблема с банковским округлением — round() использует round half up, что не всегда подходит для финансов
  • pow(10, n) — лишние вычисления

Этот метод подходит для грубого округления в UI, но не для финансовых расчётов.

Метод 2: String(format:) — округление через строку (самый быстрый для UI)

Если ваша конечная цель — показать число на экране, зачем вообще работать с Double? Превратите его в строку сразу.

let value = 123.456789
let roundedString = String(format: "%.2f", value)
print(roundedString) // "123.46"

// Обратно в Double (если нужно)
let roundedDouble = Double(roundedString) ?? 0.0

Плюсы:

  • Молниеносная скорость — нет лишних математических операций
  • Гарантированно правильное количество знаков на выходе
  • Поддержка локализации (десятичный разделитель)

Минусы:

  • Результат — строка, для дальнейших расчётов нужно конвертировать обратно (потеря точности)
  • Не подходит для банковского округления (round half up по умолчанию)

Идеально для отображения цен, процентов, рейтингов — везде, где точность ±0.01 не критична.

Метод 3: NSDecimalNumber — король финансовой точности

Когда на кону деньги, Double — зло. Используйте NSDecimalNumber — он хранит числа в десятичном формате без ошибок округления.

import Foundation

func roundDecimal(_ value: Double, toPlaces places: Int, roundingMode: NSDecimalNumber.RoundingMode = .plain) -> Double {
    let decimal = NSDecimalNumber(value: value)
    let handler = NSDecimalNumberHandler(
        roundingMode: roundingMode,
        scale: Int16(places),
        raiseOnExactness: false,
        raiseOnOverflow: false,
        raiseOnUnderflow: false,
        raiseOnDivideByZero: false
    )
    return decimal.rounding(accordingToBehavior: handler).doubleValue
}

let amount = 99.995
print(roundDecimal(amount, toPlaces: 2, roundingMode: .bankers)) // 99.99 (банковское округление!)

Плюсы:

  • Полный контроль над округлением — .plain, .down, .up, .bankers
  • Минимум ошибок точности
  • Стандарт для финансовых приложений

Минусы:

  • Медленнее простого round() — в 10-20 раз
  • Громоздкий код

Используйте для расчёта налогов, комиссий, кредитов — там, где каждый цент на счету.

Метод 4: Decimal (Swift-native) — современная альтернатива

Начиная с iOS 10 / Swift 3, у нас есть нативный Decimal — обёртка над NSDecimalNumber, но с более чистым синтаксисом.

import Foundation

func roundDecimalSwift(_ value: Double, toPlaces places: Int) -> Double {
    var decimal = Decimal(value)
    var rounded = Decimal()
    NSDecimalRound(&rounded, &decimal, places, .plain)
    return (rounded as NSDecimalNumber).doubleValue
}

let testValue = 123.456789
print(roundDecimalSwift(testValue, toPlaces: 2)) // 123.46

Плюсы:

  • Swift-native, нет лишних Objective-C мостов
  • Поддерживает все режимы округления
  • Лучше для производительности, чем NSDecimalNumber в цикле

Минусы:

  • Всё ещё медленнее, чем round() + pow()
  • Синтаксис с передачей указателя может напугать новичков

Хороший выбор для middle-ground — когда точность важна, но не на уровне банковских транзакций.

Метод 5: Кастомное расширение Double — удобство и переиспользование

Никто не хочет писать одну и ту же функцию в каждом проекте. Создайте расширение Double — и пользуйтесь.

import Foundation

extension Double {
    func rounded(toPlaces places: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Double {
        let divisor = pow(10.0, Double(places))
        return (self * divisor).rounded(rule) / divisor
    }
}

let value = 99.9999
print(value.rounded(toPlaces: 2)) // 100.0
print(value.rounded(toPlaces: 2, rule: .down)) // 99.99

Для iOS 15+ можно использовать FloatingPointRoundingRule — это даёт гибкость без NSDecimalNumber.

Плюсы:

  • Чистый синтаксис — value.rounded(toPlaces: 2)
  • Легко добавлять в любой проект через SPM или копипасту

Минусы:

Блоги

Рекомендаций по книгам