В процессе разработки в среде 1С:Предприятие программисты регулярно сталкиваются с необходимостью округления числовых значений. Будь то расчет стоимости товаров с наценкой, калькуляция налогов или определение необходимого количества упаковок для отгрузки — стандартных механизмов платформы не всегда хватает.
Классическая встроенная функция 1С Окр() выполняет округление по общепринятым арифметическим правилам (к ближайшему целому). Но что делать, если бизнес-логика требует округлить значение строго в большую сторону (математическая функция Ceil — "потолок") или строго в меньшую сторону (математическая функция Floor — "пол")? В этой статье мы подробно разберем, почему стандартные средства имеют ограничения, и рассмотрим все доступные способы реализации строгого округления в 1С.
Для начала давайте проанализируем синтаксис стандартной функции:
Окр(Число, Разрядность, РежимОкругления)
Третий параметр этой функции принимает системное перечисление РежимОкругления, которое имеет всего два значения:
РежимОкругления.Окр15как20 — округление половины разряда в большую сторону (0.5 округляется до 1).РежимОкругления.Окр15как10 — округление половины разряда в меньшую сторону (0.5 округляется до 0).Обратите внимание: этот параметр влияет исключительно на округление пограничного числа, которое заканчивается ровно на "5". Во всех остальных случаях (например, для чисел 7.1, 7.3 или 7.8) округление выполняется строго к ближайшему целому. Таким образом, округлить число 7.1 до 8 с помощью стандартной функции Окр() напрямую невозможно.
Ниже мы подробно рассмотрим проверенные на практике методы решения этой задачи.
Если перед вами стоит простая задача — быстро округлить число в большую сторону с определенной разрядностью (например, до целых чисел или до десятков) без написания громоздких функций, можно применить изящный математический прием.
Давайте разберем логику: если прибавить к исходному числу половину веса округляемого разряда и применить арифметическое округление с занижением пограничной пятерки, то любое число, имеющее дробную часть, автоматически перешагнет через математическую границу и округлится вверх. "Чистые" же числа без дробной части останутся неизменными.
Рассмотрим пример округления до десятков (в большую сторону):
// Нам нужно округлить 73 в большую сторону до десятков (ожидаем 80)
ИсходноеЧисло = 73;
Результат = Окр(ИсходноеЧисло + 5, -1, РежимОкругления.Окр15как10);
// В результате переменная "Результат" примет значение 80
Выясним причину, почему это работает: прибавив 5 к 73, мы получаем 78. Округление числа 78 по правилам дает нам 80. Если бы исходное число было ровно 70 (не требующим округления), то 70 + 5 = 75. При режиме Окр15как10 пограничное число 75 округлится вниз, то есть вернет исходные 70. Трюк работает безотказно!
Аналогично можно округлить число в меньшую сторону (вниз). Для этого мы вычитаем половину веса разряда и округляем в большую сторону для пограничных значений:
// Округляем 77 в меньшую сторону до десятков (ожидаем 70)
ИсходноеЧисло = 77;
Результат = Окр(ИсходноеЧисло - 5, -1, РежимОкругления.Окр15как20);
// В результате переменная "Результат" примет значение 70
Для дробных чисел логика масштабируется. Если мы округляем до копеек (2 знака после запятой):
// Округление вверх до сотых
Результат = Окр(Число + 0.005, 2, РежимОкругления.Окр15как10);
// Округление вниз до сотых
Результат = Окр(Число - 0.005, 2, РежимОкругления.Окр15как20);
Для создания универсального программного кода лучше всего написать собственные функции-обертки, которые имитируют поведение классических методов ceil и floor из других языков программирования. Давайте построим алгоритм на основе встроенной функции 1С Цел(), которая возвращает только целую часть числа, отбрасывая дробную.
Посмотрим на пример реализации функции округления строго вверх (Ceil):
Функция Ceil(Знач ИсходноеЧисло) Экспорт
// Если число целое, возвращаем его без изменений
Если ИсходноеЧисло = Цел(ИсходноеЧисло) Тогда
Возврат ИсходноеЧисло;
КонецЕсли;
// Для дробных чисел логика зависит от знака
Если ИсходноеЧисло > 0 Тогда
Возврат Цел(ИсходноеЧисло) + 1;
Иначе
Возврат Цел(ИсходноеЧисло);
КонецЕсли;
КонецФункции
Теперь спроектируем аналогичную функцию для округления строго вниз (Floor):
Функция Floor(Знач ИсходноеЧисло) Экспорт
// Если число целое, возвращаем его без изменений
Если ИсходноеЧисло = Цел(ИсходноеЧисло) Тогда
Возврат ИсходноеЧисло;
КонецЕсли;
// Для отрицательных чисел сдвигаем значение вниз на единицу
Если ИсходноеЧисло < 0 Тогда
Возврат Цел(ИсходноеЧисло) - 1;
Иначе
Возврат Цел(ИсходноеЧисло);
КонецЕсли;
КонецФункции
Существует продвинутый математический метод, позволяющий реализовать функцию строгого округления вверх (Ceil) в одну строку без использования условий Если ... Иначе. Такой подход работает быстрее на больших массивах данных.
Рассмотрим так называемый "М-метод" (метод с большой константой):
// М - заведомо большое целое число, превышающее любое возможное округляемое число
М = 1000000000;
Результат = М - Цел(М - ИсходноеЧисло);
Проанализируем ситуацию: если наше число равно 3.14, то выражение в скобках будет равно 1000000000 - 3.14 = 999999996.86. Встроенная функция Цел() отбросит дробную часть и вернет 999999996. Из большой константы вычитаем этот результат: 1000000000 - 999999996 = 4. Число успешно округлилось в большую сторону!
В торговых конфигурациях 1С (таких как "Управление торговлей" или "ERP Управление предприятием") часто требуется производить округление цен с определенным шагом (порядком). Например, округлять цены до 5 копеек, до 50 копеек или до 10 рублей строго в большую сторону.
Давайте рассмотрим универсальную функцию, которая решает эту задачу профессионально, оперируя понятием интервалов:
Функция ОкруглитьЦену(Знач Цена, ПорядокОкругления, ОкруглятьВБольшуюСторону = Истина) Экспорт
Если Цена = 0 Тогда
Возврат 0;
КонецЕсли;
// Если порядок округления не задан, округляем до копеек (0.01)
Если ПорядокОкругления = 0 Или ПорядокОкругления = Неопределено Тогда
Шаг = 0.01;
Иначе
Шаг = ПорядокОкругления;
КонецЕсли;
// Вычисляем количество интервалов, входящих в число
КоличествоИнтервалов = Цена / Шаг;
ЦелыхИнтервалов = Цел(КоличествоИнтервалов);
Если КоличествоИнтервалов = ЦелыхИнтервалов Тогда
// Число поделилось нацело на шаг округления, корректировка не требуется
Результат = Цена;
Иначе
Если ОкруглятьВБольшуюСторону Тогда
Результат = Шаг * (ЦелыхИнтервалов + 1);
Иначе
Результат = Шаг * ЦелыхИнтервалов;
КонецЕсли;
КонецЕсли;
Возврат Результат;
КонецФункции
Этот код отлично подходит для встраивания в общие модули ваших конфигураций. Он корректно обрабатывает произвольные шаги округления и позволяет гибко управлять направлением расчета.
Иногда необходимость округлить число строго вверх возникает внутри запроса 1С. Поскольку в языке запросов отсутствуют функции встроенного языка вроде Цел(), единственным инструментом является приведение типов с помощью конструкции ВЫРАЗИТЬ(Поле КАК ЧИСЛО). Однако ВЫРАЗИТЬ округляет числа исключительно по арифметическим правилам.
Чтобы обойти это ограничение в запросе, мы можем использовать следующую комбинацию с оператором ВЫБОР:
ВЫБОР
КОГДА ИсходноеЧисло = ВЫРАЗИТЬ(ИсходноеЧисло КАК ЧИСЛО(15, 0))
ТОГДА ИсходноеЧисло
ИНАЧЕ ВЫРАЗИТЬ(ИсходноеЧисло + 0.5 КАК ЧИСЛО(15, 0))
КОНЕЦ
Эта конструкция определяет, является ли число уже целым (сравнивая его с результатом приведения типа без знаков после запятой). Если число имеет дробную часть, то к нему прибавляется 0.5, после чего выполняется стандартное округление, что гарантирует смещение результата строго вверх.
При написании собственных математических алгоритмов в 1С крайне важно помнить о специфике работы процессора ПК с дробными числами (проблема Floating Point). Из-за особенностей двоичного представления некоторые бесконечные десятичные дроби не могут быть сохранены абсолютно точно.
Например, в результате сложных вычислений вместо точного числа 3 переменная может хранить значение 3.0000000000000002. Если к такому значению применить проверку ИсходноеЧисло = Цел(ИсходноеЧисло), условие вернет ложь. Программа решит, что у числа есть значащая дробная часть, и округлит его вверх до 4, что приведет к трудноуловимой ошибке в расчетах.
Решение проблемы: всегда делайте предварительную нормализацию чисел перед использованием логических сравнений в самописных функциях округления. Для этого достаточно предварительно "срезать" микроскопическую погрешность с помощью надежной стандартной функции:
НормализованноеЧисло = Окр(ИсходноеЧисло, 10);
Этот простой шаг сэкономит вам массу времени при отладке и гарантирует высокую точность любых финансовых расчетов.