Примеры

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

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

Это сохранённый прогон версии 1.1.0, а не проверка последнего релиза. В Python 3.12 улучшили sum и добавили math.sumprod. Последний в этих таблицах не измерен; сравнение с современной стандартной библиотекой неполное. Документация Python.

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 мкс
В этом прогоне встроенный sum, math.fsum и Balansis совпали с эталоном. Это равенство результатов на выбранных входах. Дополнительный коэффициент Balansis описывает накопленную поправку, а не оставшуюся ошибку.

Практический вывод: если вам нужна только сумма, math.fsum уже решает задачу и делает это в 25× быстрее. Balansis дополнительно возвращает нормированную поправку. Это диагностика накопления, а не граница ошибки результата или число восстановленных разрядов. Начиная с Python 3.12 встроенный sum точнее прежнего; наивный цикл в таблице остаётся отдельным методом. Изменения sum.

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

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

from fractions import Fraction
import numpy as np
from balansis.numpy_integration import compensated_dot_product

rng = np.random.default_rng(20260908)
a = rng.uniform(-1.0, 1.0, 1000) * 1e8
b = rng.uniform(-1.0, 1.0, 1000) * 1e8
rest = sum(Fraction(float(x)) * Fraction(float(y))
           for x, y in zip(a[:-1], b[:-1]))
b[-1] = float((Fraction(1) - rest) / Fraction(float(a[-1])))
exact = sum(Fraction(float(x)) * Fraction(float(y)) for x, y in zip(a, b))

print(float(np.dot(a, b)))                  # в прогоне: -32
print(float(compensated_dot_product(a, b))) # в прогоне: -0.5305626056067283
assert float(compensated_dot_product(a, b)) == float(exact)
Скалярное произведение двух векторов длины 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 получает уже округлённые произведения. Компенсация произведений сохраняет здесь остаток, который этот способ суммирования восстановить не может. math.sumprod также использует повышенную точность промежуточных произведений; без его измерения нельзя заключить, что стандартная библиотека не решает эту задачу. Совпадение с эталоном на этих входах не является общей гарантией для dot2.

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 — без сдвигаNaN—4.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, а не настоящую ошибку.

Сдвиг на максимум предотвращает переполнение в обеих устойчивых реализациях. Сравнивать Balansis следует с NumPy после такого же сдвига, а не только с наивным вычислением экспонент. Компенсация не гарантирует дополнительной точности на любых входах; разницу с эталоном показывает таблица этого прогона.

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нет
В измеренной версии 1.1.0 запись Decimal преобразуется в float. Поэтому тип ответа не обеспечивает точности десятичных денег и не восстанавливает потерянные при преобразовании разряды. Для таких расчётов сохраняйте Decimal на всём пути. Это наблюдение относится к указанному релизу, а не автоматически к новым версиям.

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

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

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

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
Balansis позволяет выбрать политику явно: исключение, сохранение особого состояния или насыщение до установленного предела. Предел — решение приложения, а не значение деления на ноль. Lean описывает соответствующую математическую модель; конкретные Python-результаты проверены этим прогоном, а не доказаны им для всех входов.

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. Как повторить прогон у себя — раздел «Честность».