Арифметика

Целые числа

В 1994 году процессор Pentium неправильно делил числа из-за ошибки в битовой таблице целых. Intel потратила 475 млн долларов на замену чипов. В 2006 году ошибка в алгоритме сортировки Java - mid = (lo + hi) / 2 - вызывала переполнение int32 на массивах длиннее миллиарда элементов. Оба инцидента - следствие одного и того же: непонимания того, как целые числа живут в железе.

  • **L1 vs L2 loss в ML**: |y - y_hat| против (y - y_hat)^2 - разница в одном знаке меняет поведение модели на выбросах
  • **Two's complement**: как CPU хранит -5 в битах и почему int32 overflow даёт отрицательное число
  • **Модулярная арифметика**: RSA, хэш-функции, кольцевые буферы в PyTorch DataLoader - всё строится на Z_n

Брахмагупта: математик, опередивший Европу на тысячу лет

Индийский математик **Брахмагупта** в труде «Брахмаспхутасиддханта» впервые в истории записал строгие правила арифметики с положительными числами («dhana» - имущество), отрицательными («rina» - долг) и нулём. Правило умножения знаков: «произведение двух долгов есть имущество» - это тот самый «минус на минус даёт плюс», которым мы пользуемся сегодня.

Долг, вычтенный из пустоты, есть имущество. Имущество, вычтенное из пустоты, есть долг.

Правила знаков из «Брахмаспхутасиддханты» - прямой предок операций над int в любом языке программирования.

Зачем нужны отрицательные числа

В 1994 году процессор Intel Pentium допустил ошибку в делении. Причина - битовый сбой в таблице целых чисел внутри FPU. Итог: отзыв партии, 475 млн долларов убытков, скандал на весь мир. Целые числа - не абстракция из учебника. Они живут внутри каждого чипа.

Натуральные числа решают задачу счёта. Но вычитание ломает их: уравнение $x + 5 = 3$ не имеет решения в $\mathbb{N}$. Вопрос при этом абсолютно реальный: «сколько было до того, как добавили 5 и получили 3?». Ответ: был долг. Недостаток 2 единиц. И чтобы записать этот ответ, нужны числа левее нуля.

Европейские математики XVI века называли отрицательные числа «numeri absurdi» - абсурдные числа. Декарт допускал их неохотно, Паскаль считал «фикцией». Торговцы, банкиры и военные инженеры думали иначе - им нужно было считать долги, дефициты и потери. Практика победила философию примерно через 500 лет сопротивления.

Почему уравнение x + 5 = 3 не имеет решения в натуральных числах?

Множество Z и числовая прямая

**Целые числа** $\mathbb{Z}$ - это натуральные, ноль и их противоположные. Название от немецкого *Zahlen* - «числа». Формально: $\mathbb{Z} = \{\ldots, -3, -2, -1, 0, 1, 2, 3, \ldots\}$. Натуральные числа - подмножество: $\mathbb{N} \subset \mathbb{Z}$.

**Противоположное число:** для каждого $n \in \mathbb{Z}$ существует $-n$ такое что $n + (-n) = 0$. Это определяет ноль как нейтральный элемент сложения. Именно это свойство делает $\mathbb{Z}$ **группой** по сложению - фундаментальная алгебраическая структура, на которой строится вся криптография.

Целые числа **замкнуты** относительно $+$, $-$, $\times$: результат этих операций всегда целое число. Но не $\div$: $7 \div 3 \notin \mathbb{Z}$. Именно эта незамкнутость по делению - мотивация для дробей и рациональных чисел.

Какое из следующих утверждений верно?

Модуль числа: расстояние и L1-loss

**Модуль** (абсолютная величина) числа $x$ - это расстояние от $x$ до нуля на числовой прямой. Обозначается $|x|$. Не «убрать минус» - именно расстояние. Разница принципиальна: расстояние всегда неотрицательно и определено геометрически.

Модуль - это не просто школьная операция. В машинном обучении $|y - \hat{y}|$ - это **L1-loss** (Mean Absolute Error). Сравните с L2-loss $(y - \hat{y})^2$: квадрат штрафует большие ошибки непропорционально, поэтому модели на L2 чувствительны к выбросам. L1-loss устойчив - одна аномальная точка не уводит всю модель. Разница в один знак ($|\cdot|$ против $(\cdot)^2$) определяет поведение системы на реальных данных.

**Знаковая функция:** $\text{sign}(x) = x / |x|$ при $x \ne 0$ (и $0$ при $x = 0$). Субградиент L1-loss по $\hat{y}$ равен $-\text{sign}(y - \hat{y})$ - постоянный шаг $\pm 1$ в отличие от линейно-растущего градиента L2. Именно это делает L1-оптимизацию робастной: выброс тянет с той же силой, что и обычная точка.

Модуль просто убирает знак минус

Модуль - расстояние до нуля; для положительных чисел он ничего не меняет

|5| = 5 не потому что «убрали несуществующий минус», а потому что 5 уже находится на расстоянии 5 от нуля. Определение через расстояние обобщается на векторы, функции и метрические пространства - там «убрать знак» не работает.

Почему L1-loss (MAE) более устойчив к выбросам, чем L2-loss (MSE)?

Как CPU хранит отрицательные числа

Компьютер хранит числа в битах. Натуральные - просто: 5 = 101. Но как хранить -5? Наивный подход - выделить старший бит под знак. Проблема: появляется «отрицательный ноль» (-0 и +0 - разные битовые паттерны). Сложение ломается. Инженеры Intel и DEC в 1960-х выбрали другой путь - **дополнение до двух** (two's complement).

Два следствия, важных для ML. Первое: операция $x >> 1$ (сдвиг вправо) для отрицательных чисел в C/Java/C++ даёт результат, зависящий от платформы - это undefined behaviour. В Python оператор `//` работает иначе: $-7 // 2 = -4$ (округление к минус бесконечности), а не $-3$. Второе: при переполнении int32 в NumPy число «перепрыгивает» через границу. Сложение $2^{31} - 1 + 1$ даёт $-2^{31}$ - самое маленькое число. Без знания two's complement это выглядит как магия.

**Почему -1 >> 1 в C может дать -1, а не 0:** при арифметическом сдвиге знаковый бит копируется, а не заполняется нулями. -1 в two's complement - это все единицы (0xFF...F). Сдвиг вправо на 1 даёт снова все единицы - то есть -1. В Python же `-1 >> 1 = -1` по той же причине - Python использует арифметический сдвиг.

Почему переполнение int32 в NumPy при 2^31 - 1 + 1 даёт -2^31, а не ошибку?

Ключевые идеи

  • $\mathbb{Z} = \{\ldots, -2, -1, 0, 1, 2, \ldots\}$ - расширение $\mathbb{N}$ для решения $x + a = b$ при $a > b$
  • Числовая прямая: правее = больше; среди отрицательных -1 > -100
  • $|x|$ - расстояние до нуля; в ML это L1-loss, робастный к выбросам
  • Two's complement: как CPU хранит отрицательные - переполнение int32/int64 не баг архитектуры, а прямое следствие
  • $\mathbb{Z}$ замкнуто под $+$, $-$, $\times$; не замкнуто под $\div$ - мотивация для $\mathbb{Q}$

Связанные темы

Целые числа - шаг к более широким структурам:

  • Сложение и вычитание — Операции с целыми числами - правила знаков на практике
  • Рациональные числа — Следующее расширение: Q закрывает Z под делением
  • Модулярная арифметика — Арифметика остатков - основа хэш-функций и криптографии
  • Двоичная система — Two's complement - хранение целых в CPU
  • Компьютерная арифметика — Переполнение int32/int64 в NumPy и C

Вопросы для размышления

  • Почему европейские математики сопротивлялись отрицательным числам 1000 лет, если они нужны для простейших задач с долгами?
  • Как знание two's complement помогает понять, почему -1 % 2 == 1 в Python, но -1 % 2 == -1 в C?
  • L1-loss использует |x|, L2-loss использует x^2. Придумайте задачу, где L2 предпочтительнее L1.

Связанные уроки

  • ar-03-addition — Операции над целыми - следующий шаг
  • ar-28-modular — Модулярная арифметика строится на свойствах Z
  • ar-26-binary — Дополнение до двух - хранение отрицательных в CPU
  • ar-44-crypto-intro — RSA и хэши работают в кольцах целых чисел
  • ar-45-computer-arithmetic — Переполнение int32/int64 - целые числа в железе
  • calc-01-sequences
Целые числа

0

1

Войти