Арифметика: целочисленное деление, остаток, переполнение
Операторы +, - и * работают с целыми числами так же, как в школьной арифметике. С делением иначе: если оба числа имеют тип 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
}Семнадцать яблок делятся на пятерых по три, и два яблока остаются. Оператор / дал частное, а % — остаток от деления.
Целочисленное деление
Значения, с которыми работает оператор, называют операндами (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. Поэтому знак остатка совпадает со знаком делимого — левого операнда:
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.
#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
}Компилятор (compiler) ошибки не видит, и программа печатает 2. Сначала выполняется 5 / 2 — целочисленное деление, и только его результат 2 превращается в double. Тип переменной слева на деление не влияет: важны типы операндов. Напишите 5 / 2.0, и в half окажется 2.5.