Арифметика: целочисленное деление, остаток, переполнение
Чему вы научитесь:
- предсказывать результат
/и%для целых чисел, в том числе отрицательных; - проверять условия сравнениями и операторами
&&,||,!; - читать, ставить и снимать отдельный бит в числе типа
unsigned; - ставить скобки там, где приоритет операторов меняет смысл выражения.

Операторы +, - и * работают с целыми числами так же, как в школьной арифметике. С делением иначе: если оба числа имеют тип int, результат тоже будет int, а у int нет дробной части.
#include <iostream>
int main() {
int apples = 17;
int kids = 5;
std::cout << apples / kids << '\n'; // 3
std::cout << apples % kids << '\n'; // 2
}Представьте, что семнадцать яблок раскладывают поровну на пять тарелок, по одной на каждого ребёнка. На каждую тарелку ложится по три яблока, а два остаются в корзине. Оператор / дал частное — сколько яблок на одной тарелке, а % — остаток, то есть то, что осталось в корзине.
Целочисленное деление
Значения, с которыми работает оператор, называют операндами. Деление двух целых операндов называют целочисленным: дробная часть результата отбрасывается, а не округляется. Поэтому 19 / 5 тоже равно 3, хотя 3.8 ближе к четырём. Дробная часть отбрасывается и у отрицательного результата: -17 / 5 — это -3, а не -4.
Если хотя бы один операнд имеет тип double, второй преобразуется в double, и деление сохраняет дробную часть: 17 / 5.0 равно 3.4. Целочисленное деление на 0 — неопределённое поведение: стандарт не говорит, что тогда произойдёт.
Остаток
Оператор % работает только с целыми операндами. Частное и остаток связаны: a / b * b + a % b равно a. Поэтому знак остатка совпадает со знаком делимого — левого операнда:
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. Если результат +, - или * в этот диапазон не помещается, происходит переполнение. Для int и long long это тоже неопределённое поведение.
Беззнаковый тип unsigned (полное имя — unsigned int) хранит только неотрицательные числа, а литерал с суффиксом u, например 5u, имеет тип unsigned. Для него выход за границу определён: вычисления идут по кругу, и после максимума снова следует 0. Так работает механический счётчик пробега в машине: после последнего числа он снова показывает одни нули.
#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 и ждут дробь:
#include <iostream>
int main() {
double half = 5 / 2; // ✗
std::cout << half << '\n'; // 2
}Компилятор ошибки не видит, и программа печатает 2. Сначала выполняется 5 / 2 — целочисленное деление, и только его результат 2 превращается в double. Тип переменной слева на деление не влияет: важны типы операндов. Напишите 5 / 2.0, и в half окажется 2.5.
Главное
- Деление двух целых чисел отбрасывает дробную часть:
17 / 5— это3, а-17 / 5— это-3. %даёт остаток, и знак остатка совпадает со знаком левого операнда.- Переполнение
int— неопределённое поведение, аunsignedидёт по кругу. - Дробь даёт только деление, в котором хотя бы один операнд —
double.
Подпишись, чтобы пройти курс
Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.