При выполнении математического моделирования, циклических численных алгоритмов или сложных формул во встроенном языке 1С разработчики иногда сталкиваются с неожиданной ситуацией: элементарная строка вычисления дроби выполняется 20 и более секунд. При этом замер производительности показывает, что время уходит не на обращения к базе данных или серверные вызовы, а непосредственно на оператор деления или умножения. Разберем по шагам, почему возникает эта проблема, как устроена математика платформы 1С и как оптимизировать код для мгновенного выполнения расчетов.
Проанализируем ситуацию на классическом примере из практики. В коде выполняется расчет выражения:
строкаР.Параметры.Плт = Формат(377.1562 * оПр / вПр - 377.836, "ЧДЦ=1; ЧГ=");
На первый взгляд, это обычная линейная формула. Однако выполнение строчки занимает десятки секунд. Если вынести выражение оПр / вПр в табло отладчика, вычисление также зависает. При этом добавление явного округления Окр(оПр, 10) / Окр(вПр, 10) решает проблему, и расчет начинает выполняться мгновенно.
Многие разработчики ошибочно полагают, что числовые переменные в оперативной памяти 1С соответствуют стандартным типам с плавающей точкой (таким как float или double стандарта IEEE 754). В реальности это не так. В финансовых системах вычисления с плавающей точкой неприменимы из-за неизбежной погрешности округления в младших разрядах.
Рассмотрим ключевые особенности работы типа Число в платформе 1С:
Число в 1С концептуально близок к классу BigDecimal в Java. Платформа оперирует десятичными числами программным путем, не полагаясь на быстрые процессорные инструкции FPU.Число установлено жесткое ограничение: максимум 38 знаков общей длины и не более 32 знаков после запятой. Однако для локальных переменных в оперативной памяти такого жесткого лимита нет. Платформа динамически наращивает разрядность мантиссы, которая может достигать сотен знаков.Главный источник проблем кроется в циклах накопительного суммирования. Посмотрим на типовой фрагмент алгоритма:
// Внутри цикла выполняется многократное накопление
оПр = оПр + дан[инд].Пр / ДайПл(дан[инд].Индекс);
Каждое деление в цикле может порождать непериодическую или длинную периодическую дробь. При последующих операциях сложения и деления разрядность числа не нормализуется автоматически, а накапливается с каждым шагом цикла. В результате в переменных оПр и вПр формируются сверхдлинные мантиссы.
Алгоритмы программного поразрядного деления длинных чисел имеют квадратичную вычислительную сложность порядка O(N²) относительно длины мантиссы. Когда в операциях участвуют операнды длиной в сотни разрядов, время работы одного оператора деления лавинообразно увеличивается от долей миллисекунды до десятков секунд.
Самое эффективное решение — предотвратить накопление разрядности в самом источнике, то есть внутри тела цикла. Округление в финальной формуле помогает от зависания итогового деления, но сам цикл к этому моменту уже может работать медленно.
Рассмотрим правильный подход к организации накопительных расчетов:
// Неправильно: разрядность неконтролируемо растет с каждым шагом
Для Каждого Стр Из ТаблицаДанных Цикл
оПр = оПр + Стр.Значение / Знаменатель;
КонецЦикла;
// Правильно: ограничение разрядности на каждой итерации
Для Каждого Стр Из ТаблицаДанных Цикл
оПр = Окр(оПр + Стр.Значение / Знаменатель, 8);
КонецЦикла;
Функция Окр() принудительно отсекает мантиссу, возвращая длину числа к разумным пределам, благодаря чему сложность последующих операций не возрастает.
Если промежуточные данные сохраняются в коллекциях, можно доверить контроль точности самой платформе. Для этого используют типизированные колонки объекта ТаблицаЗначений с заданным объектом КвалификаторыЧисла.
Разберем пример создания таблицы с фиксированной точностью:
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("Число"));
// Задаем общую длину 15 знаков и точность 4 знака после запятой
Квалификатор = Новый КвалификаторыЧисла(15, 4);
ОписаниеЧисла = Новый ОписаниеТипов(МассивТипов, , , Квалификатор);
ТаблицаРасчета = Новый ТаблицаЗначений;
ТаблицаРасчета.Колонки.Добавить("оПр", ОписаниеЧисла);
ТаблицаРасчета.Колонки.Добавить("вПр", ОписаниеЧисла);
НоваяСтрока = ТаблицаРасчета.Добавить();
// При присвоении платформа автоматически нормализует разрядность числа
НоваяСтрока.оПр = ПромежуточноеВычисление;
При записи значения в такую колонку платформа автоматически обрезает избыточную разрядность мантиссы на системном уровне, исключая эффект раздувания чисел в оперативной памяти.
Посмотрим на структуру расчетных выражений. Если в формуле присутствует цепочка последовательных делений и умножений, операцию деления рекомендуется выносить на самый поздний этап вычислений.
Если перед вами стоит задача реализации сложных математических методов (численная оптимизация, решение дифференциальных уравнений, расчет нейросетей, многомерная интерполяция), возможностей встроенного языка 1С может быть недостаточно по скорости даже при оптимизации мантиссы.
В таких сценариях применяются специализированные архитектурные решения:
Таким образом, медленный расчет математических формул в 1С — это не сбой платформы, а прямое следствие работы алгоритмов длинной десятичной арифметики. Контролируя разрядность промежуточных операндов через вызовы Окр() или типизированные структуры, можно полностью исключить деградацию производительности вычислений.