Часто при разработке отчетов или обработок в 1С возникает задача выполнить арифметические операции, в частности деление, прямо в тексте запроса. Чтобы быстро ориентироваться в правилах написания кода, полезно использовать шпаргалку по языку запросов. И здесь можно столкнуться с неожиданной проблемой: результат деления, особенно при использовании агрегатных функций вроде СУММА(), оказывается округленным до определенного количества знаков после запятой, чаще всего до шести. Попытка увеличить точность, применив оператор ВЫРАЗИТЬ ко всему выражению, не дает эффекта. Разберем, почему так происходит и как правильно управлять точностью вычислений в запросах 1С.
Ключ к пониманию этого поведения лежит в том, как платформа 1С и система управления базами данных (СУБД) определяют точность результата арифметической операции. Точность результата напрямую зависит от точности исходных данных — операндов. Проверить это поведение на практике и отладить вычисления поможет консоль запросов для управляемых форм.
Рассмотрим исходную проблему:
СУММА(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток) / СУММА(ЦенныеБумагиУОстаткиИОбороты.КоличествоКонечныйОстаток)
В этом выражении происходит несколько шагов:
СУММА(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток).СУММА(ЦенныеБумагиУОстаткиИОбороты.КоличествоКонечныйОстаток).Согласно документации 1С (ИТС), для функции СУММА "количество цифр дробной части результата равно количеству цифр дробной части операнда". Если у поля СуммаКонечныйОстаток в конфигураторе задана точность, например, 2 знака, то и у результата функции СУММА() будет 2 знака. То же самое касается и количества.
Далее происходит деление. Здесь в игру вступает СУБД (например, Microsoft SQL Server), которая при делении чисел с невысокой точностью по умолчанию может возвращать результат с точностью до 6 знаков после запятой. Именно поэтому автор и получил "шесть знаков после запятой". Подобные нюансы часто обсуждаются, когда требуются, например, точные значения единиц измерения для различных расчетов.
Попытка "исправить" ситуацию, обернув всё выражение в ВЫРАЗИТЬ, не работает:
// НЕПРАВИЛЬНЫЙ вариант, который не сработает
ВЫРАЗИТЬ(
СУММА(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток) / СУММА(ЦенныеБумагиУОстаткиИОбороты.КоличествоКонечныйОстаток)
КАК ЧИСЛО(32, 10)
)
Оператор ВЫРАЗИТЬ в данном случае применяется к уже вычисленному и округленному результату. Он может добавить нули к имеющимся шести знакам, но не может восстановить потерянную точность. Чтобы получить точный результат, нужно работать с операндами до того, как произойдет "проблемная" операция. В сложных отчетах, где используются временные таблицы, важно помнить, что начиная с версии 8.3.25 стало возможным добавление данных в существующую временную таблицу, что также может влиять на структуру и типы данных в пакете.
Это самый корректный и предпочтительный способ решения задачи. Идея состоит в том, чтобы явно указать системе, что один из операндов (или оба) должен рассматриваться как число с высокой точностью до выполнения агрегации и деления. Для быстрой отладки подобных конструкций отлично подойдет модифицированная консоль запросов УФ.
Для этого оператор ВЫРАЗИТЬ нужно применить к полю внутри агрегатной функции. Чаще всего достаточно увеличить точность числителя.
Посмотрим на пример правильного кода:
ВЫБРАТЬ
СУММА(ВЫРАЗИТЬ(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток КАК ЧИСЛО(32, 10)))
/ СУММА(ЦенныеБумагиУОстаткиИОбороты.КоличествоКонечныйОстаток) КАК СредняяЦена
ИЗ
РегистрНакопления.ЦенныеБумагиУОстаткиИОбороты КАК ЦенныеБумагиУОстаткиИОбороты
Разберем по шагам, что здесь происходит:
ВЫРАЗИТЬ(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток КАК ЧИСЛО(32, 10)): на этом шаге мы "сообщаем" системе запросов, что каждое значение поля СуммаКонечныйОстаток нужно рассматривать не с его стандартной точностью из метаданных, а как число, у которого может быть до 10 знаков после запятой.СУММА(...): теперь агрегатная функция применяется к данным с уже заданной высокой точностью. Согласно правилам, результат суммы также будет иметь точность 10 знаков в дробной части.... / СУММА(...): операция деления выполняется над числителем, который уже имеет высокую точность. В результате СУБД и платформа 1С сохраняют эту точность в итоговом значении.Этот метод позволяет полностью управлять точностью вычислений, не выходя за рамки языка запросов, и является наиболее универсальным решением.
Иногда, по разным причинам, модифицировать запрос нежелательно или сложно. Как и в случае с определением длины строки в запросе, когда приходится прибегать к ухищрениям, здесь можно использовать обходные маневры, перенеся часть вычислений из запроса в код 1С.
Это самый простой и надежный "обходной" метод. В запросе мы получаем только агрегированные суммы, а саму операцию деления выполняем уже в коде на языке 1С, где точность вычислений максимальна.
Шаг 1. Модифицируем запрос:
ВЫБРАТЬ
СУММА(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток) КАК СуммаОстаток,
СУММА(ЦенныеБумагиУОстаткиИОбороты.КоличествоКонечныйОстаток) КАК КоличествоОстаток
ИЗ
РегистрНакопления.ЦенныеБумагиУОстаткиИОбороты КАК ЦенныеБумагиУОстаткиИОбороты
Шаг 2. Выполняем деление в коде:
Запрос = Новый Запрос;
Запрос.Текст = "Текст запроса выше";
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
СредняяЦена = 0;
Если Выборка.Следующий() Тогда
Если Выборка.КоличествоОстаток <> 0 Тогда
СредняяЦена = Выборка.СуммаОстаток / Выборка.КоличествоОстаток;
КонецЕсли;
КонецЕсли;
// Переменная СредняяЦена будет содержать результат с максимальной точностью
Сообщить(СредняяЦена);
Этот метод является "гибридным". Чтобы сохранить знаки после запятой, мы искусственно сдвигаем их в целую часть путем умножения на большое число (например, 1 000 000 000, т.е. 10 в степени желаемой точности). Затем в коде делим обратно.
Шаг 1. Запрос с умножением:
ВЫБРАТЬ
СУММА(ЦенныеБумагиУОстаткиИОбороты.СуммаКонечныйОстаток) * 10000000000 / СУММА(ЦенныеБумагиУОстаткиИОбороты.КоличествоКонечныйОстаток) КАК СредняяЦенаУвеличенная
ИЗ
РегистрНакопления.ЦенныеБумагиУОстаткиИОбороты КАК ЦенныеБумагиУОстаткиИОбороты
Шаг 2. Обратное деление в коде:
Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
СредняяЦена = Выборка.СредняяЦенаУвеличенная / 10000000000;
КонецЕсли;
Этот вариант может быть полезен в сложных запросах, но вариант А (полное разделение в коде) обычно более читаем и прост в отладке.
Новички часто путают оператор запроса ВЫРАЗИТЬ и функцию встроенного языка Формат(). Важно понимать их принципиальное различие:
<strong>ВЫРАЗИТЬ(... КАК ЧИСЛО(N, M))</strong> — это операция, которая меняет тип данных и его внутреннее представление. Она влияет на то, как число будет участвовать в дальнейших вычислениях.<strong>Формат(Число, "ЧДЦ=10")</strong> — это функция, которая преобразует уже готовое число в его строковое представление для вывода на экран или в отчет. Она не меняет само число, а лишь форматирует его для отображения.Если ваша задача — просто красиво вывести число с 10 нулями после запятой, используйте Формат() после получения данных из запроса. Если же вам нужно, чтобы эти 10 знаков участвовали в дальнейших расчетах внутри запроса, ваш выбор — ВЫРАЗИТЬ.