Как обрезать число в запросе 1С без математического округления?

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

Часто при разработке отчетов, печатных форм или сложных алгоритмов проведения документов мы сталкиваемся с необходимостью получить точное значение с заданным количеством знаков после запятой. Стандартный оператор языка запросов ВЫРАЗИТЬ работает по строгим правилам математического округления. Рассмотрим классическую ситуацию: в результате вычисления скидки или выделения НДС получается число с длинной дробной частью, например 3.67500000540833. Если мы применим к нему приведение типов ВЫРАЗИТЬ(Значение КАК ЧИСЛО(15,2)), система округлит его до 3.68. Но что делать, если алгоритм требует просто отбросить (обрезать) лишнюю дробную часть и получить ровно 3.67?

Разберем по шагам, почему возникает эта проблема, как СУБД работает с дробями и какие существуют надежные способы решения этой задачи непосредственно в тексте запроса.

Причины появления "плавающих" погрешностей

Проанализируем ситуацию глубже. При выполнении арифметических операций в запросах к базе данных (особенно операций деления) неизбежно возникают микропогрешности. Деление на числа вроде 1.2 (что является стандартной операцией для выделения НДС 20%) на уровне машинной логики порождает бесконечные периодические дроби.

На уровне системы управления базами данных (будь то PostgreSQL, MS SQL Server или другие) промежуточный результат вычисления может храниться не как точное десятичное число, а с мельчайшим отклонением на дальних разрядах. Например, вместо ожидаемого значения 8.565000 в оперативной памяти может оказаться 8.564999999999 или 8.565000000001.

Когда мы применяем оператор ВЫРАЗИТЬ, система округляет это "неидеальное" число математически. Число с окончанием 4999 округлится в меньшую сторону, а с окончанием 5001 — в большую. Именно поэтому разработчики часто замечают, что при одинаковых условиях одни числа округляются предсказуемо, а другие — с необъяснимыми на первый взгляд отклонениями. Выясним, как обойти это ограничение, ведь встроенной функции вроде Truncate или Floor для дробных чисел в языке запросов 1С не предусмотрено.

Решение 1: Метод вычитания погрешности (сдвиг разряда)

Рассмотрим первый математический прием, который позволяет "обмануть" платформенное округление. Суть метода заключается в том, чтобы перед применением оператора ВЫРАЗИТЬ искусственно уменьшить наше значение на крошечную величину. Это не позволит пограничному числу округлиться в большую сторону.

Чтобы обрезать значение ровно до двух знаков после запятой, мы должны вычесть половину от требуемой точности следующего (третьего) разряда, то есть 0.005. Посмотрим на пример реализации данного метода в коде:


ВЫБРАТЬ
    ВЫРАЗИТЬ((РеализацияТоваровУслугТовары.Цена * РеализацияТоваровУслугТовары.ПроцентАвтоматическойСкидки / 100 / 1.2) - 0.005 КАК ЧИСЛО(15, 2)) КАК ОбрезанноеЗначение
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары

Если бы задача стояла оставить три знака после запятой, мы бы отнимали 0.0005, для одного знака — 0.05 и так далее. Однако у этого подхода есть существенный недостаток. Метод вычитания корректно работает исключительно для положительных чисел. Если в результате вычислений может получиться отрицательное число (например, при расчете отклонений или сторно), вычитание 0.005 только увеличит его по модулю (сделает "более отрицательным"), и результат отсечения окажется ошибочным.

Если в вашей выборке допускаются отрицательные значения, конструкцию придется усложнять условием ВЫБОР КОГДА ... ТОГДА ... ИНАЧЕ, прибавляя 0.005 для чисел меньше нуля.

Решение 2: Использование функции ЦЕЛ (Рекомендуемый метод)

Разберем более изящный и универсальный способ, который полностью исключает влияние микропогрешностей СУБД и не требует написания громоздких условий для работы с отрицательными числами. В языке запросов 1С присутствует встроенная математическая функция ЦЕЛ, которая возвращает целую часть числа, отсекая любую дробную.

Чтобы применить ее к нашим дробным значениям, мы воспользуемся простым масштабированием числа. Алгоритм решения выглядит следующим образом:

  1. Умножаем исходную формулу на 10 в степени количества необходимых знаков (для сохранения двух знаков — умножаем на 100).
  2. Применяем функцию ЦЕЛ ко всему выражению, отсекая ненужный "хвост".
  3. Делим полученный результат обратно на 100, возвращая число к исходному масштабу.

Посмотрим на пример использования этого метода в запросе:


ВЫБРАТЬ
    ЦЕЛ((РеализацияТоваровУслугТовары.Цена * РеализацияТоваровУслугТовары.ПроцентАвтоматическойСкидки / 100 / 1.2) * 100) / 100 КАК РезультатБезОкругления
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары

Проанализируем, как это сработает. Наше проблемное число 3.67500000540833 после умножения на 100 превратится в 367.500000540833. Функция ЦЕЛ отбросит всю дробную часть, оставив ровно 367. После обратного деления на 100 мы получим идеальные 3.67 без какого-либо математического округления вверх.

Данный подход является наиболее отказоустойчивым. Он работает стабильно вне зависимости от типа используемой СУБД (PostgreSQL, MS SQL, файловая база) и является "золотым стандартом" для решения задач по отсечению дробной части числа непосредственно в коде запросов 1С.

← На главную