Каждый разработчик 1С рано или поздно сталкивается с нетипичным поведением отладчика: переменная имеет значение 0, однако логическое выражение Переменная = 0 неожиданно возвращает Ложь. В результате условия в операторах Если не срабатывают, а строки таблицы значений, которые должны были удалиться при обнулении остатка, остаются нетронутыми.
Рассмотрим подробно, почему возникают такие ситуации, как устроен механизм хранения чисел в встроенном языке 1С и как правильно обрабатывать подобные вычисления в коде.
Проанализируем механику работы с числами во встроенном языке 1С. В отличие от реквизитов объектов метаданных (документов или справочников), где точность строго фиксирована конфигурацией (например, Число(15, 2)), обычные локальные переменные и колонки программно созданных таблиц значений могут хранить числа с динамической дробной частью высокой точности.
При выполнении арифметических операций (умножение на коэффициент, расчёт доли НДС, деление) результат приобретает длинный «хвост» из десятичных знаков. В процессе последовательных вычитаний в цикле эти микроскопические остатки накапливаются.
Главная причина путаницы заключается в том, что отладчик 1С при выводе значений в табло форматирует числа для удобства восприятия и отсекает длинную дробную часть. В окне отладчика мы видим ровно 0, однако в оперативной памяти платформы сохраняется число вида 0.0000000000000001 или -0.000000000000001. Естественно, с точки зрения платформы такое число не равно точной константе 0.
Разберем ситуацию на примере алгоритма распределения оплат по документам поступления:
Пока Оплаты.Количество() > 0 Цикл
ТекОплата = Оплаты.Получить(0);
ТекОплатаНДС = ТекОплата.Сумма * КоэфНДС;
ТекОплатаОсн = ТекОплата.Сумма - ТекОплатаНДС;
СуммаЗакрытияНДС = Мин(НеоплаченнаяСуммаНДС, ТекОплатаНДС);
НеоплаченнаяСуммаНДС = НеоплаченнаяСуммаНДС - СуммаЗакрытияНДС;
ТекОплата.Сумма = ТекОплата.Сумма - СуммаЗакрытияНДС;
// Ошибка: ТекОплата.Сумма может содержать невидимый остаток 0.000000000000001
Если ТекОплата.Сумма = 0 Тогда
Оплаты.Удалить(ТекОплата);
Продолжить;
КонецЕсли;
Стр = ОбщаяТаблица.Добавить();
СуммаЗакрытия = Мин(НеоплаченнаяСумма, ТекОплатаОсн);
НеоплаченнаяСумма = НеоплаченнаяСумма - СуммаЗакрытия;
ТекОплата.Сумма = ТекОплата.Сумма - СуммаЗакрытия;
Стр.ДокументПоступления = Пост.Документ;
Стр.ДокументОплаты = ТекОплата.Документ;
Стр.Сумма = СуммаЗакрытия;
Если НеоплаченнаяСумма = 0 Тогда
Прервать;
КонецЕсли;
Если ТекОплата.Сумма = 0 Тогда
Оплаты.Удалить(ТекОплата);
Продолжить;
КонецЕсли;
КонецЦикла;
В приведенном коде умножение на КоэфНДС приводит к тому, что ТекОплата.Сумма теряет точное представление в двух знаках после запятой. На следующих итерациях сравнение с нулем перестает работать, и цикл уходит в бесконечную обработку или некорректное распределение сумм.
Разберем по шагам несколько надежных подходов к устранению этой ошибки.
Самый эффективный и правильный способ для финансовых расчетов — округлять промежуточные и итоговые значения сразу при проведении арифметических операций. Используем функцию Окр() с указанием нужного количества знаков (как правило, 2 знака для денежных сумм):
ТекОплатаНДС = Окр(ТекОплата.Сумма * КоэфНДС, 2);
ТекОплатаОсн = ТекОплата.Сумма - ТекОплатаНДС;
СуммаЗакрытияНДС = Мин(НеоплаченнаяСуммаНДС, ТекОплатаНДС);
НеоплаченнаяСуммаНДС = Окр(НеоплаченнаяСуммаНДС - СуммаЗакрытияНДС, 2);
ТекОплата.Сумма = Окр(ТекОплата.Сумма - СуммаЗакрытияНДС, 2);
Если ТекОплата.Сумма = 0 Тогда
Оплаты.Удалить(ТекОплата);
Продолжить;
КонецЕсли;
Если промежуточные высокоточные данные необходимы для дальнейших математических алгоритмов, можно округлять значение непосредственно в момент проверки условия равенства нулю:
Если Окр(ТекОплата.Сумма, 2) = 0 Тогда
Оплаты.Удалить(ТекОплата);
Продолжить;
КонецЕсли;
В случаях, когда точность округления заранее неизвестна, применяют проверку вхождения значения в микроскопический диапазон около нуля:
Если ТекОплата.Сумма > -0.0001 И ТекОплата.Сумма < 0.0001 Тогда
ТекОплата.Сумма = 0;
Оплаты.Удалить(ТекОплата);
Продолжить;
КонецЕсли;
Если данные хранятся в объекте ТаблицаЗначений, рекомендуется явно задавать квалификаторы числа при создании колонок. В этом случае платформа будет автоматически ограничивать точность при записи данных в таблицу:
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("Число"));
КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 2);
ОписаниеТипаЧисло = Новый ОписаниеТипов(МассивТипов, , КвалификаторыЧисла);
ТаблицаОплат = Новый ТаблицаЗначений;
ТаблицаОплат.Колонки.Добавить("Сумма", ОписаниеТипаЧисло);
Подводя итог: при любых расчетах с финансовыми показателями во встроенном языке 1С обязательно контролируйте точность вычислений и не допускайте накопления дробных хвостов.