Почему в запросах 1С возникают ошибки при расчете процентов и как работает неявное преобразование типов?

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

При работе с платформой 1С:Предприятие 8.3 разработчики иногда сталкиваются с аномальным поведением системы, которое трудно объяснить логикой обычного программирования. Одной из таких проблем является ситуация, когда точные числовые данные (например, суммы в валюте) при арифметических операциях в запросах внезапно дают результат с «хвостом» из множества знаков после запятой. В этой статье мы подробно разберем, почему в закрытых заказах процент оплаты может отображаться как 99.9999%, как на это влияет платформа и как правильно обрабатывать такие ситуации.

Суть проблемы: когда 100% превращаются в 99.99%

Рассмотрим ситуацию, возникшую в типовых конфигурациях (например, УТ 11.4) после обновления платформы до релиза 8.3.15.1489. В списке заказов клиентов пользователи заметили, что полностью отгруженные и оплаченные заказы отображают статус готовности 99% вместо положенных 100%. При анализе запроса выяснилось, что при делении двух абсолютно одинаковых сумм результат не равен единице.

Проанализируем упрощенный фрагмент запроса, который вызывает данную ошибку:


ВЫБРАТЬ
    ЗаказыОстатки.ЗаказКлиента КАК ЗаказКлиента,
    СУММА(ЗаказыОстатки.СуммаРасход) КАК СуммаРасход
ПОМЕСТИТЬ ВТ
ИЗ
    РегистрНакопления.ЗаказыКлиентов.ОстаткиИОбороты(&Начало, &Конец, , , ЗаказКлиента = &Заказ) КАК ЗаказыОстатки
СГРУППИРОВАТЬ ПО
    ЗаказыОстатки.ЗаказКлиента;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ.СуммаРасход * 100 / Документ.СуммаДокумента КАК ПроцентОтгрузки
ИЗ
    Документ.ЗаказКлиента КАК Документ
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ
        ПО Документ.Ссылка = ВТ.ЗаказКлиента
ГДЕ
    Документ.Ссылка = &Заказ

Ситуация выглядит следующим образом: ВТ.СуммаРасход равна 10840.00, и Документ.СуммаДокумента также равна 10840.00. Однако результат вычисления 10840.00 * 100 / 10840.00 внезапно оказывается равен 99.9999756. Это классический пример накопления погрешности при неявном преобразовании типов.

Разберем причину: Ошибка платформы 10212223

Выясним причину такого поведения. Проблема кроется в механизме генерации SQL-запроса платформой 1С. В определенных версиях (ветка 8.3.15) был изменен алгоритм трансляции арифметических выражений. Вместо использования точных типов данных NUMERIC или DECIMAL, платформа начала неявно приводить операнды к типу FLOAT (число с плавающей точкой) или DOUBLE PRECISION на стороне СУБД (MSSQL, PostgreSQL).

Тип FLOAT — это приближенный тип данных. В отличие от NUMERIC(15,2), который хранит числа именно так, как они записаны, FLOAT хранит двоичное приближение числа. При преобразовании из точного десятичного формата в двоичный «плавающий» формат возникают микроскопические отклонения. В нашем примере число 100 или результат деления может превратиться в 99.9999999999997, что при последующем выводе или округлении дает видимую ошибку.

Особенно часто это проявляется при использовании:

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

Метод решения №1: Принудительное типизирование через ВЫРАЗИТЬ

Наиболее надежный способ решения проблемы «в коде» — это использование функции ВЫРАЗИТЬ(). Рассмотрим, как изменить запрос, чтобы избежать влияния FLOAT. Нам необходимо явно указать платформе, что результат вычисления или сам операнд должен оставаться в формате точного числа с фиксированной точкой.

Проанализируем исправленный вариант вычисления процента:


ВЫБРАТЬ
    ВЫРАЗИТЬ(ВТ.СуммаРасход * 100 / Документ.СуммаДокумента КАК ЧИСЛО(15, 2)) КАК ПроцентОтгрузки
ИЗ
    Документ.ЗаказКлиента КАК Документ
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ КАК ВТ
        ПО Документ.Ссылка = ВТ.ЗаказКлиента

Использование ВЫРАЗИТЬ( ... КАК ЧИСЛО(15,2)) заставляет SQL-сервер выполнить обратное приведение (CAST) из «плавающего» FLOAT в точный NUMERIC. Это отсекает микроскопическую погрешность и возвращает ожидаемое значение 100.00.

Метод решения №2: Изменение порядка арифметических операций

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

Посмотрим на пример:


// Вместо Сумма * 100 / Всего
// Попробуем:
(Сумма / Всего) * 100 

В некоторых случаях это помогает, так как результат деления 10840 / 10840 дает чистую единицу еще до того, как погрешность FLOAT успеет накопиться при умножении. Однако этот метод менее надежен, чем явное использование ВЫРАЗИТЬ.

Метод решения №3: Обновление технологической платформы

Поскольку данная проблема официально признана ошибкой платформы (номер 10212223), самым правильным стратегическим решением будет обновление до исправленного релиза. Рассмотрим список версий, где данное поведение было исправлено:

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

Последствия игнорирования проблемы

Важно понимать, что «кривые» проценты в интерфейсе — это лишь верхушка айсберга. Неявное приведение к FLOAT может привести к более серьезным последствиям в логике учета:

  1. Ошибки в условиях ГДЕ: Условие ГДЕ СуммаОстаток = 0 может не сработать, если в базе из-за ошибки вычислений хранится 0.0000000004. Заказы не будут закрываться, а отчеты по остаткам будут содержать «мусор».
  2. Проблемы с итогами: При расчете итогов в СКД или в подвалах документов суммы могут расходиться на копейки в самых неожиданных местах.
  3. Округление: Обычное математическое округление работает некорректно с числами типа FLOAT, что может привести к систематическим ошибкам в бухгалтерских и налоговых отчетах.

Резюме и рекомендации

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

  1. Проверяйте релиз платформы: Избегайте использования заведомо проблемных релизов, таких как 8.3.15.1489.
  2. Используйте типизацию в запросах: При выполнении деления или умножения в сложных запросах (особенно с использованием временных таблиц), всегда оборачивайте итоговый результат в ВЫРАЗИТЬ(... КАК ЧИСЛО(15,2)).
  3. Анализируйте SQL-трассировку: Если вы видите необъяснимые цифры, воспользуйтесь SQL Profiler. Если в тексте запроса вы видите тип float или real там, где должны быть денежные суммы — это явный признак обсуждаемой проблемы.

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

← На главную