Перейти к содержимому
Теория · Операторы · урок 3 из 7

Переполнение

В прошлом уроке калькулятор Swift делил конфеты. У него есть и другое правило: у каждого целого типа есть предел.

Представьте счётчик с тремя колёсиками цифр. Он показывает числа от 000 до 999. Обычный счётчик, как пробег в старой машине, после 999 перекатился бы на 000 и показал бы неправду, никого не предупредив. Счётчик Swift устроен строже: перекатываться он отказывается и останавливает работу.

Картинка с колёсиками неточна в одном: десятичных колёсиков в компьютере нет. Число хранится в двоичных цифрах, битах, поэтому пределы типов — не 999, а числа вроде 127.

Зачем об этом думать? В 2014 году число просмотров клипа Gangnam Style на YouTube перевалило за 2147483647. Это предел целого числа из 32 бит, и YouTube перевёл счётчик просмотров на 64-битные числа.

Пределы целых типов

На компьютерах и смартфонах тип Int занимает 64 бита. Самое маленькое и самое большое значения Int хранятся в Int.min и Int.max:

SWIFT
print(Int.min) // -9223372036854775808
print(Int.max) // 9223372036854775807
print(Int8.min, Int8.max) // -128 127

Что напечатала программа:

  1. Int.min и Int.max — пределы Int, примерно девять квинтиллионов в обе стороны.
  2. Int8 занимает всего 8 бит, и в нём помещаются числа от -128 до 127.

Кроме Int и Int8, есть Int16, Int32 и Int64: число в названии показывает, сколько бит занимает тип. В обычных программах почти всегда берут Int. Маленькие типы встречаются там, где на счету каждый бит памяти, например в файлах. В этом уроке Int8 удобен тем, что его край близко.

Разные целые типы не смешиваются: к Int8 нельзя прибавить Int. Число другого типа сначала переводят, например Int8(bonus). Число, записанное прямо в коде, например 10, само принимает нужный тип, поэтому его прибавляют к Int8 без перевода.

Когда результат не помещается

Ситуацию, когда результат действия не помещается в свой тип, называют переполнением. Обычные +, - и * молча через край не переходят никогда. Что именно произойдёт, зависит от того, когда Swift заметит переполнение.

Компилятор видит переполнение

SWIFT
var level: Int8 = 120
level += 10 // ✗
TEXT
error: arithmetic operation '120 + 10' (on type 'Int8') results in an overflow

Компилятор сам посчитал 120 + 10, получил 130 и отказался собирать программу. Как и при любой ошибке компиляции, программа не запустится вовсе.

Программа замечает переполнение во время работы

SWIFT
let bonus = 10
var health: Int8 = 120
health += Int8(bonus) // ✗
print(health)

Здесь bonus имеет тип Int, поэтому его переводят в Int8. Сумму health и Int8(bonus) компилятор заранее не посчитал, и проверка сработала при запуске:

  1. Сумма 130 не поместилась в Int8.
  2. Программа аварийно завершилась на строке с +=: остановилась посреди работы.
  3. print(health) так и не выполнился.

При многих аварийных остановках программа печатает строку Fatal error: с причиной, а при переполнении — нет. Терминал выводит только отчёт об остановке со словами Illegal instruction (так в Linux). Причину показывает Xcode: он останавливается на этой строке с пояснением Swift runtime failure: arithmetic overflow.

Какие переполнения компилятор найдёт заранее, зависит от того, что он сумел посчитать сам. Полагаться на это нельзя: в настоящих программах числа приходят во время работы, их вводит пользователь или присылает сервер. Надёжно одно: обычный + через край не переходит, он останавливает программу.

Этот вопрос часто задают на собеседовании: что будет при переполнении Int в Swift? Хороший ответ: программа аварийно завершится, а переход через край пишут явно, особыми операторами.

Переход через край нарочно

Иногда нужен именно счётчик, который перекатывается: после 127 пусть идёт -128. Для этого в Swift есть операторы переполнения &+, &- и &*. Они считают как +, - и *, но результат, который не поместился, переносят на другой край типа.

Нажимайте «Следующий шаг» и следите за counter: на второй прибавке обычный + остановил бы программу, а &+ переносит счёт на -128.

Вживую&+ переходит через крайшаг 1 из 9 · рамок: 1

У counter тип Int8: в нём помещаются числа от -128 до 127.

Следите за counter: на второй прибавке обычный + остановил бы программу

Что показал виджет:

  • 126 &+ 1 даёт 127: результат помещается, и &+ работает как +.
  • 127 &+ 1 даёт -128: после самого большого Int8 идёт самое маленькое.
  • -127 &- 2 даёт 127: через нижний край счёт переходит наверх.

Такие операторы нужны редко, например в контрольных суммах — числах, по которым проверяют, не испортился ли файл. Перенос через край там заложен в сам расчёт.

Частая ошибка

Программа аварийно завершилась на переполнении, и + заменили на &+, чтобы сбоя не было:

SWIFT
var score: Int8 = 120
score = score &+ 10 // ✗
print(score) // -126

Сбоя больше нет, но счёт стал отрицательным: -126 вместо 130. Программа работает дальше с неверным числом и никого не предупреждает. Если число может вырасти, выберите тип, в который оно поместится. Обычно это Int.

Главное

  • У каждого целого типа есть предел: Int8 хранит числа от -128 до 127, а Int — до Int.max.
  • Переполнение, которое компилятор посчитал сам, даёт ошибку компиляции.
  • Переполнение во время работы аварийно завершает программу на той строке, где оно случилось, и сообщения Fatal error при этом нет.
  • &+, &- и &* переходят через край нарочно, и переполнение ими не исправляют.