Кодирование и обработка чисел в вычислительных системах

Курс объясняет, как числа представляются в компьютере и как выполняются основные арифметические операции над ними. Рассматриваются целые и вещественные форматы, переполнения, округления, ошибки представления и практические приёмы корректной и эффективной обработки чисел.

1. Системы счисления и преобразование чисел

Системы счисления и преобразование чисел

Зачем нужны системы счисления в вычислительных системах

Компьютер хранит и обрабатывает данные в виде битов — нулей и единиц. Из-за этого внутренняя арифметика и представление чисел естественным образом опираются на двоичную систему счисления. Однако человеку удобнее читать и записывать числа в десятичной системе, а инженерам часто удобны также восьмеричная и шестнадцатеричная системы как компактные формы записи двоичных данных.

В этой статье разберём:

  • что такое система счисления и основание
  • как читается позиционная запись числа
  • как переводить целые и дробные числа между основаниями
  • как быстро переводить между 2, 8 и 16
  • Основные понятия

    Система счисления — способ записи чисел с помощью набора символов (цифр) и правил.

    Основание системы счисления (часто обозначают ) — количество различных цифр, которые используются в системе.

  • В десятичной системе , цифры: 0–9
  • В двоичной системе , цифры: 0–1
  • В восьмеричной системе , цифры: 0–7
  • В шестнадцатеричной системе , цифры: 0–9 и A–F (где A=10, B=11, ..., F=15)
  • Позиционные и непозиционные системы

    Позиционная система счисления — система, где значение цифры зависит от её позиции (разряда) в записи числа.

    Пример: в числе 505 цифра 5 слева означает пять сотен, а 5 справа — пять единиц.

    Непозиционные системы встречаются реже (пример — римские числа), и в вычислительных системах почти не используются из-за неудобства арифметики.

    Как позиционная запись превращается в значение числа

    Целая часть

    Если число записано в системе с основанием как последовательность цифр , то его значение равно сумме разрядов:

    Разберём элементы формулы:

  • — значение числа (в привычном смысле)
  • — знак суммирования: нужно сложить вклад каждого разряда
  • — номер разряда, начиная с 0 справа (разряд единиц)
  • — цифра в разряде (она всегда меньше основания: )
  • — основание системы счисления
  • вес разряда (например, для десятичной: , , )
  • Пример: переведём в десятичное значение.

  • справа налево разряды: , , ,
  • получаем:
  • Значит, .

    Дробная часть

    Дробная часть тоже подчиняется позиционному принципу, только веса идут в отрицательные степени основания.

    Если запись выглядит как (точка — разделитель), то вклад дробной части:

  • — первая цифра после точки
  • , и так далее
  • Пример: .

    -

    Значит, .

    !2^-1, 2^-2, 2^-3), стрелками выделены целая и дробная части, снизу показан вклад каждого разряда в сумму | Иллюстрация показывает, как каждый бит влияет на итоговое значение числа

    Часто используемые основания и их цифры

    | Основание | Название | Допустимые цифры | Где встречается | |---:|---|---|---| | 2 | двоичная | 0–1 | внутренняя логика и память компьютера | | 8 | восьмеричная | 0–7 | компактная запись двоичных данных (исторически) | | 10 | десятичная | 0–9 | повседневная запись чисел | | 16 | шестнадцатеричная | 0–9, A–F | адреса памяти, машинные коды, цвета, хеши |

    Перевод чисел в десятичную систему

    Из любой системы в десятичную (целые)

    Алгоритм:

  • пронумеровать разряды справа налево, начиная с 0
  • умножить каждую цифру на соответствующий вес
  • сложить результаты
  • Пример: .

    - -

    Значит, .

    Из любой системы в десятичную (дробные)

    Алгоритм:

  • для цифр после точки использовать веса
  • суммировать вклад
  • Пример: в двоичной системе мы пока не переводим, но для обратного направления полезно понимать: если бы было , то это .

    Перевод из десятичной системы в другую (целые числа)

    Самый практичный способ для целых чисел — деление с остатком.

    Алгоритм перевода в основание :

  • Разделить на , запомнить остаток.
  • Результат деления снова разделить на , опять запомнить остаток.
  • Повторять, пока частное не станет равно 0.
  • Ответ — остатки, прочитанные в обратном порядке.
  • Пример: переведём в двоичную.

    | Деление | Частное | Остаток (бит) | |---|---:|---:| | | 22 | 1 | | | 11 | 0 | | | 5 | 1 | | | 2 | 1 | | | 1 | 0 | | | 0 | 1 |

    Читаем остатки снизу вверх: .

    Значит, .

    Перевод из десятичной системы в другую (дробные числа)

    Для дробной части используется умножение на основание.

    Идея: при умножении дроби на целая часть результата становится очередной цифрой после точки.

    Алгоритм для дроби в основание :

  • Умножить дробную часть на .
  • Целая часть произведения — следующая цифра.
  • Новая дробная часть — дробная часть произведения.
  • Повторять до получения 0 или до нужной точности.
  • Пример: переведём в двоичную.

    | Шаг | Умножаем на 2 | Целая часть (бит) | Новая дробная часть | |---|---:|---:|---:| | 1 | | 1 | 0.25 | | 2 | | 0 | 0.5 | | 3 | | 1 | 0.0 |

    Получили .

    Важно: не всякая десятичная дробь имеет конечную запись в двоичной системе. Например, в двоичной системе будет бесконечной периодической дробью. В вычислительных системах это приводит к погрешностям представления, к которым мы вернёмся в следующих темах.

    Быстрые переводы между 2, 8 и 16

    Поскольку:

    - -

    можно переводить без деления и умножения — группируя биты.

    Двоичная ↔ восьмеричная

  • чтобы получить восьмеричную запись из двоичной: разбить двоичное число справа налево на группы по 3 бита
  • каждая тройка бит — одна восьмеричная цифра
  • Пример: .

  • группируем:
  • переводим тройки: , ,
  • Получаем .

    Двоичная ↔ шестнадцатеричная

  • разбить справа налево на группы по 4 бита
  • каждая четвёрка — одна шестнадцатеричная цифра
  • Пример: .

  • группируем:
  • переводим: , ,
  • Получаем .

    Шестнадцатеричная ↔ восьмеричная

    Прямого группирования нет (4 и 3 не совпадают). Практически делают так:

  • перевести 16 → 2 (каждый hex-символ в 4 бита)
  • затем 2 → 8 (группы по 3 бита)
  • Практические соглашения записи

    Чтобы не путать основания, используют обозначения:

  • индекс: , , ,
  • или префиксы (в языках программирования): 0b1011, 0o77, 255, 0xFF
  • Важно: префиксы зависят от языка и его правил, а индекс — универсальная учебная запись.

    Типичные ошибки при переводе

  • Путать порядок чтения остатков в методе деления (остатки читаются снизу вверх).
  • Использовать недопустимые цифры для основания (например, цифру 8 в восьмеричной системе).
  • Забывать, что дроби могут получаться бесконечными (нужна остановка по точности).
  • Неверно группировать биты (группировать нужно от точки: для целой части — справа налево, для дробной — слева направо).
  • Что дальше по курсу

    В этой статье мы научились понимать позиционную запись и переводить числа между системами счисления. Это фундамент для следующих тем курса: представления отрицательных чисел, фиксированной и плавающей запятой, а также анализа ошибок округления.

    Источники для углубления

  • Wikipedia: Positional notation
  • Wikipedia: Binary number
  • Wikipedia: Hexadecimal
  • 2. Двоичное кодирование целых: знаковые форматы и переполнение

    Двоичное кодирование целых: знаковые форматы и переполнение

    Связь с предыдущей темой

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

    Ключевая идея этой темы:

  • один и тот же двоичный шаблон (например, 11111111) может означать 255, -1 или другое число — это зависит от выбранного формата кодирования целых
  • из-за конечного числа битов существует ограниченный диапазон представимых значений, а выход за него приводит к переполнению
  • Битовая ширина и диапазон значений

    Пусть у нас есть фиксированное количество битов (например, , , ). Тогда всего различных двоичных шаблонов:

    Пояснение элементов формулы:

  • — количество битов в коде числа
  • — потому что каждый бит может быть либо 0, либо 1
  • — количество разных комбинаций (и, следовательно, потенциальных значений)
  • Если числа без знака, все комбинации обычно интерпретируются как значения от 0 до максимума. Если числа со знаком, часть комбинаций отводится под отрицательные значения.

    Беззнаковые целые

    Беззнаковое (unsigned) кодирование трактует все биты как вклад в величину (нет отдельного признака “минус”).

    Для бит диапазон:

    Пояснение:

  • — числовое значение
  • — максимальное значение, когда все битов равны 1
  • Пример для 8 бит:

  • 00000000
  • 11111111
  • Беззнаковые числа удобны для:

  • размеров, счётчиков, индексов (там, где отрицательные значения не имеют смысла)
  • низкоуровневых битовых масок
  • Почему знаковые числа — отдельная тема

    Хотим хранить и положительные, и отрицательные целые в фиксированных битах. Нужно ответить на два вопроса:

  • как кодировать знак
  • как выполнять арифметику (сложение/вычитание) так, чтобы аппаратно это было просто и быстро
  • Исторически существовало несколько подходов. Мы рассмотрим основные: прямой код, обратный код, дополнительный код. На практике сегодня доминирует дополнительный код.

    Прямой код (sign-magnitude)

    Идея: старший бит — это знак, остальные биты — модуль.

  • знак: 0 — плюс, 1 — минус
  • модуль: обычное беззнаковое число
  • Пример (8 бит):

  • 00000101 =
  • 10000101 =
  • Проблемы прямого кода:

  • два нуля: 00000000 = , но также 10000000 =
  • сложение и вычитание сложнее: нужно отдельно учитывать знаки и модули
  • Из-за этого прямой код почти не используется для целочисленной арифметики общего назначения.

    Обратный код (one’s complement)

    Идея: положительные числа — как обычно, отрицательные получаются побитовым инвертированием положительного.

    Как получить из :

  • записать в двоичном виде
  • заменить каждый бит: 0 → 1, 1 → 0
  • Пример (8 бит):

  • = 00000101
  • = 11111010
  • Проблемы обратного кода:

  • снова два нуля: 00000000 и 11111111 (это )
  • сложение требует “переноса через край” (end-around carry), что усложняет реализацию
  • Обратный код встречался в некоторых старых системах, но сейчас редок.

    Дополнительный код (two’s complement) — стандарт де-факто

    Дополнительный код (two’s complement) сегодня используется почти во всех массовых архитектурах и языках на уровне машинной арифметики.

    Как получить отрицательное число в битах:

  • записать в двоичном виде (в битах)
  • инвертировать все биты
  • прибавить 1
  • Пример (8 бит) для :

  • = 00000101
  • инверсия: 11111010
  • плюс 1: 11111011 значит
  • Почему это удобно

  • один ноль: 00000000
  • сложение и вычитание работают тем же сумматором, что и для беззнаковых чисел (в пределах бит)
  • знак “встроен” в арифметику: старший бит (MSB) часто интерпретируется как признак отрицательности
  • Диапазон значений в дополнительном коде

    Для бит диапазон:

    Пояснение:

  • — количество битов
  • — интерпретируемое знаковое значение
  • связано с тем, что один бит (старший) играет роль знака и одновременно участвует в весах разрядов
  • минимальное значение существует, а симметричного “” нет, поэтому положительный максимум на 1 меньше
  • Пример для 8 бит:

  • минимум: , код 10000000
  • максимум: , код 01111111
  • !Схема соответствия двоичных шаблонов и значений в 8-битном two’s complement

    Как один и тот же шаблон битов может означать разные числа

    Рассмотрим 11111111 (8 бит):

  • как беззнаковое: это
  • как дополнительный код: это
  • Вывод: биты сами по себе — это только код. Смысл появляется, когда мы выбираем интерпретацию (unsigned или signed).

    Расширение знака (sign extension)

    Частая операция: увеличить разрядность, например, из 8 бит сделать 16 бит, сохранив значение.

  • для беззнаковых: слева дописываем нули
  • для знаковых в дополнительном коде: слева дописываем копии старшего бита (бит знака)
  • Пример (8 → 16 бит):

  • : 0000010100000000 00000101
  • : 1111101111111111 11111011
  • Почему для отрицательных дописываются единицы: в additional code все старшие биты отрицательного числа равны 1, и такое расширение сохраняет значение.

    Переполнение: что это и почему возникает

    Переполнение — ситуация, когда результат операции не помещается в диапазон выбранного формата и разрядности.

    Важно различать:

  • переполнение для беззнаковых (unsigned overflow)
  • переполнение для знаковых (signed overflow)
  • Потому что критерии “не помещается” разные.

    Переполнение в беззнаковой арифметике

    Если результат больше , то в аппаратной арифметике часто остаются только младшие бит (происходит “оборачивание” по модулю ).

    Пример для 8 бит:

  • , но в 8 битах остаётся 00000000 (то есть 0)
  • Это поведение соответствует вычислениям по модулю:

    Пояснение:

  • — “математический” результат без ограничения разрядности
  • — модуль, равный количеству возможных кодов
  • — операция взятия остатка
  • Во многих языках и системах такое поведение для unsigned определено как стандартное.

    Переполнение в знаковой арифметике (two’s complement)

    В дополнительном коде переполнение происходит, когда истинный результат выходит за диапазон .

    Практическое правило для сложения two’s complement:

  • если складываем два положительных, а получаем отрицательный — переполнение
  • если складываем два отрицательных, а получаем положительный — переполнение
  • если знаки разные — переполнения при сложении быть не должно
  • Пример (8 бит):

  • = 01111111
  • = 00000001
  • сумма (в 8 битах) = 10000000 (это )
  • Математически , но 128 не представимо в 8-битном signed, поэтому возникает переполнение.

    Переполнение и языки программирования: важная оговорка

    На уровне железа часто происходит “оборачивание” по фиксированной ширине регистра. Но на уровне языков программирования правила могут отличаться:

  • для беззнаковых типов часто определено модульное поведение
  • для знаковых типов некоторые языки трактуют переполнение как ошибку, некоторые — как неопределённое или зависимое от платформы поведение
  • Для курса важно понимать базовый факт: аппаратно вы всегда ограничены битами и диапазоном представления.

    Сводная таблица форматов (кратко)

    | Формат | Идея | Ноль | Удобство арифметики | Где встречается | |---|---|---|---|---| | Беззнаковый | все биты — величина | один | очень высокое | повсеместно | | Прямой код | старший бит — знак, остальные — модуль | два нуля | низкое | редко, исторически | | Обратный код | отрицательное = побитовая инверсия | два нуля | среднее/сложнее | редко, исторически | | Дополнительный код | отрицательное = инверсия + 1 | один | высокое | стандарт де-факто |

    Что важно запомнить

  • двоичный шаблон сам по себе не “знаковый” и не “беззнаковый” — это задаёт интерпретация
  • дополнительный код удобен тем, что даёт один ноль и простую аппаратную арифметику
  • при фиксированных битах всегда есть границы диапазона, а выход за них приводит к переполнению
  • для знакового two’s complement переполнение при сложении связано с “неожиданной сменой знака” результата при одинаковых знаках слагаемых
  • Источники для углубления

  • Wikipedia: Two's complement
  • Wikipedia: One's complement
  • Wikipedia: Signed number representations
  • Wikipedia: Integer overflow
  • 3. Фиксированная точка: масштабирование и точность

    Фиксированная точка: масштабирование и точность

    Как эта тема связана с предыдущими

    В прошлых статьях мы разобрали:

  • как числа переводятся между системами счисления
  • как целые числа кодируются в двоичном виде (включая дополнительный код) и почему возникает переполнение
  • Теперь добавим следующий важный слой: как хранить дробные значения, если хочется использовать целочисленную арифметику (быструю, дешёвую и предсказуемую), не переходя к плавающей точке.

    Фиксированная точка (fixed-point) — это способ кодирования дробных чисел, где двоичная точка считается стоящей в фиксированном месте, а все значения интерпретируются через заранее выбранный масштаб.

    Что такое фиксированная точка

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

    Чаще всего в вычислительных системах масштаб выбирают степенью двойки.

    Масштаб через степень двойки

    Если мы выделяем битов под дробную часть, то масштаб равен , и интерпретация такая:

    Пояснение элементов формулы:

  • — значение, которое мы хотим получить как вещественное число
  • — хранимое целое значение (в битах, обычно в two’s complement для знаковых)
  • — число дробных битов (сколько разрядов справа от двоичной точки)
  • — масштаб, который «делит» целое на
  • Эквивалентная запись (иногда понятнее):

  • знаменатель — это число уровней внутри единицы, которое задаёт дискретность
  • Как выглядит двоичная точка внутри битов

    Если у нас есть, например, 8-битное значение и мы решили, что , то двоичная точка мысленно стоит перед тремя младшими битами.

    !Иллюстрация фиксированного положения двоичной точки и весов разрядов

    Пример интерпретации

    Пусть 8 бит, , а в памяти лежит:

  • 00101010
  • Это как целое без знака равно . Тогда фиксированная точка даёт:

    -

    То есть 00101010 в таком формате означает 5.25.

    Формат Q и соглашения о записи

    Часто фиксированную точку описывают через Q-формат.

    Идея: указать, сколько битов относится к целой части и сколько к дробной.

    Варианты записи встречаются разные, но распространённое практическое соглашение:

  • Qm.f — где это число дробных битов, а примерно соответствует числу битов целой части (включая знак или без — зависит от контекста)
  • В рамках курса полезнее держать в голове не буквы, а главное правило:

  • вы выбираете ширину слова
  • вы выбираете дробные биты
  • значение всегда интерпретируется как
  • Диапазон значений и шаг (точность)

    Шаг квантования

    Если дробных битов , то минимальный «шаг» между соседними представимыми значениями:

    Пояснение:

  • — шаг квантования (разрешение)
  • — число дробных битов
  • — насколько меняется значение при увеличении на 1
  • Пример:

  • если , то
  • значит, число не может иметь точность лучше 0.125: любая величина будет округлена к ближайшему кратному 0.125
  • Диапазон для знакового two’s complement

    Если хранится в дополнительном коде и всего битов , то целочисленный диапазон :

  • от до
  • При фиксированной точке мы просто масштабируем этот диапазон делением на :

    Пояснение элементов:

  • — ширина слова в битах
  • — дробные биты
  • — модуль минимального целого в two’s complement
  • «» в максимуме появляется из-за асимметрии диапазона two’s complement: отрицательных значений на одно больше
  • умножение на переводит целый диапазон в дробный (деление на )
  • Пример: , .

  • ,
  • - -
  • шаг
  • Как кодировать число в фиксированной точке

    На практике обычно делают так:

  • Выбирают (сколько дробных битов нужно для нужной точности).
  • Число умножают на .
  • Полученный результат округляют до целого и кладут в тип ширины .
  • То есть код получают как:

    Пояснение:

  • — исходное число
  • — переносит «дробность» в целую область
  • — выбранное правило округления (важно зафиксировать его)
  • Округление и ошибка представления

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

    Пояснение:

  • — ошибка между истинным и представимым значением
  • — шаг квантования
  • деление на 2 — потому что округляем к ближайшему уровню
  • Важно: если вместо округления использовать усечение (truncation), ошибка смещается в одну сторону и может накапливаться в вычислениях.

    Арифметика фиксированной точки

    Ключевой принцип: вы выполняете операции над целыми кодами , но обязаны следить за масштабом.

    Сложение и вычитание

    Если два числа имеют одинаковый (одинаковый масштаб), то:

  • складывать и вычитать можно прямо как целые
  • масштаб результата остаётся тем же
  • Пример (оба с ):

  • ,
  • -

    Главный риск — переполнение: ровно то же, что в целых, потому что внутри вы складываете в ограниченной ширине .

    Умножение

    Если:

    - -

    то произведение:

    Пояснение:

  • числитель — обычное целочисленное произведение кодов
  • дробные биты складываются:
  • Практический вывод:

  • после умножения масштаб «становится мельче» (дробных битов больше)
  • чтобы вернуться к исходному формату (например, снова к ), обычно делают сдвиг вправо на нужное число битов
  • Если оба числа в одном формате с дробными битами, то типичный путь:

  • посчитать tmp = I1 * I2 в более широкой разрядности (например, 32-битное произведение из двух 16-битных)
  • сделать I = round(tmp / 2^f) то есть сдвиг вправо на (с округлением)
  • Главные риски:

  • промежуточное переполнение при умножении, если нет расширения разрядности
  • потеря точности при сдвиге вправо
  • Деление

    Деление требует аккуратного сохранения масштаба. Если вы хотите получить результат в формате с дробными битами, часто делают так:

  • перед делением «поднимают» числитель:
  • затем делят на
  • Идея: вы заранее добавляете место под дробную часть результата.

    Главные риски:

  • деление на ноль
  • переполнение при предварительном умножении числителя на
  • Переполнение и стратегии обработки

    Из статьи про целые числа важно помнить: арифметика в фиксированной точке внутри остаётся целочисленной, поэтому переполнение возникает так же неизбежно.

    Есть несколько распространённых стратегий:

  • оборачивание (wrap-around): остаются младшие бит (типично для unsigned и часто на уровне железа)
  • насыщение (saturation): при выходе за пределы результат «прилипает» к максимуму или минимуму
  • расширение разрядности: считать во временно более широком типе, а затем приводить
  • В цифровой обработке сигналов часто предпочитают насыщение, чтобы избежать резких «скачков» значения при переполнении.

    Выбор масштаба: компромисс между диапазоном и точностью

    Выбор — это всегда обмен:

  • больше
  • - лучше точность (меньше шаг ) - меньше диапазон по модулю, потому что значимые биты уходят в дробную часть
  • меньше
  • - больше диапазон - хуже точность

    Пример для (знаковый two’s complement):

    | Дробные биты | Шаг | Примерный диапазон | |---:|---:|---| | 0 | 1 | от до | | 8 | | от до | | 12 | | от до |

    Типичные ошибки при работе с фиксированной точкой

  • Сложение чисел с разными масштабами (разными ) без приведения к одному формату.
  • Умножение без расширения разрядности, из-за чего переполнение происходит ещё до корректировки масштаба.
  • Сдвиг вправо после умножения без округления (систематическая ошибка в сторону уменьшения по модулю).
  • Неправильное расширение разрядности для отрицательных значений: для two’s complement нужно расширение знака, как в целочисленном случае.
  • Где фиксированная точка используется на практике

  • встроенные системы и микроконтроллеры, где нет быстрого FPU или важна предсказуемость
  • цифровая обработка сигналов (аудио, датчики)
  • финансы в некоторых системах (часто используют десятичное масштабирование, но идея похожа: хранить целое и масштаб)
  • Что дальше

    Фиксированная точка показывает, как можно представлять дроби без плавающей запятой, но ценой ручного управления масштабом и переполнением.

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

    Источники для углубления

  • Wikipedia: Fixed-point arithmetic
  • Wikipedia: Q (number format))
  • Wikipedia: Two's complement
  • 4. Плавающая точка IEEE 754: структура, NaN, бесконечности

    Плавающая точка IEEE 754: структура, NaN, бесконечности

    Связь с предыдущими темами курса

    Ранее мы разобрали:

  • как числа переводятся между системами счисления;
  • как целые кодируются в двоичном виде (включая дополнительный код) и что такое переполнение;
  • как представлять дроби через фиксированную точку и почему там приходится вручную управлять масштабом .
  • Фиксированная точка хороша предсказуемостью, но неудобна тем, что диапазон и точность жёстко зависят от выбранного масштаба. Плавающая точка решает это иначе: масштаб хранится вместе с числом, а не задаётся внешним соглашением.

    Стандартом де-факто для представления чисел с плавающей точкой в современных вычислительных системах является IEEE 754.

    Интуиция: число как мантисса и степень

    Идея плавающей точки похожа на научную запись:

  • в десятичной форме;
  • в двоичной арифметике удобно хранить как .
  • Здесь:

  • — значащая часть (часто говорят мантисса или significand);
  • — показатель степени (экспонента).
  • В отличие от фиксированной точки, где масштаб один на все значения, в плавающей точке у каждого числа свой , поэтому можно покрыть широкий диапазон величин.

    Форматы IEEE 754: float32 и float64

    На практике чаще всего встречаются два бинарных формата:

    | Формат | Бит всего | Бит знака | Бит экспоненты | Бит дроби | В языках | |---|---:|---:|---:|---:|---| | binary32 | 32 | 1 | 8 | 23 | float (часто) | | binary64 | 64 | 1 | 11 | 52 | double (часто) |

    Биты в IEEE 754 интерпретируются не как «целое со знаком», а как структура полей.

    !Структура полей числа float32: знак, экспонента и дробь

    Что именно хранится в полях

    Бит знака

    Поле знака :

  • означает «плюс»;
  • означает «минус».
  • Экспонента и смещение (bias)

    Экспонента хранится не как знаковое целое, а как смещённая величина.

    Определим:

  • — значение поля экспоненты, прочитанное как беззнаковое число;
  • — смещение (константа, зависящая от формата);
  • — «настоящая» экспонента, которая участвует в .
  • Связь задаётся формулой:

    Пояснение элементов формулы:

  • — экспонента, с которой число реально масштабируется;
  • — то, что физически записано в битах экспоненты;
  • — смещение, чтобы в поле можно было хранить и отрицательные, и положительные без отдельного знака.
  • Для популярных форматов:

  • для binary32: ;
  • для binary64: .
  • Дробь (fraction) и «скрытая единица»

    Поле дроби содержит биты после двоичной точки. Для большинства обычных чисел (так называемых нормализованных) IEEE 754 использует приём скрытой ведущей единицы:

  • значащая часть начинается с , но эту ведущую 1 не хранят, потому что в нормализованной форме она всегда одинакова.
  • Это экономит 1 бит точности.

    Нормализованные числа: основной случай

    Если поле экспоненты не все нули и не все единицы, число считается нормализованным.

    Значение вычисляется так:

    Пояснение элементов:

  • — итоговое значение;
  • — даёт знак (при это , при это );
  • — значащая часть: ведущая единица подразумевается, а — дробная добавка из поля ;
  • — масштабирование степенью двойки, где .
  • Что такое :

  • если дробь состоит из битов (где — число битов дроби: 23 для float32, 52 для float64), то
  • Пояснение:

  • — i-й бит дроби;
  • — вес этого бита (первый бит после точки весит , следующий и т.д.).
  • Пример: почему 1.0 кодируется «красиво»

    Для binary32 число имеет:

  • ;
  • ;
  • .
  • Тогда .

    Чтобы получить при bias 127, нужно , то есть двоично 01111111.

    Нули и денормализованные (subnormal) числа

    Если поле экспоненты все нули (), то стандарт разделяет два случая:

  • если дробь , то это ноль (причём бывает и );
  • если дробь , то это денормализованное (subnormal) число.
  • Зачем нужны subnormal

    Без subnormal был бы «разрыв» между:

  • самым маленьким положительным нормализованным числом;
  • нулём.
  • Subnormal заполняют этот промежуток и позволяют плавно “подходить к нулю”, ценой снижения точности.

    Значение subnormal

    Для subnormal ведущей единицы нет. Формула становится:

    Пояснение элементов:

  • — знак;
  • — дробь, собранная из поля (как сумма );
  • — фиксированная степень для всех subnormal данного формата; единица в показателе появляется из правил IEEE 754, чтобы обеспечить непрерывность с нормализованными значениями.
  • Особые значения: бесконечности и NaN

    IEEE 754 резервирует комбинации, где поле экспоненты все единицы.

    Для float32 это (в двоичном виде 11111111), для float64 это .

    Бесконечности

    Если экспонента вся из единиц, а дробь нулевая (), то это бесконечность:

  • даёт ;
  • даёт .
  • Откуда они берутся в вычислениях:

  • деление ненулевого числа на 0 (в IEEE 754 это не обязательно “крах”, а определённый результат);
  • переполнение по диапазону (слишком большое по модулю число при округлении может стать бесконечностью).
  • Примеры типичных правил:

  • ;
  • ;
  • .
  • NaN (Not a Number)

    Если экспонента вся из единиц, а дробь не нулевая (), то это NaN.

    NaN означает, что результат операции не является числом в привычном смысле.

    Типичные источники NaN:

  • ;
  • ;
  • в вещественных числах.
  • #### Тихие и сигнальные NaN (важно концептуально)

    В IEEE 754 различают:

  • quiet NaN (qNaN) — обычно «тихо распространяется» дальше по вычислениям;
  • signaling NaN (sNaN) — предназначен для сигнализации об ошибке (реакция зависит от системы и настроек).
  • В учебной практике главное запомнить:

  • NaN заражает вычисления: многие операции с NaN дают NaN;
  • сравнения с NaN ведут себя необычно.
  • Необычные (но стандартные) случаи: +0 и -0

    IEEE 754 имеет два нуля:

  • (бит знака 0, экспонента 0, дробь 0);
  • (бит знака 1, экспонента 0, дробь 0).
  • Зачем это нужно:

  • для корректной обработки предельных переходов и знака результата в некоторых операциях;
  • чтобы, например, различать и .
  • При этом в большинстве “обычных” сравнений:

  • считается истинным.
  • Как распознать класс числа по полям (быстрая памятка)

    Пусть — поле экспоненты, — поле дроби.

    | Условие по полям | Класс значения | |---|---| | , | или (по ) | | , | subnormal | | | normal | | , | или | | , | NaN |

    Почему NaN «ломает» сравнения

    NaN используют, чтобы не подменять ошибку каким-то “обычным числом” вроде 0.

    Поэтому IEEE 754 задаёт правило:

  • любое сравнение вида ложно;
  • любое сравнение вида и тоже ложно;
  • при этом истинно для любого , включая сам NaN.
  • Практический вывод: для проверки на NaN нельзя полагаться на обычные равенства, обычно используют специальные функции языка или библиотеки.

    Сопоставление с фиксированной точкой: что стало проще и что сложнее

    Что плавающая точка делает проще:

  • огромный динамический диапазон (очень большие и очень маленькие числа);
  • не нужно вручную выбирать единый масштаб для всех значений.
  • Что становится сложнее:

  • точность неравномерна по диапазону (шаг между соседними числами зависит от величины);
  • появляются особые значения (NaN, , );
  • округление и режимы округления становятся центральной темой.
  • В следующей теме обычно разбирают точность, округление, ULP, ошибки вычислений и практические ловушки.

    Источники для углубления

  • Wikipedia: IEEE 754
  • Wikipedia: Single-precision floating-point format
  • Wikipedia: Double-precision floating-point format
  • What Every Computer Scientist Should Know About Floating-Point Arithmetic (David Goldberg, ACM)
  • 5. Арифметика и округления: ошибки, устойчивость, сравнение чисел

    Арифметика и округления: ошибки, устойчивость, сравнение чисел

    Зачем нужна эта тема

    В предыдущих статьях курса мы разобрали, как числа кодируются:

  • целые в двоичном виде, включая дополнительный код и переполнение;
  • дробные в фиксированной точке, где масштаб задаётся вручную;
  • числа с плавающей точкой IEEE 754, где масштаб хранится в экспоненте и появляются особые значения вроде NaN и .
  • Следующий шаг — понять, как именно происходят вычисления над этими кодами и почему результаты часто «чуть-чуть не те», даже если переполнения нет.

    В этой статье разберём:

  • откуда берутся ошибки округления и какие они бывают;
  • что такое абсолютная и относительная ошибка;
  • почему одни формулы вычисляются надёжно, а другие нет (устойчивость);
  • как правильно сравнивать числа с плавающей точкой;
  • что делать с NaN, бесконечностями и нулями IEEE 754 в логике программ.
  • !Иллюстрация неравномерного шага между соседними числами float по диапазону

    Округление как неизбежность конечного формата

    Что происходит при записи результата

    Любой формат с фиксированным числом битов имеет конечное множество представимых значений. Если математический результат операции не попадает точно в это множество, он заменяется ближайшим представимым значением по правилу округления.

    Для IEEE 754 наиболее распространённый режим по умолчанию — округление к ближайшему, при равенстве к чётному (round to nearest, ties to even). Идея «к чётному» нужна, чтобы избежать систематического смещения при большом числе округлений.

    Модель ошибки округления

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

    Пояснение элементов формулы:

  • точный (математический) результат;
  • — результат после округления в формат (от слова floating);
  • — относительная ошибка округления; обычно она мала и ограничена некоторой константой, зависящей от формата и режима округления.
  • Важный смысл: ошибка часто относительная, то есть масштабируется вместе с числом. Поэтому абсолютная ошибка у больших чисел обычно больше.

    Абсолютная и относительная ошибка

    Пусть есть истинное значение и приближённое .

    Абсолютная ошибка

    Пояснение:

  • — истинное значение;
  • — вычисленное или представимое значение;
  • — модуль;
  • показывает «на сколько в абсолютных единицах» мы ошиблись.
  • Абсолютная ошибка полезна, когда известна физическая шкала: например, ошибка в метрах.

    Относительная ошибка

    Пояснение:

  • числитель — абсолютная ошибка;
  • знаменатель — масштаб истинного значения;
  • показывает, насколько ошибка велика по сравнению с самим числом.
  • Относительная ошибка удобна, когда важен процент или число значащих цифр. Но она плохо определена, если (деление на очень маленькое число раздувает показатель).

    ULP и машинный эпсилон

    ULP

    ULP (unit in the last place) — шаг между двумя соседними представимыми числами в конкретной области шкалы. В IEEE 754 этот шаг растёт с увеличением экспоненты: чем больше по модулю число, тем «реже сетка».

    Практический смысл: ошибка «в 1 ULP» часто означает «округлено максимально аккуратно для данного формата», но абсолютный размер ULP зависит от величины числа.

    Машинный эпсилон

    Машинный эпсилон обычно понимают как расстояние между 1 и ближайшим числом больше 1 в данном формате.

  • для binary64 (double) это примерно ;
  • для binary32 (float) это примерно .
  • Здесь показатель связан с тем, сколько бит точности хранится в значащей части (с учётом скрытой единицы у нормализованных чисел).

    Важно: машинный эпсилон — это не «максимальная ошибка любых вычислений», а характеристика плотности представимых чисел около 1.

    Типичные источники численных проблем

    Катастрофическая потеря значимости

    Самый известный случай — вычитание близких чисел:

  • если , то разность мала;
  • но в вычислении участвуют числа, у которых уже есть округления;
  • значащие биты результата могут «вымыться», и относительная ошибка результата резко возрастает.
  • Пример идеи без привязки к конкретным значениям:

  • было две величины с точностью до, скажем, 15 значащих цифр;
  • после вычитания почти равных чисел может остаться 2–3 значащих цифры.
  • Это не «баг IEEE 754», а математическое следствие: операция вычитания превращает относительную ошибку входов в большую относительную ошибку выхода.

    Поглощение (absorption)

    Если складывать очень большое и очень маленькое число, маленькое может не изменить сумму:

  • , если меньше половины ULP около .
  • Это нормальное поведение округления. Особенно заметно при накоплении суммы, когда частичные суммы растут, а добавки остаются маленькими.

    Накопление ошибки

    Округление происходит почти после каждой операции. Ошибки могут:

  • частично компенсироваться;
  • или накапливаться, если порядок действий неудачен.
  • Поэтому два математически эквивалентных выражения могут давать различный результат на компьютере.

    Устойчивость алгоритмов

    Важно различать:

  • чувствительность задачи: насколько сильно истинный результат меняется при малом изменении входа;
  • устойчивость алгоритма: насколько алгоритм добавляет ошибок сверх неизбежных.
  • Идея устойчивости в практических терминах

    Алгоритм считается численно устойчивым, если итог можно интерпретировать как точный ответ для немного изменённых входных данных, где «немного» сопоставимо с машинными округлениями.

    Эта формулировка полезна, потому что:

  • любое представление входа уже содержит округление;
  • «идеальной точности» всё равно нет.
  • Пример: суммирование многих чисел

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

    Практические правила:

  • складывать числа примерно одного масштаба обычно лучше;
  • суммировать от меньших по модулю к большим часто точнее, чем наоборот;
  • для высокой точности используют компенсированное суммирование.
  • Один из известных приёмов — суммирование Кэхэна (Kahan summation): оно хранит компенсацию потерянной «мелочи» и уменьшает ошибку накопления.

    В этом курсе важно понимать когда нужно думать о таких приёмах: когда вы складываете много чисел с разными масштабами (статистика, интегрирование, обработка сигналов).

    Сравнение чисел с плавающей точкой

    Почему a == b часто плохая идея

    Из-за округлений выражения, которые математически равны, могут давать результаты, отличающиеся на малую величину.

    Пример типовой ситуации:

  • вы вычисляете значение двумя способами (через разные формулы);
  • оба способа «правильные», но округления разные;
  • прямое сравнение на равенство возвращает ложь.
  • Сравнение с допуском

    Обычно используют допуск.

    Абсолютный допуск:

    Пояснение:

  • , — сравниваемые значения;
  • — их абсолютная разница;
  • — выбранный порог (например, допустимая физическая погрешность измерения).
  • Относительный допуск:

    Пояснение:

  • числитель — абсолютная разница;
  • знаменатель — масштаб сравниваемых значений (берём максимум, чтобы не делить на слишком маленькое);
  • — допустимая относительная ошибка.
  • На практике часто комбинируют оба:

  • абсолютный допуск важен около нуля;
  • относительный — на больших масштабах.
  • Важная оговорка про выбор допуска

    Допуск нельзя выбирать «магически» одинаковым для всех задач. Его определяют:

  • требования предметной области (например, точность датчика);
  • масштаб данных;
  • оценка накопленной ошибки алгоритма.
  • Особые значения IEEE 754 в вычислениях и сравнениях

    NaN

    NaN распространяется через многие операции и «ломает» обычные сравнения:

  • — ложь для любого ;
  • и — тоже ложь;
  • — истина для любого , включая NaN.
  • Вывод: проверять NaN нужно специальными средствами языка или библиотеки.

    Бесконечности

    и ведут себя предсказуемо во многих операциях (например, сравнения с конечными числами), но требуют аккуратности в выражениях вида , которые дают NaN.

    Два нуля

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

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

  • Избегайте вычитания близких чисел, если можно преобразовать формулу.
  • При суммировании большого количества чисел учитывайте порядок сложения; при необходимости используйте компенсированное суммирование.
  • Не сравнивайте числа с плавающей точкой через прямое равенство, кроме случаев, когда вы точно контролируете вычисления (например, сравнение с заранее записанной константой, которая получена тем же способом).
  • Для сравнения используйте абсолютный и относительный допуски.
  • Всегда продумывайте, что должно происходить при NaN и в вашей логике.
  • Что дальше

    Эта статья завершает базовую картину курса: мы не только знаем, как числа кодируются, но и понимаем, как вычисления над кодами порождают округления и ошибки, и как с этим жить в инженерной практике.

    Если углубляться далее, обычно рассматривают:

  • оценку ошибки конкретных численных методов;
  • ULP-метрики и тестирование корректности библиотек;
  • интервальные вычисления и рациональную арифметику в задачах, где ошибки недопустимы.
  • Источники для углубления

  • What Every Computer Scientist Should Know About Floating-Point Arithmetic (David Goldberg)
  • Wikipedia: Floating-point arithmetic
  • Wikipedia: Machine epsilon
  • Wikipedia: Kahan summation algorithm
  • Wikipedia: Numerical stability
  • 6. Алгоритмы численной обработки: суммы, фильтрация, нормализация

    Алгоритмы численной обработки: суммы, фильтрация, нормализация

    Связь с предыдущими темами курса

    В предыдущих статьях мы разобрали, как числа представлены в компьютере:

  • целые числа и переполнение (в том числе дополнительный код);
  • фиксированную точку и то, что масштаб приходится контролировать вручную;
  • IEEE 754 и особые значения вроде NaN и ;
  • округления, ULP, поглощение, потерю значимости и то, почему сравнение a == b опасно.
  • Теперь соберём эти знания в практические алгоритмы численной обработки — такие, которые постоянно встречаются в реальных системах: суммирование массивов, фильтрация сигналов и нормализация данных. Главная цель статьи — научиться делать эти операции так, чтобы ошибки округления и переполнение не разрушали результат.

    !Сравнение линейного и попарного суммирования

    Суммы: как получить точнее и надёжнее

    Почему сумма — это не тривиально

    Математически сложение ассоциативно: . На компьютере для чисел с плавающей точкой это часто не так, потому что после каждого сложения результат округляется в конечный формат.

    На точность суммы влияют:

  • порядок сложения;
  • разный масштаб слагаемых;
  • наличие очень маленьких добавок к большой частичной сумме (поглощение);
  • переполнение (для целых и фиксированной точки) и выход за диапазон (для IEEE 754 с переходом в ).
  • Базовая модель: сумма как алгоритм

    Пусть есть числа . Сумма в математике:

    Пояснение элементов формулы:

  • — итоговая сумма;
  • — операция суммирования;
  • — индекс элемента;
  • — количество элементов;
  • — i-е слагаемое.
  • Компьютер же обычно делает так:

  • заводит переменную s = 0;
  • по очереди добавляет s = s + x[i].
  • И именно порядок этих операций определяет, сколько значащих битов вы потеряете.

    Приём: суммирование от меньших по модулю к большим

    Если вы можете отсортировать значения по возрастанию модуля, то часто становится лучше, потому что маленькие числа суммируются, пока частичная сумма ещё не слишком велика.

    Ограничения:

  • сортировка стоит времени и памяти;
  • если данные потоковые (стрим), сортировать невозможно.
  • Приём: попарное (tree) суммирование

    Идея: складывать числа попарно, затем суммы снова попарно, как в дереве. Это уменьшает типичную глубину «цепочки округлений».

    Практический эффект:

  • меньше накопление ошибки, чем при линейной схеме слева направо;
  • особенно полезно в параллельных вычислениях.
  • См. описание подхода: Pairwise summation.

    Приём: компенсированное суммирование Кэхэна

    Компенсированное суммирование хранит отдельную переменную-компенсацию потерянной «мелочи».

    Интуиция:

  • при s + x часть значащих битов x может не попасть в результат из-за округления;
  • алгоритм пытается оценить эту потерю и добавить её позже.
  • Классический вариант (в псевдокоде):

    Смысл переменных:

  • s — текущая сумма;
  • c — компенсация (то, что потерялось из-за округления);
  • y — скорректированная добавка;
  • t — промежуточная сумма.
  • Ссылка: Kahan summation algorithm.

    Если данные целочисленные или фиксированная точка

    Для целых и fixed-point главная опасность часто не округление, а переполнение.

    Практические правила:

  • суммируйте в более широком типе (например, 16-битные значения складывать в 32-битный аккумулятор);
  • при fixed-point учитывайте масштаб: если , то сумма кодов переполняется так же, как обычные целые;
  • если нужна предсказуемость, используйте насыщение (saturation), а не оборачивание.
  • Фильтрация: сглаживание и выделение полезного

    Фильтрация — это преобразование последовательности чисел (сигнала) в другую последовательность, часто чтобы:

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

    FIR-фильтры: конечная импульсная характеристика

    Один из самых базовых вариантов — FIR (finite impulse response). Он вычисляет выход как взвешенную сумму нескольких последних входов.

    Общее уравнение:

    Пояснение элементов формулы:

  • — входной сигнал;
  • — выходной сигнал;
  • — номер текущего отсчёта (текущий момент времени в дискретном сигнале);
  • — коэффициенты фильтра (веса);
  • — число коэффициентов (длина фильтра);
  • — i-й прошлый отсчёт.
  • Пример FIR, который знают почти все: скользящее среднее.

    Скользящее среднее как фильтр

    Если взять последних значений и усреднить, получится сглаживание.

    Пояснение:

  • — размер окна усреднения;
  • — одинаковый вес каждого элемента;
  • остальное как в FIR.
  • Численные риски:

  • для целых и fixed-point сумма может переполниться, даже если результат после деления на помещается;
  • в float сумма больших и малых значений в окне может терять точность.
  • Ускорение (и иногда улучшение точности) для скользящего среднего:

  • хранить сумму окна sum;
  • на каждом шаге делать sum = sum + x_new - x_old.
  • Но здесь появляется другой риск: sum + x_new - x_old — это вычитание близких величин (особенно если сигнал стабильный), что может усиливать относительную ошибку. В float это обычно приемлемо при разумных масштабах, но в задачах высокой точности иногда предпочитают периодический пересчёт суммы окна заново.

    !Принцип работы скользящего среднего и обновления суммы окна

    Ссылки для ориентировки:

  • Finite impulse response
  • Moving average
  • Convolution как универсальная форма FIR

    FIR-фильтрация по сути является частным случаем свёртки (convolution) последовательностей.

    Свёртка важна как концепция, потому что:

  • объясняет, почему FIR — это «взвешенная сумма соседей»;
  • помогает понимать, как меняется сигнал при разных .
  • Ссылка: Convolution.

    IIR-фильтры: бесконечная импульсная характеристика

    IIR (infinite impulse response) добавляет обратную связь: выход зависит не только от входов, но и от прошлых выходов.

    Общий вид разностного уравнения:

    Пояснение элементов:

  • — коэффициенты прямой части (вклад входов);
  • — коэффициенты обратной связи (вклад прошлых выходов);
  • — глубина по входу;
  • — глубина по выходу;
  • минус перед второй суммой — распространённая форма записи (зависит от соглашения).
  • Численные особенности IIR:

  • ошибки округления могут накапливаться из-за обратной связи;
  • в fixed-point очень важно иметь запас по диапазону (headroom), иначе насыщение или переполнение ломает динамику фильтра;
  • устойчивость фильтра зависит не только от математики коэффициентов, но и от реализации (структуры прямой формы, каскады бикуадов и т.д.).
  • Ссылка: Infinite impulse response.

    Практика реализации фильтра: где прячутся ошибки

    Типовые источники проблем:

  • коэффициенты фильтра записаны в float32, а не float64, и точности недостаточно;
  • при fixed-point не учтён масштаб коэффициентов и промежуточных произведений;
  • умножения делаются без расширения разрядности, и переполнение происходит до того, как вы «вернули» масштаб сдвигом;
  • NaN или попали на вход (например, из деления на 0), и дальше «заражают» весь поток.
  • Минимальные меры предосторожности:

  • считать промежуточные суммы и произведения в более широком типе;
  • выбирать порядок суммирования коэффициентов (например, попарно);
  • явно определить политику обработки NaN/ (пропуск, замена, останов).
  • Нормализация: приводим данные к удобному масштабу

    Нормализация нужна, чтобы:

  • сделать числа сопоставимыми по масштабу;
  • улучшить обусловленность вычислений;
  • предотвратить переполнение в фиксированной точке;
  • стабилизировать обучение моделей и статистические методы;
  • сделать пороги и допуски осмысленными.
  • Важно: нормализация — это тоже арифметика (вычитания, деления, корни), значит она может порождать NaN, и ошибки округления.

    Min-max нормализация

    Приводит данные к диапазону, например .

    Пояснение элементов:

  • — исходное значение;
  • — минимум по выборке;
  • — максимум по выборке;
  • — нормализованное значение.
  • Численные риски и проверки:

  • если , деление на 0 даёт NaN или (в IEEE 754);
  • вычитание может терять значимость, если значения очень близки, но обычно это терпимо, если затем делить на такой же небольшой диапазон.
  • Стандартизация (z-score)

    Приводит данные к среднему 0 и стандартному отклонению 1.

    Пояснение:

  • — среднее значение по выборке;
  • — стандартное отклонение;
  • — стандартизованное значение.
  • Численные акценты:

  • расчёт — это суммирование (применимы приёмы из начала статьи);
  • дисперсия и требуют аккуратности из-за вычитания близких величин, поэтому на практике используют устойчивые алгоритмы (например, однопроходные варианты Уэлфорда).
  • Ссылки:

  • Standard score
  • Algorithms for calculating variance
  • Нормализация по длине (L2-норма)

    Часто встречается в геометрии, обработке признаков и сигналов: вектор приводится к длине 1.

    Пусть . Тогда:

    Пояснение:

  • — длина (евклидова норма) вектора;
  • — квадраты компонент;
  • корень делает итоговую величину сопоставимой с масштабом исходных компонент;
  • деление всех компонент на норму даёт вектор единичной длины.
  • Численные риски и типичный приём:

  • сумма квадратов может переполниться (в fixed-point и даже в float при очень больших значениях);
  • чтобы снизить риск, применяют масштабирование: выносят общий множитель, либо используют устойчивые реализации нормы.
  • Ссылка: Euclidean norm.

    Как выбрать представление и масштаб в реальной системе

    Если вы используете IEEE 754

    Практические ориентиры:

  • чаще всего float64 даёт ощутимо более стабильные суммы и статистики, чем float32;
  • проверяйте и документируйте обработку NaN/ на границах алгоритма;
  • сравнение результатов делайте с допуском (абсолютным и относительным), а не через ==.
  • Полезный обзор, почему это важно: What Every Computer Scientist Should Know About Floating-Point Arithmetic.

    Если вы используете fixed-point

    Нужно одновременно контролировать:

  • масштаб (сколько дробных битов в формате );
  • запас по диапазону (headroom) для промежуточных сумм;
  • стратегию переполнения: оборачивание или насыщение;
  • округление при сдвигах (усечение даёт систематическое смещение).
  • Практическая рекомендация уровня «чтобы работало»:

  • умножения и суммирование делайте в расширенной разрядности;
  • после этого приводите к целевому формату с округлением и, при необходимости, насыщением.
  • Итог: что вы должны уметь после этой темы

  • Объяснить, почему порядок суммирования влияет на результат в float.
  • Выбрать подход к суммированию: сортировка по модулю, попарное дерево, Кэхэн.
  • Понимать FIR как взвешенную сумму и уметь распознавать численные риски (переполнение суммы, накопление ошибки).
  • Понимать IIR как рекуррентную схему и почему обратная связь усиливает требования к точности.
  • Нормализовать данные и заранее предусмотреть крайние случаи: нулевой диапазон, нулевая дисперсия, нулевая норма.
  • Источники для углубления

  • What Every Computer Scientist Should Know About Floating-Point Arithmetic
  • Pairwise summation
  • Kahan summation algorithm
  • Finite impulse response
  • Infinite impulse response
  • Moving average
  • Algorithms for calculating variance
  • 7. Практика: выбор формата, тестирование и оптимизация вычислений

    Практика: выбор формата, тестирование и оптимизация вычислений

    Как эта тема связывает весь курс

    Ранее мы разобрали:

  • системы счисления и переводы между ними;
  • целые числа в двоичном виде, знаковые форматы и переполнение;
  • фиксированную точку как целое плюс масштаб;
  • IEEE 754: нормальные числа, subnormal, NaN, бесконечности, ;
  • округления, ULP, поглощение, потерю значимости и устойчивые приёмы суммирования;
  • базовые алгоритмы обработки: суммы, фильтрация, нормализация.
  • Теперь соберём всё в инженерную практику: как выбрать формат представления, как тестировать численные вычисления и как оптимизировать их, не сломав корректность.

    !Схема рабочего процесса: от требований к формату, тестам и оптимизации

    Выбор формата: как не ошибиться в самом начале

    Выбор формата почти всегда определяет:

  • максимальную ошибку и характер ошибок (округление, квантование, переполнение);
  • поведение на краях (что будет при выходе за диапазон);
  • скорость и энергопотребление;
  • воспроизводимость результатов на разных платформах.
  • Что нужно собрать как требования

    Полезно зафиксировать требования до выбора формата:

  • диапазон входов и выходов (минимум, максимум, типичные значения);
  • требуемая точность (в абсолютных единицах и относительно масштаба);
  • допустимы ли особые значения (NaN, , ) или их нужно запрещать;
  • режим отказа при ошибке: оборачивание, насыщение, сигнализация, пропуск значения;
  • ограничения платформы (есть ли FPU, нужна ли строгая детерминированность, важна ли скорость).
  • Быстрая карта выбора

    | Вариант представления | Когда подходит | Главные риски | Типичные меры защиты | |---|---|---|---| | Целые (signed/unsigned) | счётчики, индексы, точные дискретные величины | переполнение, неверная интерпретация знака | более широкий аккумулятор, проверки границ, saturating-arithmetic | | Фиксированная точка (fixed-point, ) | сигналка, датчики, финальные форматы хранения, предсказуемость | переполнение промежуточных произведений, неверный масштаб, систематическое усечение | расширение разрядности, округление при сдвиге, насыщение, запас по диапазону | | Плавающая точка (IEEE 754 float32) | много вычислений, важна скорость и память, точности достаточно | поглощение, потеря значимости, NaN в потоке | правильный порядок операций, допуски при сравнении, фильтрация NaN | | Плавающая точка (IEEE 754 float64) | статистика, суммы, нормы, устойчивость важнее скорости | всё ещё есть округления, но меньше | те же приёмы, плюс контроль крайних случаев |

    Оценка диапазона и точности: практическая логика

    Для целых чисел:

  • диапазон в битах ограничен (для знаковых two’s complement: от до );
  • если сумма или произведение может выйти за диапазон, нужно либо расширять тип, либо менять алгоритм.
  • Для фиксированной точки (fixed-point):

    Вы выбираете число дробных битов и храните целое , а реальное значение вычисляется по формуле:

    Где:

  • — значение в привычном смысле;
  • — то, что реально лежит в памяти как целое;
  • — задаёт масштаб: чем больше , тем меньше шаг и тем меньше диапазон;
  • — делитель, который переводит целое обратно в дробное.
  • Практический вывод: точность задаётся шагом . Переполнение происходит на уровне операций над , поэтому промежуточные вычисления почти всегда делают в более широком типе.

    Для плавающей точки (IEEE 754):

  • точность неравномерна: шаг между числами растёт с масштабом;
  • важны особые значения (NaN, , ) и правила сравнения.
  • Практический вывод: если вы ожидаете широкий диапазон и не хотите вручную вести масштаб, выбирают float. Если важны строгая предсказуемость и контроль переполнения, часто выбирают fixed-point.

    Мини-кейс: скользящее среднее

    Если нужно скользящее среднее по окну размера :

  • в float32 это часто нормально, но порядок операций и поглощение могут влиять;
  • в fixed-point опасно, что сумма окна переполнится до деления.
  • Практика для fixed-point:

  • Копить сумму окна в расширенном аккумуляторе (например, 16-битные отсчёты суммировать в 32 бита).
  • Делить с округлением.
  • Если система требует предсказуемых границ, применять насыщение.
  • Тестирование численных вычислений: что именно проверять

    Тестирование численных вычислений отличается от обычного тем, что:

  • равенство a == b часто не является корректной проверкой;
  • множество ошибок проявляется только на краях диапазона или на специальных значениях;
  • разные платформы и компиляторы могут менять порядок вычислений и получать чуть разные результаты.
  • Что считать эталоном

    Варианты эталона (reference) зависят от задачи:

  • вычисление в более точном типе (например, тестируем float32 через float64);
  • вычисление в целых с точным масштабом (когда это возможно);
  • независимая реализация другим способом (например, другая формула, другой порядок суммирования);
  • высокоточная библиотека (когда требуется).
  • Сравнение результатов: допуски вместо равенства

    Часто используют комбинированный критерий с допусками:

    Где:

  • и — сравниваемые значения;
  • — абсолютная разница;
  • — абсолютный допуск (важен около нуля);
  • — относительный допуск (важен на больших масштабах);
  • — задаёт характерный масштаб, чтобы не делить на очень маленькое число.
  • Практический смысл: если числа около нуля, главным становится . Если числа большие, главным становится относительный член с .

    > Если вы тестируете алгоритмы, чувствительные к округлению, полезно также измерять ошибку в ULP (Unit in the Last Place), но это уже более низкоуровневая техника.

    Крайние случаи: обязательный чек-лист

    Для целых и fixed-point:

  • максимум и минимум представимого значения;
  • значения около переполнения при сложении и умножении;
  • политика переполнения: оборачивание или насыщение;
  • расширение знака при приведении типов.
  • Для IEEE 754:

  • и ;
  • денормалы (subnormal — очень маленькие по модулю значения);
  • и ;
  • NaN (и проверка, что он не ломает ветвления и сравнения).
  • Свойства вместо конкретных чисел

    Часто полезнее проверять свойства (property-based тестирование), чем фиксированные примеры:

  • монотонность: если вход растёт, выход не должен уменьшаться;
  • ограниченность: выход всегда в диапазоне;
  • симметрия: связано с ожидаемым образом;
  • инварианты: например, после нормализации .
  • Свойства помогают ловить ошибки масштабирования в fixed-point и ошибки обработки NaN или бесконечностей в float.

    Оптимизация вычислений: как ускорять и не испортить результат

    Оптимизация вычислений почти всегда меняет хотя бы один из пунктов:

  • порядок операций;
  • точность промежуточных значений;
  • поведение на краях (переполнение, NaN, денормалы).
  • Поэтому правило практики:

  • Сначала делаем корректно и тестируем.
  • Затем профилируем.
  • Затем оптимизируем и снова прогоняем тесты, включая крайние случаи.
  • Оптимизация порядка операций

    То, что математически эквивалентно, вычислительно может быть разным. Примеры практичных замен:

  • суммирование большого массива: использовать попарное суммирование или алгоритм Кэхэна, если точность критична;
  • избегать вычитания близких чисел, если есть альтернативная формула;
  • при вычислении нормы или дисперсии использовать устойчивые алгоритмы, а не прямые формулы с вычитанием близких величин.
  • Ускорение фильтров и свёрток

    Типовые ускорения:

  • переиспользовать частичные суммы (для скользящего среднего);
  • применять SIMD-векторизацию, если данные упакованы;
  • для fixed-point хранить коэффициенты в удобном формате Q и умножать с расширением разрядности.
  • Но есть численные последствия. Переиспользование суммы окна делает шаг sum = sum + x_new - x_old, а это может усиливать чувствительность к округлению в float. SIMD и параллельное суммирование часто меняют порядок сложения, значит результат может отличаться в последних битах.

    Использование FMA: быстрее и иногда точнее

    На многих платформах есть операция умножить и прибавить с одним округлением — FMA (Fused Multiply-Add).

    Практический эффект:

  • часто быстрее, чем отдельные умножение и сложение;
  • иногда точнее, потому что промежуточное произведение не округляется отдельно.
  • Риск практики: результаты могут отличаться от платформ, где FMA нет, что важно для строгой воспроизводимости.

    Управление денормалами и производительностью

    Денормалы (subnormal) полезны математически, но на некоторых системах могут обрабатываться сильно медленнее.

    Практические подходы зависят от области:

  • в научных расчётах обычно сохраняют корректное IEEE 754 поведение;
  • в обработке сигналов иногда применяют режимы flush-to-zero (сброс в ноль), чтобы ускорить вычисления, но это меняет поведение около нуля.
  • Если вы выбираете ускоряющий режим, это должно быть зафиксировано как требование и покрыто тестами именно для области малых чисел.

    Fixed-point: оптимизация через сдвиги и насыщение

    Fixed-point часто выбирают именно ради скорости и детерминизма. Три практических правила, которые одновременно про корректность и скорость:

  • умножение делать в расширенном типе, затем применять сдвиг вправо на ;
  • при сдвиге вправо по возможности делать округление, а не усечение;
  • переполнение обрабатывать насыщением, если алгоритм не должен "оборачиваться".
  • Итоговый рабочий процесс

    Надёжный процесс для численного фрагмента (функции, фильтра, нормализации) можно описать так:

  • Определить диапазоны входов и выходов.
  • Выбрать формат (int, fixed-point, float32, float64) по диапазону, точности и ограничениям платформы.
  • Определить политику краёв: переполнение, NaN, бесконечности, .
  • Написать эталон или метод проверки (более точный расчёт, независимая реализация).
  • Построить тесты с допусками и покрытием крайних случаев.
  • Профилировать.
  • Оптимизировать (порядок операций, SIMD, FMA, масштабирование).
  • Повторно прогнать тесты и зафиксировать ожидаемые допустимые расхождения.
  • Источники для углубления

  • IEEE Standard for Floating-Point Arithmetic (IEEE 754)
  • What Every Computer Scientist Should Know About Floating-Point Arithmetic (David Goldberg)
  • Kahan summation algorithm
  • Pairwise summation
  • Fixed-point arithmetic
  • Итоги

  • Выбор числового формата (int, fixed-point, float) определяет баланс между точностью, диапазоном и производительностью.
  • Тестирование вычислений требует использования допусков (абсолютных и относительных) вместо строгого равенства, а также обязательной проверки крайних случаев (NaN, , переполнения).
  • Оптимизация (изменение порядка операций, SIMD, FMA) может изменить итоговый результат, поэтому её следует проводить только после написания тестов и профилирования.