Числовые типы и арифметика

Разбираем числовые типы Zig и его честное отношение к переполнению.

Переполнение целого в Zig по умолчанию — ошибка: в отладочной сборке оно ловится и роняет программу с понятным сообщением, а не молча даёт неверный результат, как в C.

Имена числовых типов в Zig говорят сами за себя. Целые — это буква разрядности (i для знаковых, u для беззнаковых) и число бит: i8, u16, i32, u64. В отличие от C, где размер int зависит от платформы, в Zig i32 — это ровно 32 бита везде. Это убирает целый класс ошибок переносимости.

Целые произвольной разрядности

Zig идёт дальше стандартных размеров: можно объявить целое любой ширины от 1 до 65535 бит, например u3 или i7. Это бесценно для работы с регистрами оборудования и упакованными битовыми полями.

const flag: u1 = 1;     // ровно один бит
const color: u3 = 5;    // три бита: значения 0..7
const big: u128 = 0;    // 128-битное целое

Переполнение ловится, а не игнорируется

Главное отличие от C: знаковое переполнение в C — неопределённое поведение, а беззнаковое молча оборачивается. В Zig обычная арифметика в отладочной сборке проверяет переполнение и аварийно завершает программу с диагностикой. Это превращает скрытый баг в громкую ошибку.

var x: u8 = 250;
x += 10; // 260 не влезает в u8 → паника в отладке:
         // "integer overflow"

Когда оборачивание нужно осознанно (например, в хеш-функциях), используют специальные операторы с суффиксом %: +%, -%, *%. Они явно говорят «я хочу оборачивание».

var x: u8 = 250;
x +%= 10; // явное оборачивание: 260 → 4 (260 - 256)

Плавающая точка и приведение

Типы с плавающей точкой — f16, f32, f64, f128. Zig не делает неявных сужающих приведений: смешать i32 и u8 в одном выражении просто так нельзя — нужно явное приведение через @intCast или @as. Это тоже про явность: вы видите каждое преобразование типа.

const a: u8 = 200;
const b: u32 = a;          // расширение безопасно, разрешено
const c: u8 = @intCast(b); // сужение — только явно

Как работает под капотом

Проверки переполнения вставляет компилятор только в отладочных и safe-сборках. В режиме ReleaseFast они убираются ради скорости, а поведение становится неопределённым — как в C. Поэтому отлаживать логику нужно в safe-режиме, где переполнение поймается. Операторы +% работают одинаково во всех режимах: оборачивание гарантировано.

Частые ошибки

Самая частая — ждать молчаливого оборачивания как в C и удивляться панике. Если оборачивание нужно — берите +%. Вторая ошибка — попытка неявно сузить тип; компилятор не даст и потребует @intCast. Третья — забыть, что в ReleaseFast защита от переполнения отключена, и полагаться на неё в продакшене.

Итог

  • Имена типов задают точную разрядность: i32 — всегда 32 бита.
  • Можно объявить целое любой ширины, например u3 — полезно для битовых полей.
  • Переполнение в safe-сборке ловится и роняет программу; осознанное оборачивание — операторы +%, -%, *%.
  • Неявных сужающих приведений нет — нужно @intCast/@as.
Проверьте себя
1. Что делает обычное сложение u8-переменных при переполнении в отладочной сборке Zig?
AМолча оборачивается как в C
BРоняет программу с ошибкой integer overflow
CРасширяет тип до u16
DВозвращает максимальное значение
2. Как в Zig получить осознанное оборачивание при сложении?
AИспользовать обычный +
BИспользовать оператор +%
CПривести к u16
DИспользовать @intCast