Переполнение
В прошлом уроке калькулятор Swift делил конфеты. У него есть и другое правило: у каждого целого типа есть предел.
Представьте счётчик с тремя колёсиками цифр. Он показывает числа от 000 до 999. Обычный счётчик, как пробег в старой машине, после 999 перекатился бы на 000 и показал бы неправду, никого не предупредив. Счётчик Swift устроен строже: перекатываться он отказывается и останавливает работу.
Картинка с колёсиками неточна в одном: десятичных колёсиков в компьютере нет. Число хранится в двоичных цифрах, битах, поэтому пределы типов — не 999, а числа вроде 127.
Зачем об этом думать? В 2014 году число просмотров клипа Gangnam Style на YouTube перевалило за 2147483647. Это предел целого числа из 32 бит, и YouTube перевёл счётчик просмотров на 64-битные числа.
Пределы целых типов
На компьютерах и смартфонах тип Int занимает 64 бита. Самое маленькое и самое большое значения Int хранятся в Int.min и Int.max:
print(Int.min) // -9223372036854775808
print(Int.max) // 9223372036854775807
print(Int8.min, Int8.max) // -128 127Что напечатала программа:
Int.minиInt.max— пределыInt, примерно девять квинтиллионов в обе стороны.Int8занимает всего 8 бит, и в нём помещаются числа от-128до127.
Кроме Int и Int8, есть Int16, Int32 и Int64: число в названии показывает, сколько бит занимает тип. В обычных программах почти всегда берут Int. Маленькие типы встречаются там, где на счету каждый бит памяти, например в файлах. В этом уроке Int8 удобен тем, что его край близко.
Разные целые типы не смешиваются: к Int8 нельзя прибавить Int. Число другого типа сначала переводят, например Int8(bonus). Число, записанное прямо в коде, например 10, само принимает нужный тип, поэтому его прибавляют к Int8 без перевода.
Когда результат не помещается
Ситуацию, когда результат действия не помещается в свой тип, называют переполнением. Обычные +, - и * молча через край не переходят никогда. Что именно произойдёт, зависит от того, когда Swift заметит переполнение.
Компилятор видит переполнение
var level: Int8 = 120
level += 10 // ✗error: arithmetic operation '120 + 10' (on type 'Int8') results in an overflowКомпилятор сам посчитал 120 + 10, получил 130 и отказался собирать программу. Как и при любой ошибке компиляции, программа не запустится вовсе.
Программа замечает переполнение во время работы
let bonus = 10
var health: Int8 = 120
health += Int8(bonus) // ✗
print(health)Здесь bonus имеет тип Int, поэтому его переводят в Int8. Сумму health и Int8(bonus) компилятор заранее не посчитал, и проверка сработала при запуске:
- Сумма
130не поместилась вInt8. - Программа аварийно завершилась на строке с
+=: остановилась посреди работы. print(health)так и не выполнился.
При многих аварийных остановках программа печатает строку Fatal error: с причиной, а при переполнении — нет. Терминал выводит только отчёт об остановке со словами Illegal instruction (так в Linux). Причину показывает Xcode: он останавливается на этой строке с пояснением Swift runtime failure: arithmetic overflow.
Какие переполнения компилятор найдёт заранее, зависит от того, что он сумел посчитать сам. Полагаться на это нельзя: в настоящих программах числа приходят во время работы, их вводит пользователь или присылает сервер. Надёжно одно: обычный + через край не переходит, он останавливает программу.
Этот вопрос часто задают на собеседовании: что будет при переполнении Int в Swift? Хороший ответ: программа аварийно завершится, а переход через край пишут явно, особыми операторами.
Переход через край нарочно
Иногда нужен именно счётчик, который перекатывается: после 127 пусть идёт -128. Для этого в Swift есть операторы переполнения &+, &- и &*. Они считают как +, - и *, но результат, который не поместился, переносят на другой край типа.
Нажимайте «Следующий шаг» и следите за counter: на второй прибавке обычный + остановил бы программу, а &+ переносит счёт на -128.
1var counter: Int8 = 1262counter = counter &+ 13print(counter)4counter = counter &+ 15print(counter)6counter = counter &+ 17print(counter)8counter = counter &- 29print(counter)
У counter тип Int8: в нём помещаются числа от -128 до 127.
Что показал виджет:
126 &+ 1даёт127: результат помещается, и&+работает как+.127 &+ 1даёт-128: после самого большогоInt8идёт самое маленькое.-127 &- 2даёт127: через нижний край счёт переходит наверх.
Такие операторы нужны редко, например в контрольных суммах — числах, по которым проверяют, не испортился ли файл. Перенос через край там заложен в сам расчёт.
Частая ошибка
Программа аварийно завершилась на переполнении, и + заменили на &+, чтобы сбоя не было:
var score: Int8 = 120
score = score &+ 10 // ✗
print(score) // -126Сбоя больше нет, но счёт стал отрицательным: -126 вместо 130. Программа работает дальше с неверным числом и никого не предупреждает. Если число может вырасти, выберите тип, в который оно поместится. Обычно это Int.
Главное
- У каждого целого типа есть предел:
Int8хранит числа от-128до127, аInt— доInt.max. - Переполнение, которое компилятор посчитал сам, даёт ошибку компиляции.
- Переполнение во время работы аварийно завершает программу на той строке, где оно случилось, и сообщения
Fatal errorпри этом нет. &+,&-и&*переходят через край нарочно, и переполнение ими не исправляют.