Спецификаторы формата строки: правило округления, используемое для десятичных значений

Я использую String(format:) для преобразования Float. Думал округлить число.

Иногда бывает.

      String(format: "%.02f", 1.455)     //"1.46"

Иногда нет.

      String(format: "%.02f", 1.555)     //"1.55"
String(round(1.555 * 100) / 100.0) //"1.56"

Полагаю, 1.55 нельзя представить в точности как двоичную. И это становится что-то вроде 1.549999XXXX

Но NumberFormatter, похоже, не вызывает такой же проблемы ... Почему? Должен ли он быть предпочтительнее String(формат :)?

      let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2

if let string = formatter.string(for: 1.555) {
    print(string)  // 1.56
}

1 ответ

Ссылку на проблему (для использования для округления десятичного числа) можно найти в ответах (или, чаще, в комментариях) на следующие вопросы: Округление двойного значения до x количества десятичных знаков в быстром и Как преобразовать двойное число в валюту - Свифт 3 . Но проблема, которую он охватывает (математика с помощью FloatingPoint), неоднократно решалась на SO (для всех языков).

String(format:)не имеет функции округления десятичного числа (даже если это, к сожалению, предлагается в некоторых ответах), но его форматирования (как следует из названия). Такое форматирование иногда вызывает округление. Это правда. Но мы должны помнить о том, что число 1,555 ... не стоит 1,555.

В Swift и, которые соответствуют протоколу , соответствуют спецификации IEEE 754. Однако некоторые значения не могут быть точно представлены стандартом IEEE 754.

Точно так же, как вы не можете точно представить третью в (конечном) десятичном расширении, существует множество чисел, которые выглядят простыми в десятичном представлении, но которые имеют длинные или бесконечные расширения в двоичном расширении ". ( Источник)

Чтобы убедиться в этом, мы можем использовать преобразователь чисел с плавающей запятой для преобразования между десятичным представлением чисел (например, «1.02») и двоичным форматом, используемым всеми современными процессорами (с плавающей запятой IEEE 754). Для 1.555, значение, фактически сохраненное в float, равно 1.55499994754791259765625

Так что проблема не в String (format :). Например, мы можем попробовать другой способ округления до тысячной и найти ту же проблему. :

      round (8.45 * pow (10.0, 3.0)) / pow (10.0, 3.0)
// 8.449999999999999

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

Итак, настоящий вопрос: действительно ли это проблема для вас? Это зависит от приложения. Обычно, если мы конвертируем число в String, ограничивая его точность (округлением), это происходит потому, что мы считаем, что эта точность бесполезна для пользователя. Если мы говорим о таких данных, то можно использовать FloatingPoint.

Однако для его форматирования может быть более уместным использовать NumberFormatter. Не обязательно из-за его алгоритма округления, а потому, что он позволяет вам найти формат:

      let formatter = NumberFormatter()
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
formatter.locale = Locale(identifier: "fr_FR")
formatter.string(for: 1.55)!
// 1,55
formatter.locale = Locale(identifier: "en_US")
formatter.string(for: 1.55)!
// 1.55

И наоборот, если мы находимся в ситуации, когда точность имеет значение, мы должны отказаться от Double / Float и использовать Decimal. Тем не менее, чтобы сохранить наш пример округления, мы можем использовать это расширение (которое может быть лучшим ответом на вопрос «Округление двойного значения до x количества десятичных знаков в swift»):

      extension Double {
    func roundedDecimal(to scale: Int = 0, mode: NSDecimalNumber.RoundingMode = .plain) -> Decimal {
        var decimalValue = Decimal(self)
        var result = Decimal()
        NSDecimalRound(&result, &decimalValue, scale, mode)
        return result
    }
}

1.555.roundedDecimal(to: 2)
// 1.56
Другие вопросы по тегам