Почему в 1С число визуально равно нулю, но условие равенства нулю возвращает Ложь?

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

Каждый разработчик 1С рано или поздно сталкивается с нетипичным поведением отладчика: переменная имеет значение 0, однако логическое выражение Переменная = 0 неожиданно возвращает Ложь. В результате условия в операторах Если не срабатывают, а строки таблицы значений, которые должны были удалиться при обнулении остатка, остаются нетронутыми.

Рассмотрим подробно, почему возникают такие ситуации, как устроен механизм хранения чисел в встроенном языке 1С и как правильно обрабатывать подобные вычисления в коде.

Выясним причину: почему 0 не равен 0

Проанализируем механику работы с числами во встроенном языке 1С. В отличие от реквизитов объектов метаданных (документов или справочников), где точность строго фиксирована конфигурацией (например, Число(15, 2)), обычные локальные переменные и колонки программно созданных таблиц значений могут хранить числа с динамической дробной частью высокой точности.

При выполнении арифметических операций (умножение на коэффициент, расчёт доли НДС, деление) результат приобретает длинный «хвост» из десятичных знаков. В процессе последовательных вычитаний в цикле эти микроскопические остатки накапливаются.

Главная причина путаницы заключается в том, что отладчик 1С при выводе значений в табло форматирует числа для удобства восприятия и отсекает длинную дробную часть. В окне отладчика мы видим ровно 0, однако в оперативной памяти платформы сохраняется число вида 0.0000000000000001 или -0.000000000000001. Естественно, с точки зрения платформы такое число не равно точной константе 0.

Посмотрим на пример некорректного кода

Разберем ситуацию на примере алгоритма распределения оплат по документам поступления:


Пока Оплаты.Количество() > 0 Цикл
    ТекОплата = Оплаты.Получить(0);
    ТекОплатаНДС = ТекОплата.Сумма * КоэфНДС;
    ТекОплатаОсн = ТекОплата.Сумма - ТекОплатаНДС;
    
    СуммаЗакрытияНДС = Мин(НеоплаченнаяСуммаНДС, ТекОплатаНДС);
    НеоплаченнаяСуммаНДС = НеоплаченнаяСуммаНДС - СуммаЗакрытияНДС;
    ТекОплата.Сумма = ТекОплата.Сумма - СуммаЗакрытияНДС;
    
    // Ошибка: ТекОплата.Сумма может содержать невидимый остаток 0.000000000000001
    Если ТекОплата.Сумма = 0 Тогда
        Оплаты.Удалить(ТекОплата);
        Продолжить;
    КонецЕсли;
    
    Стр = ОбщаяТаблица.Добавить();
    СуммаЗакрытия = Мин(НеоплаченнаяСумма, ТекОплатаОсн);
    НеоплаченнаяСумма = НеоплаченнаяСумма - СуммаЗакрытия;
    ТекОплата.Сумма = ТекОплата.Сумма - СуммаЗакрытия;
    
    Стр.ДокументПоступления = Пост.Документ;
    Стр.ДокументОплаты = ТекОплата.Документ;
    Стр.Сумма = СуммаЗакрытия;
    
    Если НеоплаченнаяСумма = 0 Тогда
        Прервать;
    КонецЕсли;
    
    Если ТекОплата.Сумма = 0 Тогда
        Оплаты.Удалить(ТекОплата);
        Продолжить;
    КонецЕсли;
КонецЦикла;

В приведенном коде умножение на КоэфНДС приводит к тому, что ТекОплата.Сумма теряет точное представление в двух знаках после запятой. На следующих итерациях сравнение с нулем перестает работать, и цикл уходит в бесконечную обработку или некорректное распределение сумм.

Способы решения проблемы

Разберем по шагам несколько надежных подходов к устранению этой ошибки.

Способ 1. Нормализация вычислений с помощью функции Окр()

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


ТекОплатаНДС = Окр(ТекОплата.Сумма * КоэфНДС, 2);
ТекОплатаОсн = ТекОплата.Сумма - ТекОплатаНДС;

СуммаЗакрытияНДС = Мин(НеоплаченнаяСуммаНДС, ТекОплатаНДС);
НеоплаченнаяСуммаНДС = Окр(НеоплаченнаяСуммаНДС - СуммаЗакрытияНДС, 2);
ТекОплата.Сумма = Окр(ТекОплата.Сумма - СуммаЗакрытияНДС, 2);

Если ТекОплата.Сумма = 0 Тогда
    Оплаты.Удалить(ТекОплата);
    Продолжить;
КонецЕсли;

Способ 2. Округление в самом условии проверки

Если промежуточные высокоточные данные необходимы для дальнейших математических алгоритмов, можно округлять значение непосредственно в момент проверки условия равенства нулю:


Если Окр(ТекОплата.Сумма, 2) = 0 Тогда
    Оплаты.Удалить(ТекОплата);
    Продолжить;
КонецЕсли;

Способ 3. Проверка попадания в допустимый интервал (дельта)

В случаях, когда точность округления заранее неизвестна, применяют проверку вхождения значения в микроскопический диапазон около нуля:


Если ТекОплата.Сумма > -0.0001 И ТекОплата.Сумма < 0.0001 Тогда
    ТекОплата.Сумма = 0;
    Оплаты.Удалить(ТекОплата);
    Продолжить;
КонецЕсли;

Способ 4. Явное задание типа колонки таблицы значений

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


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

КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 2);
ОписаниеТипаЧисло = Новый ОписаниеТипов(МассивТипов, , КвалификаторыЧисла);

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

Подводя итог: при любых расчетах с финансовыми показателями во встроенном языке 1С обязательно контролируйте точность вычислений и не допускайте накопления дробных хвостов.

← На главную