Перейти к содержанию

1.3 Работа с числами

В задачах по спортивному программированию корректная работа с числами имеет ключевое значение. Ошибки переполнения, неточности вычислений и неправильное использование типов данных — одни из самых частых причин неверных решений.

Целые числа

Самый часто используемый целочисленный тип — int. Это 32-битный тип со значениями примерно от −2·10^9 до 2·10^9.

Если этого диапазона недостаточно, используется тип long long (64 бита), который хранит значения примерно от −9·10^18 до 9·10^18.

long long x = 98765432123456789LL;

Суффикс LL указывает, что литерал имеет тип long long.

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

Даже если результат сохраняется в long long, промежуточные вычисления могут выполняться в типе int.

int a = 50000;
long long b = a * a;
cout << b << "\n";  // неверный результат из‑за переполнения

Проблема в том, что выражение a * a вычисляется как int. Решение:

long long b = 1LL * a * a;

или

long long a = 50000;
long long b = a * a;

В большинстве задач long long достаточно. В некоторых компиляторах также доступен тип __int128_t, но он поддерживается не везде.


Модульная арифметика

Запись x mod m означает остаток от деления x на m.

Например:

23 mod 7 = 2

Во многих задачах требуется вывести ответ по модулю числа, например 10^9 + 7. Это позволяет работать с очень большими числами, не выходя за пределы стандартных типов.

Свойства остатка

Остаток можно брать после каждой операции:

  • (a + b) mod m = (a mod m + b mod m) mod m
  • (a − b) mod m = (a mod m − b mod m) mod m
  • (a · b) mod m = (a mod m · b mod m) mod m

Благодаря этому числа никогда не становятся слишком большими.

Пример: вычисление степени по модулю

long long result = 1;
for (int i = 0; i < n; i++) {
    result = (result * 3) % m;
}
cout << result << "\n";

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

В C++ остаток может быть отрицательным. Чтобы гарантировать значение в диапазоне 0…m−1:

x %= m;
if (x < 0) x += m;

Это особенно важно при операциях вычитания.


Числа с плавающей точкой

Обычно используются типы:

  • double (64 бита)
  • long double (более высокая точность)

Чаще всего double достаточно.

Вывод с заданной точностью

double x = 7.0 / 3.0;
printf("%.8f\n", x);

Это выведет число с 8 знаками после запятой.

Проблема точности

Некоторые числа невозможно точно представить в двоичной системе.

double x = 0.2 * 3;
printf("%.20f\n", x);

Результат может быть не ровно 0.6 из‑за погрешности округления.

Сравнение вещественных чисел

Нельзя напрямую сравнивать числа через ==.

Правильный способ:

if (abs(a - b) < 1e-9) {
    // считаем числа равными
}

Здесь 1e-9 — допустимая погрешность.

Важно помнить, что целые числа до 2^53 могут быть представлены точно в типе double.


Практические рекомендации

  1. По умолчанию используйте long long, если возможны большие значения.
  2. При умножении больших чисел явно приводите тип.
  3. В задачах с большими ответами применяйте модульную арифметику.
  4. Для вещественных чисел используйте сравнение с эпсилон.
  5. Всегда проверяйте граничные случаи: ноль, отрицательные числа, переполнение.

Работа с числами — фундаментальный навык в алгоритмических задачах. Понимание диапазонов типов, свойств остатка и особенностей вещественных вычислений позволяет избежать множества скрытых ошибок.