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

Размеры целых и переполнение

В прошлом уроке калькулятор Go отбрасывал дробную часть при делении. Теперь посмотрим на другое его правило: у каждого целого типа ограниченное число разрядов.

Вспомните счётчик пробега в старой машине. У него шесть колёсиков с цифрами, и больше 999999 он показать не может. Проедьте ещё километр — и счётчик покажет 000000: колёсики провернулись и начали сначала.

Целые типы Go устроены так же. Переменная типа int8 хранит числа от -128 до 127. Если прибавить единицу к максимуму, ошибки не будет: значение перейдёт на другой конец диапазона.

GO
package main

import "fmt"

func main() {
    var x int8 = 127
    x++
    fmt.Println(x) // -128
}

Давайте разберём программу по строкам:

  1. var x int8 = 127 кладёт в x самое большое число, которое помещается в int8.
  2. x++ увеличивает переменную на единицу, а x-- уменьшает. Тип при этом не меняется, поэтому результат остаётся в диапазоне int8.
  3. fmt.Println(x) печатает -128: число 128 в int8 не помещается, и значение перешло на другой конец диапазона.

Здесь аналогия со счётчиком пробега расходится с Go. У счётчика нет отрицательных чисел, а у int8 есть. Поэтому после максимума идёт не ноль, а самое маленькое отрицательное число.

Типы по размеру

Цифра в имени типа — это число бит, а бит в байте восемь. Чем больше байт, тем шире диапазон:

ТипБайтДиапазон
int81от -128 до 127
int162от -32768 до 32767
int324около ±2,1 миллиарда
int648около ±9,2 · 10¹⁸
uint81от 0 до 255

Целые типы делятся на три группы. Разберём каждую.

Знаковые: от int8 до int64

Эти типы хранят и положительные, и отрицательные числа. Примерно половина диапазона отдана отрицательным, поэтому у int8 минимум -128, а максимум только 127.

Такой тип выбирают, когда значение может оказаться меньше нуля: температура, разница двух чисел, изменение счёта.

Беззнаковые: от uint8 до uint64

Типы с буквой u в начале — беззнаковые: они хранят только неотрицательные числа, зато их максимум примерно вдвое больше.

  • Кроме uint8 есть uint16, uint32 и uint64.
  • byte — другое имя для uint8.
  • Минимум у всех беззнаковых типов — 0, поэтому вычитание может увести их за нижнюю границу.

Тип int

На 64-битных системах и в задачах курса тип int занимает 8 байт, как int64.

Для обычных целых чисел берите int: его диапазона хватает почти всегда. Типы конкретного размера нужны, когда важен формат данных, например байты файла хранят в uint8.

Выход за границы

Если результат не помещается в диапазон типа, происходит переполнение. Что за ним последует, зависит от того, когда оно случилось.

Во время выполнения

Во время выполнения Go о переполнении не сообщает:

  • после максимума идёт минимум: int16 после 32767 даёт -32768;
  • ниже минимума идёт максимум: int8 после -128 даёт 127;
  • у беззнаковых типов так же: uint8 после 255 даёт 0, как счётчик пробега.

Нажимайте x++ и следите за восемью битами uint8: переносы бегут справа налево, а после 255 все биты становятся нулями.

Вживуюuint8: восемь битовx = 253

x = 253: в uint8 восемь битов, самое большое число — 255. Нажмите x++.

Следите за переносом из старшего бита

Об этом часто спрашивают на собеседовании: что будет при переполнении целого числа в Go? Ответ: значение молча переходит на другой конец диапазона, без паники и без ошибки.

При сборке

Константу, которая не помещается в тип, компилятор не пропустит:

GO
var b uint8 = 256 // ✗
TEXT
cannot use 256 (untyped int constant) as uint8 value in variable declaration (overflows)

Компилятор проверяет только значения, известные при сборке. Инструкция b++ меняет переменную во время выполнения, и такой проверки там нет.

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

Количество, которое не бывает отрицательным, хранят в беззнаковом типе, а потом вычитают из него:

GO
package main

import "fmt"

func main() {
    var stock uint8 = 3
    stock = stock - 5  // ✗
    fmt.Println(stock) // 254
}

Ни компилятор, ни программа ошибки не замечают. Разность 3 - 5 перешла через ноль вниз и стала 254, как счётчик, который открутили назад. Величину, которая при вычитании может уйти в минус, храните в знаковом типе, например int.

Главное

  • У каждого целого типа свой диапазон: у int8 от -128 до 127, у uint8 от 0 до 255.
  • При переполнении во время выполнения значение молча переходит на другой конец диапазона.
  • Константу, которая не помещается в тип, компилятор не пропускает.
  • Для обычных чисел берите int, а величину, которая может уйти в минус, не храните в беззнаковом типе.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом