Почему медленно вычисляются арифметические выражения в 1С и как это исправить?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При выполнении математического моделирования, циклических численных алгоритмов или сложных формул во встроенном языке 1С разработчики иногда сталкиваются с неожиданной ситуацией: элементарная строка вычисления дроби выполняется 20 и более секунд. При этом замер производительности показывает, что время уходит не на обращения к базе данных или серверные вызовы, а непосредственно на оператор деления или умножения. Разберем по шагам, почему возникает эта проблема, как устроена математика платформы 1С и как оптимизировать код для мгновенного выполнения расчетов.

Проанализируем ситуацию на классическом примере из практики. В коде выполняется расчет выражения:

строкаР.Параметры.Плт = Формат(377.1562 * оПр / вПр - 377.836, "ЧДЦ=1; ЧГ=");

На первый взгляд, это обычная линейная формула. Однако выполнение строчки занимает десятки секунд. Если вынести выражение оПр / вПр в табло отладчика, вычисление также зависает. При этом добавление явного округления Окр(оПр, 10) / Окр(вПр, 10) решает проблему, и расчет начинает выполняться мгновенно.

Выясним причину: как платформа 1С хранит числа в оперативной памяти

Многие разработчики ошибочно полагают, что числовые переменные в оперативной памяти 1С соответствуют стандартным типам с плавающей точкой (таким как float или double стандарта IEEE 754). В реальности это не так. В финансовых системах вычисления с плавающей точкой неприменимы из-за неизбежной погрешности округления в младших разрядах.

Рассмотрим ключевые особенности работы типа Число в платформе 1С:

  1. Эмуляция десятичной арифметики произвольной точности: Тип Число в 1С концептуально близок к классу BigDecimal в Java. Платформа оперирует десятичными числами программным путем, не полагаясь на быстрые процессорные инструкции FPU.
  2. Разница между базой данных и памятью: В таблицах СУБД для типа Число установлено жесткое ограничение: максимум 38 знаков общей длины и не более 32 знаков после запятой. Однако для локальных переменных в оперативной памяти такого жесткого лимита нет. Платформа динамически наращивает разрядность мантиссы, которая может достигать сотен знаков.
  3. Скрытый «хвост» дроби: При выводе числа в окно сообщений или табло отладчика платформа не показывает всю мантиссу целиком, усекая отображение. Из-за этого переменная визуально выглядит как компактное число, хотя в памяти содержит сотни знаков после запятой.

Рассмотрим механизм каскадного роста разрядности

Главный источник проблем кроется в циклах накопительного суммирования. Посмотрим на типовой фрагмент алгоритма:


// Внутри цикла выполняется многократное накопление
оПр = оПр + дан[инд].Пр / ДайПл(дан[инд].Индекс);

Каждое деление в цикле может порождать непериодическую или длинную периодическую дробь. При последующих операциях сложения и деления разрядность числа не нормализуется автоматически, а накапливается с каждым шагом цикла. В результате в переменных оПр и вПр формируются сверхдлинные мантиссы.

Алгоритмы программного поразрядного деления длинных чисел имеют квадратичную вычислительную сложность порядка O(N²) относительно длины мантиссы. Когда в операциях участвуют операнды длиной в сотни разрядов, время работы одного оператора деления лавинообразно увеличивается от долей миллисекунды до десятков секунд.

Способ 1. Принудительное округление внутри накопительного цикла

Самое эффективное решение — предотвратить накопление разрядности в самом источнике, то есть внутри тела цикла. Округление в финальной формуле помогает от зависания итогового деления, но сам цикл к этому моменту уже может работать медленно.

Рассмотрим правильный подход к организации накопительных расчетов:


// Неправильно: разрядность неконтролируемо растет с каждым шагом
Для Каждого Стр Из ТаблицаДанных Цикл
    оПр = оПр + Стр.Значение / Знаменатель;
КонецЦикла;

// Правильно: ограничение разрядности на каждой итерации
Для Каждого Стр Из ТаблицаДанных Цикл
    оПр = Окр(оПр + Стр.Значение / Знаменатель, 8);
КонецЦикла;

Функция Окр() принудительно отсекает мантиссу, возвращая длину числа к разумным пределам, благодаря чему сложность последующих операций не возрастает.

Способ 2. Использование явной типизации через КвалификаторыЧисла

Если промежуточные данные сохраняются в коллекциях, можно доверить контроль точности самой платформе. Для этого используют типизированные колонки объекта ТаблицаЗначений с заданным объектом КвалификаторыЧисла.

Разберем пример создания таблицы с фиксированной точностью:


МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("Число"));

// Задаем общую длину 15 знаков и точность 4 знака после запятой
Квалификатор = Новый КвалификаторыЧисла(15, 4);
ОписаниеЧисла = Новый ОписаниеТипов(МассивТипов, , , Квалификатор);

ТаблицаРасчета = Новый ТаблицаЗначений;
ТаблицаРасчета.Колонки.Добавить("оПр", ОписаниеЧисла);
ТаблицаРасчета.Колонки.Добавить("вПр", ОписаниеЧисла);

НоваяСтрока = ТаблицаРасчета.Добавить();
// При присвоении платформа автоматически нормализует разрядность числа
НоваяСтрока.оПр = ПромежуточноеВычисление;

При записи значения в такую колонку платформа автоматически обрезает избыточную разрядность мантиссы на системном уровне, исключая эффект раздувания чисел в оперативной памяти.

Способ 3. Алгебраическая перегруппировка операций

Посмотрим на структуру расчетных выражений. Если в формуле присутствует цепочка последовательных делений и умножений, операцию деления рекомендуется выносить на самый поздний этап вычислений.

  1. Накапливайте числитель и знаменатель раздельно в виде произведений или сумм.
  2. Умножение целых чисел или коротких дробей увеличивает длину числа значительно медленнее, чем операция деления, порождающая бесконечные периодические хвосты.
  3. Финальное деление числителя на знаменатель производите однократно в самом конце алгоритма с обязательным округлением результата.

Способ 4. Вынос объемных численных методов во внешние модули

Если перед вами стоит задача реализации сложных математических методов (численная оптимизация, решение дифференциальных уравнений, расчет нейросетей, многомерная интерполяция), возможностей встроенного языка 1С может быть недостаточно по скорости даже при оптимизации мантиссы.

В таких сценариях применяются специализированные архитектурные решения:

  1. Внешние компоненты (Native API): Создание библиотеки на C, C++ или Rust. Внутри компоненты расчеты выполняются на аппаратном уровне процессора с использованием 64-битных типов двойной точности. Платформа 1С передает исходные параметры массивом и получает обратно готовый результат за миллисекунды.
  2. Микросервисы: Передача пакета исходных данных через HTTP-сервис в микросервис (например, на Python с библиотеками NumPy/SciPy или Go), специально оптимизированный под векторные и матричные вычисления.

Таким образом, медленный расчет математических формул в 1С — это не сбой платформы, а прямое следствие работы алгоритмов длинной десятичной арифметики. Контролируя разрядность промежуточных операндов через вызовы Окр() или типизированные структуры, можно полностью исключить деградацию производительности вычислений.

← На главную