Если оба операнда / — целые, деление тоже целое: дробная часть не округляется, а отбрасывается в сторону нуля. Оператор % даёт остаток от такого деления.

7 / 2     // 3, не 3.5 и не 4
7 % 2     // 1
11 / 4    // 2
11 % 4    // 3

В допустимых случаях результаты связаны равенством a == (a / b) * b + a % b. Это равенство и определяет знак остатка: раз деление усекает к нулю, остаток обязан взять знак делимого.

Чтобы получить дробный результат, хотя бы один операнд должен быть вещественным — иначе делить будут целые, а результат уже потом расширится до double, когда терять нечего:

double wrong = 7 / 2;                      // 3.0 — деление было целым
double right = 7.0 / 2;                    // 3.5
double also_right = static_cast<double>(a) / b;

Отрицательные числа

Начиная с C++11 деление усекает к нулю, а знак остатка совпадает со знаком делимого:

-7 / 2     // -3   (не -4: усечение к нулю, а не вниз)
-7 % 2     // -1
 7 % -2    //  1
-11 / 4    // -2
-11 % 4    // -3

Здесь же лежит разница со сдвигом: -7 >> 1 даёт −4, потому что сдвиг вправо округляет вниз, а деление — к нулю. См. Побитовые операции.

Это отличается от математического определения остатка, где он неотрицателен, и от поведения % в Python, где знак берётся от делителя. Если нужен именно неотрицательный остаток по модулю n, его получают в два действия:

int mod = ((value % n) + n) % n;   // всегда в диапазоне [0, n)

Первое % приводит значение в диапазон (-n, n), прибавление n убирает отрицательность, второе % возвращает в [0, n).

Деление на ноль

Целочисленное деление на ноль — Неопределённое поведение. Не исключение, не бесконечность: на x86 программа обычно падает с SIGFPE, но полагаться нельзя ни на что. Делитель, который может оказаться нулём, проверяют заранее — и проверку ставят слева от &&, чтобы сработало короткое замыкание:

bool large = y != 0 && x / y > 2;   // деления не будет при y == 0

См. Логические операции и короткое замыкание.

Сообщение оболочки Floating point exception при этом не означает ни дробных чисел, ни исключения C++ — так конкретная среда сообщает о сбое.

Есть и второй, менее известный случай UB: деление минимального знакового значения на −1. Положительный результат не представим в том же типе, поэтому проверки одного лишь b != 0 для произвольных знаковых данных недостаточно.

Беззнаковый тип от деления на ноль не спасает — см. Беззнаковые типы. А у вещественных иначе: там получаются inf и nan, и переносить это поведение на целые нельзя.

Приёмы, которые стоит знать

Деление с округлением вверх для неотрицательных целых:

int parts = (total + size - 1) / size;   // вместо std::ceil(total / size)

Прибавление size - 1 подталкивает результат через границу, если остаток ненулевой. Через double и std::ceil то же самое делать не стоит: появляется погрешность, а на больших числах — и потеря точности.

Разбор числа на разряды — % 10 даёт последнюю цифру, / 10 убирает её:

constexpr int kBase = 10;
int last_digit = value % kBase;
value /= kBase;

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

  • Ожидать 3.5 от 7 / 2 и получить 3.
  • Считать, что -7 % 2 даст 1: даст −1.
  • Приводить к double результат, а не операнд: static_cast<double>(a / b) уже поздно, деление произошло в целых.
  • Делить на переменную, не проверив её на ноль.