При работе с платформой 1С:Предприятие 8.3 разработчики иногда сталкиваются с аномальным поведением системы, которое трудно объяснить логикой обычного программирования. Одной из таких проблем является ситуация, когда точные числовые данные (например, суммы в валюте) при арифметических операциях в запросах внезапно дают результат с «хвостом» из множества знаков после запятой. В этой статье мы подробно разберем, почему в закрытых заказах процент оплаты может отображаться как 99.9999%, как на это влияет платформа и как правильно обрабатывать такие ситуации.
Рассмотрим ситуацию, возникшую в типовых конфигурациях (например, УТ 11.4) после обновления платформы до релиза 8.3.15.1489. В списке заказов клиентов пользователи заметили, что полностью отгруженные и оплаченные заказы отображают статус готовности 99% вместо положенных 100%. При анализе запроса выяснилось, что при делении двух абсолютно одинаковых сумм результат не равен единице.
Проанализируем упрощенный фрагмент запроса, который вызывает данную ошибку:
ВЫБРАТЬ
ЗаказыОстатки.ЗаказКлиента КАК ЗаказКлиента,
СУММА(ЗаказыОстатки.СуммаРасход) КАК СуммаРасход
ПОМЕСТИТЬ ВТ
ИЗ
РегистрНакопления.ЗаказыКлиентов.ОстаткиИОбороты(&Начало, &Конец, , , ЗаказКлиента = &Заказ) КАК ЗаказыОстатки
СГРУППИРОВАТЬ ПО
ЗаказыОстатки.ЗаказКлиента;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ.СуммаРасход * 100 / Документ.СуммаДокумента КАК ПроцентОтгрузки
ИЗ
Документ.ЗаказКлиента КАК Документ
ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ
ПО Документ.Ссылка = ВТ.ЗаказКлиента
ГДЕ
Документ.Ссылка = &Заказ
Ситуация выглядит следующим образом: ВТ.СуммаРасход равна 10840.00, и Документ.СуммаДокумента также равна 10840.00. Однако результат вычисления 10840.00 * 100 / 10840.00 внезапно оказывается равен 99.9999756. Это классический пример накопления погрешности при неявном преобразовании типов.
Выясним причину такого поведения. Проблема кроется в механизме генерации SQL-запроса платформой 1С. В определенных версиях (ветка 8.3.15) был изменен алгоритм трансляции арифметических выражений. Вместо использования точных типов данных NUMERIC или DECIMAL, платформа начала неявно приводить операнды к типу FLOAT (число с плавающей точкой) или DOUBLE PRECISION на стороне СУБД (MSSQL, PostgreSQL).
Тип FLOAT — это приближенный тип данных. В отличие от NUMERIC(15,2), который хранит числа именно так, как они записаны, FLOAT хранит двоичное приближение числа. При преобразовании из точного десятичного формата в двоичный «плавающий» формат возникают микроскопические отклонения. В нашем примере число 100 или результат деления может превратиться в 99.9999999999997, что при последующем выводе или округлении дает видимую ошибку.
Особенно часто это проявляется при использовании:
ОстаткиИОбороты).ВТ), где типы колонок определяются платформой автоматически.Наиболее надежный способ решения проблемы «в коде» — это использование функции ВЫРАЗИТЬ(). Рассмотрим, как изменить запрос, чтобы избежать влияния FLOAT. Нам необходимо явно указать платформе, что результат вычисления или сам операнд должен оставаться в формате точного числа с фиксированной точкой.
Проанализируем исправленный вариант вычисления процента:
ВЫБРАТЬ
ВЫРАЗИТЬ(ВТ.СуммаРасход * 100 / Документ.СуммаДокумента КАК ЧИСЛО(15, 2)) КАК ПроцентОтгрузки
ИЗ
Документ.ЗаказКлиента КАК Документ
ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ
ПО Документ.Ссылка = ВТ.ЗаказКлиента
Использование ВЫРАЗИТЬ( ... КАК ЧИСЛО(15,2)) заставляет SQL-сервер выполнить обратное приведение (CAST) из «плавающего» FLOAT в точный NUMERIC. Это отсекает микроскопическую погрешность и возвращает ожидаемое значение 100.00.
Как ни странно, порядок операций в запросе также имеет значение. Разберем ситуацию: в исходном примере сначала выполнялось умножение на 100, а затем деление. Если мы сначала выполним деление, а затем умножение, платформа может применить иную логику типизации промежуточного результата.
Посмотрим на пример:
// Вместо Сумма * 100 / Всего
// Попробуем:
(Сумма / Всего) * 100
В некоторых случаях это помогает, так как результат деления 10840 / 10840 дает чистую единицу еще до того, как погрешность FLOAT успеет накопиться при умножении. Однако этот метод менее надежен, чем явное использование ВЫРАЗИТЬ.
Поскольку данная проблема официально признана ошибкой платформы (номер 10212223), самым правильным стратегическим решением будет обновление до исправленного релиза. Рассмотрим список версий, где данное поведение было исправлено:
Если вы используете версию 8.3.15.1489, настоятельно рекомендуется запланировать техническое обслуживание для перехода на более стабильный билд.
Важно понимать, что «кривые» проценты в интерфейсе — это лишь верхушка айсберга. Неявное приведение к FLOAT может привести к более серьезным последствиям в логике учета:
ГДЕ СуммаОстаток = 0 может не сработать, если в базе из-за ошибки вычислений хранится 0.0000000004. Заказы не будут закрываться, а отчеты по остаткам будут содержать «мусор».FLOAT, что может привести к систематическим ошибкам в бухгалтерских и налоговых отчетах.Подведем итоги нашего исследования. Мы выяснили, что причина странных расчетов кроется в компоненте backbas.dll платформы 1С, которая отвечает за генерацию SQL-кода. Чтобы обезопасить свою систему, придерживайтесь следующих правил:
ВЫРАЗИТЬ(... КАК ЧИСЛО(15,2)).SQL Profiler. Если в тексте запроса вы видите тип float или real там, где должны быть денежные суммы — это явный признак обсуждаемой проблемы.Системный подход к написанию запросов и своевременное обновление платформы позволят вам избежать «магии» в расчетах и обеспечат точность управленческого и бухгалтерского учета.