Спецификаторы формата строки: правило округления, используемое для десятичных значений
Я использую 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