Перейти к содержимому
Теория · Операторы и выражения · урок 1 из 8

Арифметика: целочисленное деление, остаток, переполнение

Операторы +, - и * работают с целыми числами так же, как в школьной арифметике. С делением иначе: если оба числа имеют тип int, результат тоже будет int, а у int нет дробной части.

C++
#include <iostream>

int main() {
    int apples = 17;
    int kids = 5;
    std::cout << apples / kids << '\n';  // 3
    std::cout << apples % kids << '\n';  // 2
}

Семнадцать яблок делятся на пятерых по три, и два яблока остаются. Оператор / дал частное, а % — остаток от деления.

Целочисленное деление

Значения, с которыми работает оператор, называют операндами (operands). Деление двух целых операндов называют целочисленным (integer division): дробная часть результата отбрасывается, а не округляется. Поэтому 19 / 5 тоже равно 3, хотя 3.8 ближе к четырём. Отрицательный результат тоже округляется к нулю: -17 / 5 — это -3, а не -4.

Если хотя бы один операнд имеет тип double, второй преобразуется в double, и деление сохраняет дробную часть: 17 / 5.0 равно 3.4. Целочисленное деление на 0 — неопределённое поведение (undefined behavior): стандарт не говорит, что тогда произойдёт.

Остаток

Оператор % работает только с целыми операндами. Частное и остаток связаны: a / b * b + a % b равно a. Поэтому знак остатка совпадает со знаком делимого — левого операнда:

C++
std::cout << -17 / 5 << ' ' << -17 % 5 << '\n';  // -3 -2
std::cout << 17 / -5 << ' ' << 17 % -5 << '\n';  // -3 2

Остаток помогает разбирать число на цифры: n % 10 — последняя цифра, а n / 10 — число без неё. Для 253 это 3 и 25.

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

Тип int хранит числа из ограниченного диапазона: там, где он занимает 4 байта, — от -2147483648 до 2147483647. Если результат +, - или * в этот диапазон не помещается, происходит переполнение (overflow). Для int и long long это тоже неопределённое поведение.

Беззнаковый тип unsigned (полное имя — unsigned int) хранит только неотрицательные числа, а литерал с суффиксом u, например 5u, имеет тип unsigned. Для него выход за границу определён: вычисления идут по кругу, и после максимума снова следует 0.

C++
#include <iostream>

int main() {
    unsigned zero = 0u;
    std::cout << zero - 1u << '\n';  // 4294967295
    std::cout << zero - 1u + 1u << '\n';  // 0
}

Число 4294967295 — максимум 4-байтового unsigned. Вычитание из нуля перешло через границу вниз, а прибавление единицы к максимуму — вверх.

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

Результат деления двух int сохраняют в переменную типа double и ждут дробь:

C++
#include <iostream>

int main() {
    double half = 5 / 2;  // ✗
    std::cout << half << '\n';  // 2
}

Компилятор (compiler) ошибки не видит, и программа печатает 2. Сначала выполняется 5 / 2 — целочисленное деление, и только его результат 2 превращается в double. Тип переменной слева на деление не влияет: важны типы операндов. Напишите 5 / 2.0, и в half окажется 2.5.