Примеры

Восемь задач, прогнанных по-настоящему

Ни одно число на этой странице не переписано из документации. Все они получены запуском scripts/generate-benchmarks.py на библиотеке balansis 1.1.0, установленной с PyPI, 2026-09-08. Там, где прогон не подтвердил обещание из README, это написано прямо.

1. Агрегация со взаимным уничтожением слагаемых

Сумма 100 повторов блока [1e16, 1.0, -1e16]. Точный ответ — 100.

from balansis import AbsoluteValue as Bv, Operations

values = [Bv.from_float(v) for v in [1e16, 1.0, -1e16] * 100]
result, compensation = Operations.sequence_sum(values)

result.to_float()   # 100
compensation        # 1.000e+17
Сумма 100 повторов блока [1e16, 1.0, -1e16]. Точный ответ — 100. Измерено 2026-09-08
МетодРезультатВерных знаковАбс. ошибкаВремя (min)
наивный цикл float64
порядок слева направо, как в любом учебном коде
00.01003.11 мкс
встроенный sum()
с CPython 3.12 sum() применяет компенсацию Ноймайера — README Balansis всё ещё показывает для него 0.0, это устаревшее утверждение
10016 (предел float64)01.11 мкс
numpy.sum (попарное суммирование)
pairwise summation numpy, не компенсация
90.0918.32 мкс
math.fsum
точное суммирование Шевчука в C — прямой конкурент, не соломенное чучело
10016 (предел float64)01.57 мкс
Balansis Operations.sequence_sum
Ноймайер поверх AbsoluteValue; возвращает и сумму, и величину компенсации
10016 (предел float64)039.31 мкс
Наивный цикл теряет ответ полностью. Balansis его восстанавливает — но math.fsum восстанавливает тот же ответ и делает это быстрее. Отличие Balansis здесь не в цифре суммы, а в том, что вместе с суммой возвращается величина компенсации: сколько именно точности пришлось спасать.

Практический вывод: если вам нужна только сумма, math.fsum уже решает задачу и делает это в 25× быстрее. Balansis здесь покупает не точность, а число рядом с ответом — величину компенсации, которую можно логировать и по которой можно ставить пороги.

2. Плохо обусловленное скалярное произведение

Скалярное произведение двух векторов длины 1000: точный ответ -0.530562605606728 при числе обусловленности ≈ 4.62e+18. Эталон вычислен в рациональной арифметике (fractions.Fraction), а не в float повышенной точности.

import numpy as np
from balansis.numpy_integration import compensated_dot_product

# a, b: по 1000 элементов в диапазоне ±1e8
float(np.dot(a, b))                  # -32
float(compensated_dot_product(a, b)) # -0.5305626056067283
Скалярное произведение двух векторов длины 1000: точный ответ -0.530562605606728 при числе обусловленности ≈ 4.62e+18. Эталон вычислен в рациональной арифметике (fractions.Fraction), а не в float повышенной точности. Измерено 2026-09-08
МетодРезультатВерных знаковАбс. ошибкаВремя (min)
наивный цикл float64-320.031.469437.81 мкс
numpy.dot (BLAS)-320.031.4694643.0 нс
math.fsum над произведениями
точная сумма округлённых произведений — ошибка остаётся
-8.374969482421880.07.8444151.89 мкс
Balansis dot2 (Ogita–Rump–Oishi)
two_product ловит ошибку каждого произведения, math.fsum складывает всё точно
-0.53056260560672816 (предел float64)4.478e-1787.65 мкс
Balansis numpy_integration.compensated_dot_product
публичный numpy-мост, внутри тот же dot2
-0.53056260560672816 (предел float64)4.478e-1787.57 мкс
Здесь у стандартной библиотеки нет ответа: math.fsum складывает точно, но произведения уже округлены до вызова. Это тот случай, ради которого компенсированная арифметика и существует — и цена за неё видна в колонке времени.

3. Softmax на логитах, выходящих за диапазон float64

softmax([1000, 1000, 999, -1000]) — exp(1000) переполняет float64.

Логиты [1000, 1000, 999, -1000]; эталон — decimal.Decimal.exp с 60 значащими цифрами, сумма и деление там же. Измерено 2026-09-08
МетодРезультатМакс. абс. ошибкаВремя (min)
np.exp(x)/sum — без сдвигаNaN4.04 мкс
np.exp(x - max)/sum — стандартный приём0.422318798, 0.422318798, 0.155362403, 01.238e-173.32 мкс
Balansis compensated_softmax0.422318798, 0.422318798, 0.155362403, 01.238e-175.48 мкс

Второй прогон — на 20 000 логитов, где ошибка копится в знаменателе: максимальная абсолютная ошибка у numpy со сдвигом 4.771e-19, у Balansis 4.771e-19 — то есть одинаковая, при разнице во времени 41×.

Про эталон здесь важна деталь методики: экспонента считается не во float64, а в decimal с 60 значащими цифрами, сумма и деление — там же. Иначе «эталон» сам был бы участником соревнования, и величины ниже 1e−16 измеряли бы совпадение с float, а не настоящую ошибку.

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

4. Двойная запись: баланс обязан быть нулём

Четыре проводки, сумма которых математически равна нулю — в двух масштабах величин.

from decimal import Decimal
from balansis.finance.ledger import Ledger

ledger = Ledger()
for amount in ["128.50", "0.25", "-128.50", "-0.25"]:
    ledger.post_entry("cash", Decimal(amount))

ledger.balance().is_absolute()   # True — структурный ноль
Один и тот же набор проводок в трёх масштабах величин. Точная сумма во всех случаях — ноль. Измерено 2026-09-08
СлучайМетодБалансНоль получен?
128.5
суммы, точно представимые в двоичном float64 (128.5, 0.25)
накопление в float640точный ноль
decimal.Decimal0.00точный ноль
Balansis Ledger.balance()0ABSOLUTE
12345.67
обычные денежные суммы (12345.67), не представимые в двоичном float точно
накопление в float644.365570405174e-13нет
decimal.Decimal0.00точный ноль
Balansis Ledger.balance()4.365570405174e-13нет
10000000000000.01
суммы за пределами точности float64 (14 значащих цифр плюс копейки)
накопление в float64-0.00046875нет
decimal.Decimal0.00точный ноль
Balansis Ledger.balance()-0.00046875нет
Прогон показал границу честнее, чем это описано в самом репозитории. Balansis.Ledger отвечает состоянием ABSOLUTE — структурным нулём, который нельзя перепутать с маленьким остатком, — но ровно тогда, когда и обычное сложение float даёт ноль: суммы должны быть точно представимы в двоичном float64. На «12345.67» и тем более на «10000000000000.01» структурный ноль исчезает, и Ledger возвращает ту же ошибку, что и float, потому что Ledger.post_entry принимает Decimal и немедленно приводит его к float. Компенсация не восстанавливает то, что потеряно на входе. Вывод для практики: ценность Ledger — в типе ответа (ноль как состояние, а не как число), а точность на деньгах даёт Decimal, и Balansis его не заменяет.

5. Сигнал о потере точности — не то же самое, что восстановленное значение

compensated_add(1e16, -1e16): математически точный ответ здесь — ноль.

1e16 + (-1e16): точная разность равна нулю. Измерено 2026-09-08
МетодРезультатТочный ответСовпадает?
1e16 + (-1e16) в float6400да
Operations.compensated_add
коэффициент компенсации 5.000e+15; величина равна одному ULP операнда
20нет — это сигнал, а не значение
Здесь Balansis намеренно отвечает не тем числом, которое верно математически. Точная разность равна нулю, и float64 её даёт; Balansis возвращает величину в один ULP от операнда — это флаг «в этом месте операнды настолько велики, что настоящая разность могла быть потеряна раньше», плюс большой коэффициент компенсации. Полезно, когда ноль опасно принимать за истину (следом идёт деление или логарифм). Вредно, если читать результат как «восстановленное значение»: это сигнал, а не восстановление. Ровно так это и нужно понимать в разделе README «Catastrophic Cancellation».

6. Деление на ноль как состояние, а не как исключение

1/0, -1/0 и 0/0 — что возвращает язык и что возвращает Balansis.

from balansis import AbsoluteValue as Bv, Operations, SingularPolicy

ratio, compensation = Operations.compensated_divide_extended(
    Bv.from_float(1.0), Bv.absolute()
)
ratio.kind        # 'infinite'
ratio.reason      # 'finite_over_absolute'

value, _, event = Operations.compensated_divide_policy(
    Bv.from_float(1.0), Bv.absolute(), policy=SingularPolicy.SATURATE
)
value.numerical_value()   # 1000000000000
Что возвращают язык, numpy и Balansis на вырожденном делении. Измерено 2026-09-08
ВыражениеPython / numpyBalansis: состояниеpropagatesaturateraise
1 / 0ZeroDivisionError · numpy infinfinite
finite_over_absolute
inf1000000000000ValueError
-1 / 0ZeroDivisionError · numpy -infinfinite
finite_over_absolute
-inf-1000000000000ValueError
0 / 0ZeroDivisionError: float division by zero · numpy nanindeterminate
absolute_over_absolute
nannanValueError
Python бросает исключение и теряет знак; numpy тихо возвращает inf и nan с предупреждением, которое почти всегда отключено. Balansis возвращает типизированное состояние с направлением и причиной, а политика raise/propagate/saturate выбирается вызывающим кодом явно. Ровно эта семантика и доказана в Lean4 (файл ACT/ExtendedRatio.lean).

7. SVD: что в ней действительно «компенсированное»

Матрица 24×24 с заданными сингулярными числами от 1 до 1e-14.

Матрица 24×24 с заданными сингулярными числами от 1 до 1e−14. Измерено 2026-09-08
БэкендОшибка восстановленияОтн. ошибка сингулярных чиселВремя (min)
numpy_gesdd2.169e-150.001054041.80 мс
act_jacobi5.395e-150.00171966188.05 мс
Бэкенд numpy_gesdd — это буквально numpy.linalg.svd: выигрыша в точности у Balansis здесь нет и заявлять его было бы неправдой. Что добавлено — телеметрия по каждому сингулярному числу и явная политика на вырожденных состояниях. Бэкенд act_jacobi считает скалярные произведения через dot2; цена этого видна в таблице времени.

Полные исходные данные всех сценариев, включая медианы времени и параметры генерации входов, лежат в файле data/benchmarks.json репозитория сайта; скрипт, который их получил, — scripts/generate-benchmarks.py. Как повторить прогон у себя — раздел «Честность».