Точность форматирования строк в Swift: секреты и примеры

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

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

Начать курс

Precision string format specifier в Swift: как управлять точностью вывода чисел

Привет, коллега! Если ты когда-нибудь сталкивался с необходимостью вывести число с определённым количеством знаков после запятой в Swift, ты наверняка замечал, что классический String(format:) ведёт себя не всегда очевидно. Особенно, если ты пришёл из мира C или Objective-C, где printf — это священная корова. В этой статье мы разберёмся, как работает precision string format specifier в Swift, почему он иногда «ломается» и как его правильно использовать. Поехали!

Что такое precision specifier и зачем он нужен?

Precision specifier — это часть строки формата, которая определяет, сколько знаков после запятой (или общее количество знаков) должно быть выведено. В Swift, как и в C, он записывается после точки внутри спецификатора формата. Например, %.2f означает «вывести число с плавающей точкой с двумя знаками после запятой».

Но тут есть нюанс: Swift использует Core Foundation для форматирования строк, и его поведение может отличаться от того, что ты привык видеть в C. Особенно это касается работы с Double и Float.

Давай сразу посмотрим на простой пример, который часто встречается в вопросах на Stack Overflow:

let pi = Double.pi
let formatted = String(format: "%.2f", pi)
print(formatted) // Выведет: "3.14"

Всё работает как часы. Но как только мы начинаем экспериментировать с более сложными форматами, начинаются сюрпризы.

Основные типы precision specifier в Swift

В Swift поддерживаются следующие спецификаторы, которые влияют на точность:

  • %f — число с плавающей точкой. Precision указывает количество знаков после запятой.
  • %e — экспоненциальная запись. Precision — количество знаков после точки.
  • %g — выбирает между %f и %e в зависимости от значения. Precision — общее количество значащих цифр.
  • %d / %i — целые числа. Precision — минимальное количество цифр (дополняется нулями слева).
  • %s — строки. Precision — максимальное количество выводимых символов.

Но самое интересное начинается, когда мы пытаемся применить precision к Double или Float через String(format:).

Популярная проблема: «Почему precision не работает?»

Одна из самых частых жалоб на Stack Overflow: «Я пишу String(format: "%.2f", myDouble), но получаю неожиданный результат». Давай разберём типичный сценарий:

let value: Float = 1.23456789
let result = String(format: "%.4f", value)
print(result) // Может вывести "1.2346" или "1.2345" в зависимости от компиляции

Почему? Потому что Float имеет точность около 6-7 значащих цифр. Если ты передаёшь 1.23456789, он округляется до 1.234568 (или около того) ещё до того, как попадёт в формат. А precision в %.4f уже применяется к этому округлённому значению.

Чтобы избежать путаницы, всегда используй Double для чисел с плавающей точкой, если нужна высокая точность:

let value: Double = 1.23456789
let result = String(format: "%.4f", value)
print(result) // Надёжно выведет "1.2346"

Расширенные примеры: как управлять precision динамически

Иногда precision нужно задавать не жёстко, а через переменную. Swift позволяет делать это двумя способами:

Способ 1: через * (звёздочка)

В спецификаторе формата можно использовать * вместо числа, и тогда precision будет браться из следующего аргумента:

let number = 3.14159
let precision = 3
let result = String(format: "%.*f", precision, number)
print(result) // "3.142"

Обрати внимание: precision передаётся как Int перед самим числом. Это работает, но может сбивать с толку.

Способ 2: через интерполяцию строки формата

Более идиоматичный для Swift способ — собрать строку формата динамически:

let number = 2.71828
let precision = 2
let formatString = "%.\(precision)f"
let result = String(format: formatString, number)
print(result) // "2.72"

Этот подход более читаемый и менее подвержен ошибкам.

Precision для целых чисел: неожиданные возможности

Многие забывают, что precision specifier можно применять и к целым числам. Например, чтобы вывести число с ведущими нулями:

let number = 42
let result = String(format: "%.5d", number)
print(result) // "00042"

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

Precision для строк: обрезка и безопасность

А вот ещё одна «фишка», о которой знают не все: precision для строк ограничивает количество выводимых символов. Это может пригодиться, когда нужно обрезать длинный текст:

let longString = "Hello, World! This is a very long string."
let result = String(format: "%.10s", longString)
print(result) // "Hello, Wor" (только первые 10 символов)

Но будь осторожен: если строка содержит Unicode-символы (например, эмодзи), обрезка может разорвать последовательность, что приведёт к некорректному отображению. В таких случаях лучше использовать prefix из Swift Standard Library.

Почему String(format:) не всегда лучший выбор?

Несмотря на свою мощь, String(format:) в Swift имеет несколько ограничений:

  • Безопасность типов: компилятор не проверяет, соответствуют ли аргументы спецификаторам. Ошибка может проявиться только в рантайме.
  • Локализация: String(format:) не учитывает локаль пользователя. Для чисел с разделителями тысяч или разными десятичными разделителями лучше использовать NumberFormatter.
  • Производительность: при большом количестве вызовов может быть медленнее, чем интерполяция строк.

Вот как выглядит решение через NumberFormatter для локализованного вывода с точностью:

import Foundation

let number = 1234.5678
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2

let result = formatter.string(from: NSNumber(value: number)) ?? ""
print(result) // "1 234,57" для русской лока                

Блоги

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