Плавающая точка запрещена на консенсусном пути по одной причине: два честных узла должны совпасть в последнем бите — между компиляторами, флагами и железом. Остаётся фиксированная точка — а стоит её принять, как большая часть мистики исчезает. Это гайд по Q32.32 так, как его реально использует ядро.
Раскладка
al_q32 — обычный uint64_t. Старшие 32 бита — целая часть, младшие 32 — дробная, поэтому единица веса хранится как 1 << 32. Ни структур, ни тегов, ни упаковки — просто целое число и конвенция.
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 — поэтому его вывод побитово совпадает с валидатором. Детерминизм — не свойство, на которое надеются, а то, от чего отказываются на каждом шаге.