
Зиновьев Василий
автор библиотеки paradox
Добрый день, друзья! Сегодня вы познакомитесь с «новой» парадигмой в математике и получите реальный инструмент для смелых экспериментов. В конце статьи вы найдёте ссылку на работающую библиотеку C++ (Paradox libraries), для использования в ваших проектах.

Постановка задачи:
Большинство людей уже не воспринимают эти проблемы как проблемы, а профессиональные разработчики искусно обходят все подводные камни работы с нулями и бесконечностями, но давайте нарушим привычный ход вещей и объявим следующие проблемы:
Деление на 0! Да многие из вас воскликнут, это не проблема, мы с лёгкостью можем ловить и обрабатывать такое исключение. Да всё верно, само деление на ноль мы обработаем, но результат операции будет потерян для дальнейших расчётов...
Умножение на 0! Тут справедливо вы заметите, что это вообще не проблема и никогда таковой не была! В целом вы правы, классика нам говорит, что результатом будет 0! Но как дальше использовать это? Снова безвозвратная потеря информации.
Если мы попытаемся разделить результат умножения числа на ноль, на тот же самый ноль, что мы получим в классике? NAN. Какой никакой результат, но теперь мы вообще не понимаем как это использовать..
Ну и наконец, давайте представим ситуацию когда нам необходимо сымитировать бесконечность в расчётах, что мы будем использовать? Как быстро у нас случиться переполнение?
Мы не будем углубляться в историю проблем "деления на ноль" и разбирать "концепции работы с бесконечностями", за историю человечества их накопилось целое множество, о выше упомянутых проблемах и способах их решения вы найдёте много материала в интернете, особенно подробно описал их уважаемый Ivan Galavachov @habit в этих статьях: Часть 1. Вообще-то уже все поделили до нас! Часть 2. Истина где-то рядом, за что ему отдельное спасибо и глубокое уважение.
Итак давайте создадим такой инструмент(метод), который не нарушая общепринятые законы классической математики, решит все перечисленные выше проблемы и сделает это в нашей обычной числовой системе которую использует каждый из наших компьютеров, а именно позволит делить и умножать на 0 и/или бесконечность без потери данных*, решит проблему NAN при 0/0 или бесконечность/бесконечность. Добавим математически строгую и корректную работу с бесконечностями и конечно же устраним проблему переполнения чисел в расчётах.
*(потерей данных в данном случае назовём невозможность обратного восстановления числа при умножении или делении его с нолём и/или бесконечностью), забегу немного вперёд, в итоге мы получим ещё кое-какие фичи, возможно важные, а возможно нет, а их использование возможно станет темой следующих статей других авторов.
Сразу скажу, что данная теория ни как не относится к теории пределов функций и математическому анализу, это вообще не про пределы, это приём работы с конкретными числами, нолями и бесконечностями.
Общий концепт:
Представьте, вы находитесь в пространстве, а в наших руках линейка, она на столько длинная насколько это возможно. Как любой физический предмет наша линейка имеет конец. В попытке ответить на вопрос: а что находится за этим концом?, можно сколь угодно долго увеличивать линейку, но мы так и не получим ответа. Пора смериться и сказать самим себе: за пределами линейки - бесконечность. она не имеет значения в числовом виде, это не числа, как мы можем их представить, это бесконечно большое количество сущностей, в каждую из которых войдёт бесконечно большое количество наших невообразимо длинных линеек.
Для того, чтобы оперировать бесконечно большими сущностями, теми что находятся за пределами линейки нужен некий ориентир на который мы будем опираться, назовём его Абсолют (А), и дадим определение:
Абсолют (А) — это условно выбранная, мысленно фиксированная "точка" или "состояние" в бесконечности. Она не является числом, не имеет конкретной "величины" в привычном смысле и не может быть выражена в числовой форме. Это методологический прием: поскольку работать с "бесконечностью" вообще абстрактно и сложно, мы вводим A как воображаемый, но фиксированный ориентир в бесконечности. Его "значение" неизвестно (или принципиально неопределимо как число), но мы принимаем его существование и фиксированность по соглашению для нужд нашей модели.
Абсолют служит фундаментальной опорой (репером, базисом), точкой отсчета, относительно которой определяются направления ("порядок Абсолюта," "степень Абсолюта") сравниваются другие бесконечности или конечные величины, строятся и вводится понятие "Ноля", вводится понятия Уровня бесконечности(абсолюта).
Не надо путать Абсолют с классическими понятиями:
Это не мощность/ординал в чистом виде: А не характеризует размер множества (ℵ₀, ℵ₁), а служит внешним ориен��иром для сравнения таких размеров.
Это не предел: А — это не число, к которому что-то стремится (как lim x->∞ 1/x = 0). Он — сама условная цель стремления (x -> 𝔸).
Это не "бесконечность" вообще: А — конкретно выбранный (пусть условно) экземпляр бесконечности в данной модели.
Имея такую абстракцию как А, зададим правила для расширенного числового пространства:
пусть Х - регулярное число, зададим что А0 = 1, тогда X = X * А0;
Тогда X = X * А1- это X в бесконечности или если по классике это бесконечность, но в нашем случае конкретная бесконечность, тогда любое регулярное число умноженное на абсолют в степени больше чем 0 это число в бесконечности или по классике это бесконечность. в то же время любое регулярное число умноженное на А-1, А-2 и т.д. это 0.
ноль | регулярные числа | бесконечность |
X * А-1, А-2 ,...., А-n | X * А0 | X * А1, А2 ,...., Аn |
Дадим определение ноля в нашей модели:
Ноль - это любое число умноженное на абсолют в отрицательной степени.
Итак, мы получили индексированную или многоуровневую модель арифметики, и в связи с особенностями её архитектуры, появилась необходимость ввести ещё кое какие правила:
Регулярное число X не может быть рав��ым 0, так как Ноль для нашей системы это множество, то есть запись типа: 0 * А0 некорректна.
Бесконечность не равна бесконечности, если только это не таже самая бесконечность.
Ноль не равен нулю, если только это не тот же самый ноль.
Полный код библиотеки и примеры использования вы можете по ссылке в конце статьи, тут я приведу лишь ключевые моменты инициализации и математических операторов для работы с многоуровневыми числами.
Как устроено многомерное число: под капотом Paradox libraries
Когда вы создаёте объект dspirit из обычного числа, происходит не просто копирование значения. Давайте разберёмся, почему и как.
Конструкторы: мост между классической и многомерной математикой
// Основные конструкторы dspirit:
// Из double
dspirit a(3.14); // Обычное число -> уровень 0
dspirit b(0.0); // Классический ноль -> особый случай!
// Из float и int
dspirit c(2.5f); // Float автоматически приводится
dspirit d(42); // Целое число -> уровень 0
// Специальный конструктор из уровня
dspirit e = dspirit::fromLevel(1.0, -1.0); // Число 1 на уровне -1Почему ноль — это особый случай?
// Внутри конструктора dspirit::Impl(double value):
void init(double value, double level = 0.0) {
if (isApproxZero(value)) {
// Для нуля используем единицу в первом отрицательном слое
r_ = 1.0; // Храним 1.0, а не 0.0!
i_ = 0.0;
j_ = 0.0;
level_ = level - 1.0; // Уровень на 1 ниже
} else {
// Обычное число
r_ = value;
i_ = 0.0;
j_ = 0.0;
level_ = level;
}
}Почему так? Потому что в нашей системе:
0(классический ноль) представляется как1 * ∞⁻¹1(единица) представляется как1 * ∞⁰∞(бесконечность) представляется как1 * ∞¹
Таким образом, ноль — это не отсутствие значения, а значение на уровне -1!
Математические операторы: как работает магия
Давайте посмотрим на реализацию ключевых операций.
1. Сложение: работа с разными уровнями
Impl add(const Impl& other) const {
// Быстрый путь для одинаковых уровней
if (isApproxEqualLevel(level_, other.level_)) {
Impl result;
result.r_ = r_ + other.r_;
result.i_ = i_ + other.i_;
result.j_ = j_ + other.j_;
result.level_ = level_;
result.normalize(); // Важно: нормализуем результат!
return result;
}
// Находим максимальный уровень
const double max_level = std::max(level_, other.level_);
// Если разница в уровнях больше 2, меньшими уровнями можно пренебречь
if (max_level - std::min(level_, other.level_) > 2.5) {
return (level_ > other.level_) ? *this : other;
}
// Суммируем значения на каждом уровне
const double sum_r = atLevel(max_level) + other.atLevel(max_level);
const double sum_i = atLevel(max_level - 1.0) + other.atLevel(max_level - 1.0);
const double sum_j = atLevel(max_level - 2.0) + other.atLevel(max_level - 2.0);
Impl result(sum_r, sum_i, sum_j, max_level);
result.normalize();
return result;
}Пример работы сложения:
// 10 (уровень 0) + 1*∞ (уровень 1)
dspirit a(10.0); // 10 * ∞⁰
dspirit b = dspirit::INF; // 1 * ∞¹
dspirit c = a + b;
// Результат: 1 * ∞¹ (бесконечность "поглощает" конечное число)
// Но информация о 10 сохраняется в младших уровнях!2. Умножение: складываем уровни
Impl multiply(const Impl& other) const {
// Умножение: уровни складываются!
const double result_level = level_ + other.level_;
// Умножаем значения (как в полиномах)
const double result_r = r_ * other.r_;
const double result_i = r_ * other.i_ + i_ * other.r_;
const double result_j = r_ * other.j_ + i_ * other.i_ + j_ * other.r_;
Impl result(result_r, result_i, result_j, result_level);
result.normalize();
return result;
}Математическая основа:
Если A = a ∞ᴸ и B = b ∞ᴹ, то: A B = (a b) * ∞ᴸ⁺ᴹ
3. Деление: вычитаем уровни
Impl divide(const Impl& divisor) const {
// Деление: уровни вычитаются!
const double result_level = level_ - divisor.level_;
// Алгоритм аналогичен делению полиномов
const double result_r = r_ / divisor.r_;
const double result_i = (i_ - result_r * divisor.i_) / divisor.r_;
const double result_j = ((j_ - result_r * divisor.j_) - result_i * divisor.i_) / divisor.r_;
Impl result(result_r, result_i, result_j, result_level);
result.normalize();
return result;
}Почему это работает для деления на ноль?
// Делим число на ноль:
dspirit x(42.0); // 42 * ∞⁰
dspirit zero(0.0); // 1 * ∞⁻¹
dspirit result = x / zero;
// Уровень результата: 0 - (-1) = 1
// Значение: 42 / 1 = 42
// Итог: 42 * ∞¹ (бесконечность, содержащая информацию о 42)Нормализация: поддержание канонической формы
void normalize() {
if (isApproxZero(r_)) {
if (!isApproxZero(i_)) {
// Сдвигаем значения вверх
r_ = i_;
i_ = j_;
j_ = 0.0;
level_ -= 1.0; // Понижаем уровень
} else if (!isApproxZero(j_)) {
r_ = j_;
i_ = 0.0;
j_ = 0.0;
level_ -= 2.0;
} else {
// Все нули - особый случай
r_ = 1.0;
i_ = 0.0;
j_ = 0.0;
level_ -= 1.0;
}
return;
}
resetLowerLevels();
}Зачем нужна нормализация? Чтобы всегда иметь каноническое представление:
Старший уровень
r_никогда не равен нулюНули в младших уровнях явно обнуляются
Число всегда в наиболее компактной форме
Операторы сравнения: как сравнивать бесконечности
bool lessThan(const Impl& other) const {
// Оба нуля
if (isZero() && other.isZero()) return false;
// Текущее - ноль, другое - нет
if (isZero()) return other.isPositive();
// Другое - ноль, текущее - нет
if (other.isZero()) return isNegative();
// Оба не нули - сравниваем уровни
if (!isApproxEqualLevel(level_, other.level_)) {
if (isPositive() && other.isPositive()) {
return level_ < other.level_; // Меньший уровень < большего
}
if (isNegative() && other.isNegative()) {
return level_ > other.level_; // Для отрицательных наоборот
}
return isNegative() && other.isPositive();
}
// Одинаковые уровни - сравниваем значения
return r_ < other.r_;
}Пример сравнения бесконечностей:
dspirit inf1 = dspirit::INF; // 1 * ∞¹
dspirit inf2 = inf1 * inf1; // 1 * ∞²
cout << (inf2 > inf1) << endl; // true!
cout << (inf1 > 1000.0) << endl; // true
cout << (0.0 > -inf1) << endl; // trueПолный пример: цепочка операций
#include <iostream>
#include "paradox/spirit_double.h"
using namespace paradox;
void demonstrate_chain() {
std::cout << "=== Цепочка операций с сохранением информации ===" << std::endl;
// Исходное число
dspirit x = 7.5;
std::cout << "1. Исходное число: " << x.debugString() << std::endl;
// Умножаем на ноль
dspirit y = x * 0.0;
std::cout << "2. После умножения на 0: " << y.debugString() << std::endl;
std::cout << " В классике: 0, у нас: " << y << std::endl;
// Умножаем на бесконечность
dspirit z = y * dspirit::INF;
std::cout << "3. После умножения на INF: " << z.debugString() << std::endl;
// Делим на бесконечность
dspirit w = z / dspirit::INF;
std::cout << "4. После деления на INF: " << w.debugString() << std::endl;
// Делим на ноль
dspirit v = w / 0.0;
std::cout << "5. После деления на 0: " << v.debugString() << std::endl;
// И восстанавливаем исходное!
dspirit restored = v * 0.0 * dspirit::INF / dspirit::INF * 0.0;
std::cout << "6. Восстановленное: " << restored.debugString() << std::endl;
std::cout << " Значение: " << restored << " (ожидалось 7.5)" << std::endl;
if (std::abs(static_cast<double>(restored) - 7.5) < 1e-10) {
std::cout << "✓ Информация полностью сохранена!" << std::endl;
}
}
int main() {
demonstrate_chain();
return 0;
}Интеграция с STL и алгоритмами
// dspirit можно использовать в контейнерах STL
#include <vector>
#include <algorithm>
#include <numeric>
void stl_integration() {
std::vector<dspirit> numbers = {5.0, 0.0, -3.0, dspirit::INF, 2.5};
// Сортировка работает!
std::sort(numbers.begin(), numbers.end());
std::cout << "Отсортированные числа:" << std::endl;
for (const auto& n : numbers) {
std::cout << n.debugString() << std::endl;
}
// Суммирование с аккумуляцией
dspirit sum = std::accumulate(
numbers.begin(),
numbers.end(),
dspirit::ZERO
);
std::cout << "\nСумма: " << sum.debugString() << std::endl;
// Поиск максимального (учитывая бесконечности!)
auto max_it = std::max_element(numbers.begin(), numbers.end());
std::cout << "Максимум: " << max_it->debugString() << std::endl;
}Производительность: под капотом
// dspirit использует идиому Pimpl (Pointer to implementation)
// Это даёт несколько преимуществ:
class dspirit {
private:
class Impl; // Объявление внутреннего класса
Impl* pimpl; // Указатель на реализацию
public:
// Небольшой размер (размер указателя)
// Быстрое копирование (можно добавить copy-on-write)
// Стабильный ABI (можно менять Impl без перекомпиляции клиентов)
// Размеры:
static_assert(sizeof(dspirit) == sizeof(void*),
"dspirit должен быть размером с указатель");
};
// При этом все данные хранятся в куче:
class dspirit::Impl {
double r_, i_, j_; // Три уровня
double level_; // Текущий уровень
// Итого: 4 * double = 32 байта
};Заключение технической части
Мы рассмотрели, как Paradox libraries реализует многомерные числа:
Конструкторы создают мост между классическими и многомерными числами
Ноль представляется как число на уровне -1, а не как отсутствие значения
Арифметические операторы работают с учётом уровней бесконечности
Нормализация поддерживает каноническую форму чисел
Сравнения учитывают и уровни, и значения внутри уровней
Ключевое преимущество: Библиотека не просто "обходит" проблемы деления на ноль, а предоставляет последовательную математическую модель, где все операции имеют чёткий смысл.
Попробуйте в действии:
// Самый впечатляющий пример:
dspirit create_singularity(double value) {
return value / 0.0 * 0.0 / 0.0 * 0.0 * dspirit::INF / dspirit::INF;
}
// Несмотря на все "опасные" операции,
// информация о value не теряется!Где это можно применять?
Научные расчёты — физика, инженерия, там где встречаются сингулярности
Машинное обучение — обработка выбросов, работа с разреженными данными
Финансовое моделирование — расчёты с нулевыми ставками, бесконечными горизонтами планирования
Компьютерная графика — обработка перспективы, камеры, работа с бесконечно удалёнными объектами
Игровые движки — физика, коллизии, генерация бесконечных миров
Попробуйте сами!
Библиотека доступна на GitHub: https://github.com/Dydya-Vasya/paradox.
В репозитории вы найдёте:
Полную документацию API
Примеры использования
Юнит-тесты для проверки корректности
CMake-файлы для лёгкой интеграции
Что дальше?
Эта реализация — только начало. В планах:
Поддержка комплексных многомерных чисел
Векторизованные операции (SIMD)
Интеграция с библиотеками линейной алгебры
Реализация на других языках (Python, Rust, JavaScript)
Присоединяйтесь к разработке! Если идея вас заинтересовала:
Ставьте звёзды на GitHub
Пробуйте в своих проектах
Сообщайте об ошибках и предлагайте улучшения
Пишите свои статьи и исследования на основе библиотеки
Вопрос к читателям: В какой области вы бы попробовали применить эту технологию? Делитесь идеями в комментариях!
P.S. Помнитетот пример с линейкой в начале статьи? Теперь у вас есть не просто линейка, а телескоп, который позволяет заглянуть за её край, и микроскоп, чтобы рассмотреть, что происходит в самой точке «ноль». И всё это — в привычном мире double‑чисел вашего процессора.
