Примеры
Восемь задач, прогнанных по-настоящему
Ни одно число на этой странице не переписано из документации. Все они получены запуском scripts/generate-benchmarks.py на библиотеке balansis 1.1.0, установленной с PyPI, 2026-09-08. Там, где прогон не подтвердил обещание из README, это написано прямо.
- 1. Агрегация со взаимным уничтожением
- 2. Плохо обусловленное скалярное произведение
- 3. Softmax за пределами float64
- 4. Бухгалтерский ноль
- 5. Сигнал о потере точности
- 6. Деление на ноль как состояние
- 7. SVD: что в ней компенсированное
- 8. Цена компенсации (в бенчмарках)
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| Метод | Результат | Верных знаков | Абс. ошибка | Время (min) |
|---|---|---|---|---|
| наивный цикл float64 порядок слева направо, как в любом учебном коде | 0 | 0.0 | 100 | 3.11 мкс |
| встроенный sum() с CPython 3.12 sum() применяет компенсацию Ноймайера — README Balansis всё ещё показывает для него 0.0, это устаревшее утверждение | 100 | 16 (предел float64) | 0 | 1.11 мкс |
| numpy.sum (попарное суммирование) pairwise summation numpy, не компенсация | 9 | 0.0 | 91 | 8.32 мкс |
| math.fsum точное суммирование Шевчука в C — прямой конкурент, не соломенное чучело | 100 | 16 (предел float64) | 0 | 1.57 мкс |
| Balansis Operations.sequence_sum Ноймайер поверх AbsoluteValue; возвращает и сумму, и величину компенсации | 100 | 16 (предел float64) | 0 | 39.31 мкс |
Практический вывод: если вам нужна только сумма, 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| Метод | Результат | Верных знаков | Абс. ошибка | Время (min) |
|---|---|---|---|---|
| наивный цикл float64 | -32 | 0.0 | 31.4694 | 37.81 мкс |
| numpy.dot (BLAS) | -32 | 0.0 | 31.4694 | 643.0 нс |
| math.fsum над произведениями точная сумма округлённых произведений — ошибка остаётся | -8.37496948242188 | 0.0 | 7.84441 | 51.89 мкс |
| Balansis dot2 (Ogita–Rump–Oishi) two_product ловит ошибку каждого произведения, math.fsum складывает всё точно | -0.530562605606728 | 16 (предел float64) | 4.478e-17 | 87.65 мкс |
| Balansis numpy_integration.compensated_dot_product публичный numpy-мост, внутри тот же dot2 | -0.530562605606728 | 16 (предел float64) | 4.478e-17 | 87.57 мкс |
3. Softmax на логитах, выходящих за диапазон float64
softmax([1000, 1000, 999, -1000]) — exp(1000) переполняет float64.
| Метод | Результат | Макс. абс. ошибка | Время (min) |
|---|---|---|---|
| np.exp(x)/sum — без сдвига | NaN | — | 4.04 мкс |
| np.exp(x - max)/sum — стандартный приём | 0.422318798, 0.422318798, 0.155362403, 0 | 1.238e-17 | 3.32 мкс |
| Balansis compensated_softmax | 0.422318798, 0.422318798, 0.155362403, 0 | 1.238e-17 | 5.48 мкс |
Второй прогон — на 20 000 логитов, где ошибка копится в знаменателе: максимальная абсолютная ошибка у numpy со сдвигом 4.771e-19, у Balansis 4.771e-19 — то есть одинаковая, при разнице во времени 41×.
Про эталон здесь важна деталь методики: экспонента считается не во float64, а в decimal с 60 значащими цифрами, сумма и деление — там же. Иначе «эталон» сам был бы участником соревнования, и величины ниже 1e−16 измеряли бы совпадение с float, а не настоящую ошибку.
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 — структурный ноль| Случай | Метод | Баланс | Ноль получен? |
|---|---|---|---|
| 128.5 суммы, точно представимые в двоичном float64 (128.5, 0.25) | накопление в float64 | 0 | точный ноль |
| decimal.Decimal | 0.00 | точный ноль | |
| Balansis Ledger.balance() | 0 | ABSOLUTE | |
| 12345.67 обычные денежные суммы (12345.67), не представимые в двоичном float точно | накопление в float64 | 4.365570405174e-13 | нет |
| decimal.Decimal | 0.00 | точный ноль | |
| Balansis Ledger.balance() | 4.365570405174e-13 | нет | |
| 10000000000000.01 суммы за пределами точности float64 (14 значащих цифр плюс копейки) | накопление в float64 | -0.00046875 | нет |
| decimal.Decimal | 0.00 | точный ноль | |
| Balansis Ledger.balance() | -0.00046875 | нет |
5. Сигнал о потере точности — не то же самое, что восстановленное значение
compensated_add(1e16, -1e16): математически точный ответ здесь — ноль.
| Метод | Результат | Точный ответ | Совпадает? |
|---|---|---|---|
| 1e16 + (-1e16) в float64 | 0 | 0 | да |
| Operations.compensated_add коэффициент компенсации 5.000e+15; величина равна одному ULP операнда | 2 | 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| Выражение | Python / numpy | Balansis: состояние | propagate | saturate | raise |
|---|---|---|---|---|---|
| 1 / 0 | ZeroDivisionError · numpy inf | infinite finite_over_absolute | inf | 1000000000000 | ValueError |
| -1 / 0 | ZeroDivisionError · numpy -inf | infinite finite_over_absolute | -inf | -1000000000000 | ValueError |
| 0 / 0 | ZeroDivisionError: float division by zero · numpy nan | indeterminate absolute_over_absolute | nan | nan | ValueError |
7. SVD: что в ней действительно «компенсированное»
Матрица 24×24 с заданными сингулярными числами от 1 до 1e-14.
| Бэкенд | Ошибка восстановления | Отн. ошибка сингулярных чисел | Время (min) |
|---|---|---|---|
numpy_gesdd | 2.169e-15 | 0.00105404 | 1.80 мс |
act_jacobi | 5.395e-15 | 0.00171966 | 188.05 мс |
Полные исходные данные всех сценариев, включая медианы времени и параметры генерации входов, лежат в файле data/benchmarks.json репозитория сайта; скрипт, который их получил, — scripts/generate-benchmarks.py. Как повторить прогон у себя — раздел «Честность».