Перейти к содержимому
Astrolune
Гайды27 янв. 2026 г.6 мин чтения

Фиксированная точка без слёз: Q32.32

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

Astrolune Core

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

Q32.32 — одно 64-битное слово, разделённое на целую и дробную половины

Раскладка

al_q32 — обычный uint64_t. Старшие 32 бита — целая часть, младшие 32 — дробная, поэтому единица веса хранится как 1 << 32. Ни структур, ни тегов, ни упаковки — просто целое число и конвенция.

c
typedef uint64_t al_q32;

#define AL_Q32_ONE  ((al_q32)1ULL << 32)
#define AL_Q32_HALF (AL_Q32_ONE >> 1)

/* double -> q32 (только тестовые фикстуры; на горячем пути — никогда) */
al_q32 al_q32_from(double d);

Умножение — единственная хитрая операция

Произведение двух значений Q32.32 даёт 64.64 бита, и ответ нужно вернуть к 32.32 сдвигом вправо через промежуточную ширину в 96 бит. На практике ядро считает (a * b) >> 32 над unsigned __int128, который есть в каждой поддерживаемой цепочке инструментов, и усекает, а не округляет.

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

ОперацияРеализацияПогрешность
add / subнативный u64точно или с проверкой переполнения
mul(u128)a * b >> 32≤ 1 ulp, всегда вниз
min / capнативное сравнениеточно

Три способа ошибиться

  • Смешивать сырые целые с Q32.32. Обычный счётчик — не вес; оборачивайте явным сдвигом или не трогайте.
  • Менять порядок умножений. Произведение ассоциативно, но усечение — нет: mul(mul(a, b), c) может отличаться от mul(a, mul(b, c)) на ulp. Ядро фиксирует один порядок для каждой формулы и закрепляет его тестами.
  • Делить вообще. Деление существует ровно в двух местах, оба под защитой. Если вы соберлись делить — скорее всего, вам нужно умножение на обратное.

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