Когда вы работаете с целочисленными типами, для вас и ваших программ нет никаких проблем с точностью вычисления. Число 2 помноженное на 99 всегда будет 198, а 10 деленное на 2 — однозначно равняется 5. Но рано или поздно вы придете к необходимости применения чисел с плавающей запятой, т.е. чисел с дробной частью. И в некоторых случаях это может привести к неожиданным проблемам. Рассмотрим пример из листинга 1.1.
Листинг 1.1
var wallet: Float = 0 let productPrice: Float = 0.01
Переменная wallet
описывает ваш кошелёк, а productPrice
— стоимость товара, который вы продаёте.
Что будет, если вы продадите 100 единиц товара? Конечно же в вашем кошельке появится 1 рубль (листинг 1.2).
for-in
. Это так называемый цикл, который позволяет выполнить вложенные в него выражения необходимое количество раз. Далее мы подробно рассмотрим широкие возможности for-in
.Листинг 1.2
for _ in 1...100 { wallet += productPrice } wallet // 0.9999999993
Но что мы видим? По какой-то причине вместо числа 1.0 мы получили очень приближенное к нему, но отличающееся от задуманного, значение 0.9999999993
.
Причина этому в особенностях работы компьютера с дробными числами. Как вы знаете, любое число в конечном счёте рассматривается компьютером, как совокупность 0 и 1. Так число 2 — это 10, число 13 — это 1101 и т.д. компьютеру очень просто и удобно оперировать с такими числами, перемножать их, вычитать, делить, ну и собственно, проводить любые математические (и логические) операции.
Ошибки в точности вычислений возникают, когда вы работаете с числами с плавающей запятой. В некоторых случаях компьютер не может точно представить число в двоичной форме, и использует максимально близкое, приближенное по значению. Так было и в случае с productPrice
. Нет числа в двоичной форме, которое бы представило без ошибок 0,01. И таких примеров практически бесконечное множество.
Теперь представьте, что вы разрабатываете биржу криптовалюты. Пользователи проводят операции с биткоином, ошибка накапливается, и внезапно вы становитесь аферистом, мошенником, и все потому что просто использовали не правильный тип данных.
Не правильный тот тип? — спросите вы — А что существует правильный тип?
Да, в Swift есть числовой тип данных, который позволяет проводить операции с дробными числами без потери точности вычислений. Он называется Decimal
. Попробуем поменять тип параметров из листингов выше с Float
на Decimal
, и посмотрим на результат (листинг 1.3)
Листинг 1.3
import Foundation var wallet: Decimal = 0 let productPrice: Decimal = 0.01 for _ in 1...100 { wallet += productPrice } wallet // 1
import Foundation
. О его предназначении мы поговорим позже, но если коротко, то с его помощью обеспечивается подключение библиотеки функций, в состав которой входят многие дополнительные типы данных и функции. Тип Decimal
не является фундаментальным, для его работы необходима дополнительная библиотека (Foundation).Как вы можете видеть, проблема ушла. Количество денег в кошельке ровно то, какое мы и ожидали.
Decimal
отличается от Float
и Double
тем, что с его помощью можно оперировать и проводить операции с числами с базой 10. Данный тип обслуживается не просто аппаратной частью вашего компьютера, где из десятичного числа получается двоичное (и вместе с этим преобразованием возникают и ошибки в точности). В интересах Decimal
функционируют специальные программные компоненты, которые обеспечивают точность. Конечно на нижнем уровне (в процессоре) числа все равно состоят из 0 и 1, но сложная логика работы этих специальных компонентов компенсирует все возникающие ошибки.
Но у данного решения существует и обратная сторона — тип Decimal
работает значительно медленнее, чем Float
или Double
. Не стоит использовать Decimal
повсеместно. Но где именно стоит его применять?
В случаях, когда значения могут быть измерены (например физические величины) применяйте Float
и Double
. В случаях, когда значения могут быть сосчитаны (например деньги) — используйте Decimal
.
18 Comments
Как измерить на сколько медленнее работает Decimal чем Float? Я правда не знаю, зачем мне это знать сейчас, но когда читаешь доп. материал и постоянно видишь, это мы дальше пройдем это дальше, это вы узнаете позже — невольно возникает желание задавать тупые вопросы)))))
Я рад, что вы читаете данный материал, значит он написан не просто так)
Для измерений можете составить цикл, производящий к примеру миллион операций над числами конкретного типа, замерить время перед его выполнение и после. Повторить для второго типа. Сравнить результаты.
Задавайте вопросы, это хорошо! Так же можете воспользоваться нашим чатом в Telegram https://swiftme.ru/telegramchat
Прошу прощения, что продолжаю дальше эту тему с глупыми вопросами, но не подскажете, а как-то можно замерить время внутренними средствами языка Swift?
Средствами, которые нашёл в интернете, пришёл к такому решению данного вопроса и не знаю, насколько это корректно и можно ли лучше:
// Объявление параметров для Decimal
var walletDec: Decimal = 0
let productPriceDec: Decimal = 0.01
// Фиксируем момент запуска цикла Decimal
let firstDate = Date.init()
for _ in 1…100000 {
walletDec += productPriceDec
}
// Фиксируем время, сколько цикл Decimal работал
let decTime = firstDate.distance(to: Date())
// Объявление параметров для Float
let productPriceFlt: Float = 0.01
var walletFlt: Float = 0
// Фиксируем момент запуска цикла Float
let secondDate = Date.init()
for _ in 1…100000 {
walletFlt += productPriceFlt
}
// Фиксируем время, сколько цикл Float работал
let floatTime = secondDate.distance(to: Date())
// Определяем, во сколько раз быстрее Float
let timeDif = decTime / floatTime
// На небольших числах где-то в 10 раз
// При проверки с 1_000_000, 73 секунды против 8.5 показало
// Но там комп начал гудеть и, возможно, из-за этого не в 10 раз различаются
var decimalNum: Decimal = 0.23
В качестве результата выдает 0.2300000000000000512.
Выходит, не такая уж и точность?
Тип данных значения
0.23
в первой строке —Double
.Ваш код равносилен
let doubleNum: Double = 0.23
var decimalNum: Decimal = doubleNum
Число
0.23
так же имеет проблемы с переводом в двоичный вид (имеется ошибка точности).Но, если избежать
Double
:1) Попробуйте к примеру перевести String в Decimal
Decimal(string: "0.23")
2) или провести 23 операции сложения с 0.01
var result: Decimal = 0
let decimalNum: Decimal = 0.01
for _ in 1...23 {
result += decimalNum
}
и проверьте итоговое значение
Оно будет
0.23
Весьма интересно, сижу ломаю голову, почему при явном указании типа данных Decimal, число обрабатывается как Double?
Здравствуйте.
Я Сергей Хватюк – 30 минут назад приступил к изучению SWIFT. Ваш блог помог мне разобраться с типом Decimal.
Я благодарю Вас за Вашу работу и желаю Вам баланса и благополучия.
Сергей, большое спасибо за то, что с нами!
И наконец дописать своё первое приложение ?Падаю на самом последнем контроллере
При явном указании все работает верно:
var num = 12.3 // Double
var num: Decimal = 12.3 // Decimal
Всем привет ✌️Я новенькая ?Меня зовут Олеся и я очень хочу научится классно кодить ☝️?
Спасибо за материал! Продолжаем учиться 🙂
Благодарю Автора за подробные пояснения. Пока очень доходчиво!
Василий, добрый день!Книга написана прекрасно. Большое спасибо!
>>Будьте осторожны с физическими величинами, для которых критически важна точность вплоть до стотысячных, миллионных долей и т.д.
Так что же делать в этом случае?)
Написано, что в коде должно присутствовать выражение import Foundation. У меня и с UIKit всё работает. Объясните?
работает, т.к. UIKit включает в себя import Foundation.
Спасибо за книгу, материал и доп. задания / информацию. Очень удобно и понятно. Пробовал учить Swift в небезызвестной онлайн школе, не получилось, было непонятно, сейчас по книге и доп. материалам намного яснее. Спасибо ещё раз!