В работе с 1С мы часто сталкиваемся с необходимостью округления чисел по нестандартным правилам, например, всегда в большую сторону. Это означает, что любое нецелое число, такое как 10.1, должно быть округлено до 11, при этом целые числа, например 10, должны оставаться без изменений. Такая логика округления соответствует математической функции "потолок" (Ceiling) для положительных чисел. Давайте подробно разберем различные подходы к решению этой задачи в 1С, рассмотрим особенности обработки целых и дробных чисел, а также нюансы работы с отрицательными значениями.
Один из наиболее интуитивно понятных и простых способов округления положительного числа до целого всегда в большую сторону — это проверка наличия у числа дробной части. Если дробная часть присутствует, мы просто увеличиваем целую часть на единицу. Если число уже является целым, оно остается без изменений.
Рассмотрим этот метод по шагам:
Цел(). Эта функция округляет число до ближайшего целого в меньшую сторону (т.е., является функцией "пол" или Floor). Например, Цел(10.1) вернет 10, а Цел(-10.1) вернет -11.?() (или стандартную конструкцию Если Тогда Иначе КонецЕсли) для принятия решения:
Вот пример реализации этого подхода:
ЦелаяЧасть = Цел(Число);
ДробнаяЧасть = Число - ЦелаяЧасть;
КонечноеЧисло = ?(ДробнаяЧасть = 0, ЦелаяЧасть, ЦелаяЧасть + 1);
Мы можем также записать эту логику в более краткой форме, как было предложено в обсуждении на форуме:
Если Число - Цел(Число) > 0 Тогда
Число = Цел(Число) + 1;
КонецЕсли;
Этот метод идеально подходит для положительных чисел, которые необходимо округлить до ближайшего большего целого числа (классическое математическое "округление вверх" или "потолок"). Однако, если мы применим этот подход к отрицательным числам, например, к -10.1, то Цел(-10.1) вернет -11, и тогда -11 + 1 даст -10. Это соответствует математическому потолку для отрицательных чисел (Ceiling(-10.1) = -10). Если же вам требуется иное поведение для отрицательных чисел, например, округление "от нуля" (т.е. -10.1 должно стать -11), тогда потребуется модификация.
Одним из самых мощных и гибких инструментов для округления в 1С является встроенная функция Окр(). Используя ее в комбинации с небольшим смещением, мы можем добиться желаемого поведения "округления всегда в большую сторону" для положительных чисел, при этом сохраняя целые числа без изменений.
Давайте сначала вспомним синтаксис функции Окр():
Окр(Число, [Разрядность], [РежимОкругления])
Число: Это исходное число, которое мы хотим округлить.Разрядность: Этот необязательный параметр определяет количество знаков после запятой, до которого следует округлить число. Если мы указываем 0, то число будет округлено до целого. Отрицательные значения разрядности позволяют округлять до десятков, сотен и так далее.РежимОкругления: Этот параметр является ключевым для нашего решения, так как он влияет на то, как функция Окр() обрабатывает числа, заканчивающиеся на 0.5.
РежимОкругления.Окр15как10 (или числовой параметр 0): В этом режиме числа, оканчивающиеся на 0.5, округляются в меньшую сторону. Например, 10.5 будет округлено до 10.РежимОкругления.Окр15как20 (или числовой параметр 1): В этом режиме числа, оканчивающиеся на 0.5, округляются в большую сторону. Например, 10.5 будет округлено до 11. Этот режим часто используется по умолчанию, если РежимОкругления не указан.Для того чтобы округлить число всегда в большую сторону до целого, при этом не изменяя целые числа, мы можем использовать следующую конструкцию:
Результат = Окр(Число + 0.5, 0, РежимОкругления.Окр15как10);
Давайте подробно проанализируем, как эта конструкция работает на различных примерах:
Число = 10.1:
0.5 к числу: 10.1 + 0.5 = 10.6.Окр(10.6, 0, РежимОкругления.Окр15как10). Поскольку 0.6 больше, чем 0.5, число 10.6 будет округлено в большую сторону до 11. Это именно то, что нам нужно.Число = 10.0 (целое число):
0.5: 10.0 + 0.5 = 10.5.Окр(10.5, 0, РежимОкругления.Окр15как10). Здесь вступает в действие параметр РежимОкругления.Окр15как10, который предписывает округлять 0.5 в меньшую сторону. В результате 10.5 округляется до 10. Таким образом, целое число осталось без изменений, что соответствует нашим требованиям.Число = 10.99:
0.5: 10.99 + 0.5 = 11.49.Окр(11.49, 0, РежимОкругления.Окр15как10). Поскольку 0.49 меньше, чем 0.5, число 11.49 будет округлено в меньшую сторону до 11. Это также соответствует требуемому поведению.Этот метод является очень надежным и широко используемым для положительных чисел. Его главное преимущество заключается в том, что он корректно обрабатывает целые числа, не изменяя их, и в то же время гарантирует округление всех дробных частей в большую сторону. Он реализует логику математической функции "потолок" для положительных чисел.
Ключевую роль в описанном выше решении играет параметр РежимОкругления.Окр15как10. Часто этот режим вводит в заблуждение, так как его название ("15 как 10") наводит на мысль об округлении всегда в меньшую сторону. Однако его действие строго ограничено случаями, когда дробная часть числа в точности равна 0.5. Для всех остальных дробных частей (например, 0.1-0.4 или 0.6-0.9) функция Окр() работает по стандартным математическим правилам: округляет вниз, если дробная часть меньше 0.5, и вверх, если больше 0.5.
Добавление 0.5 к исходному числу является хитрым приемом, который стратегически "смещает" все дробные части:
X.ABC (где ABC > 0) после прибавления 0.5 превратится в Y.DEF, где DEF будет 0.5 или больше (например, 10.0000001 + 0.5 = 10.5000001). Такие числа будут округлены вверх.X.0 после прибавления 0.5 станут точно X.5 (например, 10.0 + 0.5 = 10.5). И именно для этих случаев параметр РежимОкругления.Окр15как10 сработает, округлив X.5 вниз до X.Таким образом, эта комбинация гарантирует, что дробные числа всегда округляются вверх (к большему целому), а целые числа остаются неизменными.
Для того чтобы убедиться в корректности работы этого метода, вы можете воспользоваться простым проверочным кодом, предложенным в одном из сообщений на форуме:
Сч = 0;
Пока Сч < 24 Цикл
Результат = Окр(Сч + 0.5, 0, РежимОкругления.Окр15как10);
СообщениеПользователю = СтрШаблон("%1 = %2", Сч, Результат);
Сообщить(СообщениеПользователю); // Можно использовать ОбщегоНазначения.СообщитьПользователю() в зависимости от контекста
Сч = Сч + 0.1;
КонецЦикла;
Этот цикл позволит вам проследить поведение округления для ряда чисел с шагом 0.1 и убедиться, что все работает согласно описанной логике.
Если нам нужно округлить число всегда в большую сторону не только до целого, но и до определенного количества знаков после запятой (до заданной разрядности), мы можем модифицировать подход со смещением. Идея заключается в прибавлении минимально возможной величины, которая гарантирует округление "вверх" на требуемой позиции разряда.
В обсуждении на форуме была предложена функция, которая реализует этот универсальный принцип:
Функция ОкрВсегдаВверх(ВходЧисло, РазрядОкругления)
Возврат ?(ТипЗнч(ВходЧисло) = Тип("Число"), Окр(ВходЧисло + 5 * Pow(10, -РазрядОкругления - 1), РазрядОкругления, РежимОкругления.Окр15как10), Неопределено);
КонецФункции
Давайте разберем принцип работы этой функции:
ВходЧисло числом. Если нет, возвращается Неопределено, предотвращая ошибки.5 * Pow(10, -РазрядОкругления - 1). Это вычисляет очень маленькую величину, которая будет добавлена к числу. Эта величина гарантирует, что даже если округляемая цифра находится на "границе" округления (например, X.0000001 при округлении до 0 знаков, или X.Y000001 при округлении до 1 знака), она будет смещена таким образом, чтобы округлиться вверх.
РазрядОкругления = 0 (округляем до целого), то 5 * Pow(10, -0 - 1) = 5 * Pow(10, -1) = 5 * 0.1 = 0.5. В этом случае мы получаем наше знакомое смещение +0.5.РазрядОкругления = 1 (округляем до одного знака после запятой), то 5 * Pow(10, -1 - 1) = 5 * Pow(10, -2) = 5 * 0.01 = 0.05. То есть, к числу будет прибавлено 0.05 перед округлением до одного знака.РазрядОкругления = -1 (округляем до десятков), то 5 * Pow(10, -(-1) - 1) = 5 * Pow(10, 0) = 5 * 1 = 5. В этом случае к числу прибавляется 5 перед округлением до десятков.Окр(): После добавления смещения, функция Окр() вызывается с требуемой РазрядОкругления и режимом РежимОкругления.Окр15как10, что обеспечивает корректное округление "вверх" для дробных чисел и сохранение целых значений (для соответствующих разрядов).Эта функция является универсальным решением для округления "вверх" с любой заданной разрядностью, действуя как математический "потолок" для положительных чисел.
Понимание термина "округление в большую сторону" для отрицательных чисел может сильно различаться. В строгом математическом смысле функция "потолок" (Ceiling) всегда округляет число к ближайшему большему целому или равному, то есть в сторону положительной бесконечности. Рассмотрим примеры:
Ceiling(10.1) = 11Ceiling(10.0) = 10Ceiling(-10.1) = -10 (потому что -10 больше, чем -10.1)Ceiling(-10.0) = -10Однако в некоторых бизнес-сценариях под "округлением в большую сторону" для отрицательных чисел подразумевают "округление от нуля" или "округление по модулю вверх". То есть, -10.1 должно превратиться в -11. Такой подход не является математическим "потолком", но может быть необходим по специфике учета.
В обсуждении на форуме была предложена функция, которая, судя по комментариям к ней, стремится реализовать именно такую логику: округлять положительные числа как "потолок", а отрицательные числа "от нуля" (т.е. в сторону отрицательной бесконечности, если они нецелые). Давайте проанализируем и скорректируем предложенный код, чтобы он соответствовал заявленному поведению.
Исходная идея из сообщения 47:
Функция ОкрВ(Число)
// Округляет число всегда вверх (без учета знака)
// 0 = 0
// 1 = 1
// 1.0001 = 2
// 1.9999 = 2
// -1 = -1
// -1.0001 = -2
// -1.9999 = -2
Если Число = Цел(Число) Тогда
Возврат Число
КонецЕсли;
Если Число > 0 Тогда
Возврат Цел(Число) + 1;
Иначе
Возврат Цел(Число) - 1; // Исходный код на форуме
КонецЕсли;
КонецФункции
Давайте проверим поведение этой функции, особенно для отрицательных чисел, учитывая, что функция Цел() в 1С работает как математический "пол" (Floor), т.е. округляет к ближайшему меньшему целому:
Число = 1.0001: Число > 0, Цел(1.0001) = 1. Возвращается 1 + 1 = 2. Соответствует комментарию.Число = -1.0001: Число > 0 ложно. Цел(-1.0001) = -2. Исходный код возвращает -2 - 1 = -3. Это не соответствует комментарию // -1.0001 = -2.Число = -1.9999: Число > 0 ложно. Цел(-1.9999) = -2. Исходный код возвращает -2 - 1 = -3. Это не соответствует комментарию // -1.9999 = -2.Чтобы функция ОкрВ() соответствовала своим же комментариям для отрицательных чисел ("округление от нуля"), логика для отрицательных дробных чисел должна быть изменена. Если Цел(Число) для -1.0001 уже дает -2, то просто возврат Цел(Число) будет правильным:
Скорректированная функция ОкрВ() (соответствующая комментариям из сообщения 47):
Функция ОкрВ(Число)
// Округляет число всегда вверх (для положительных) или от нуля (для отрицательных),
// если оно не является целым. Целые числа остаются без изменений.
// Примеры поведения:
// 0 = 0
// 1 = 1
// 1.0001 = 2
// 1.9999 = 2
// -1 = -1
// -1.0001 = -2
// -1.9999 = -2
Если Число = Цел(Число) Тогда
Возврат Число;
КонецЕсли;
Если Число > 0 Тогда
Возврат Цел(Число) + 1; // Для положительных: математический 'потолок' (Ceiling)
Иначе // Число < 0
Возврат Цел(Число); // Для отрицательных: округление 'от нуля' (например, -1.0001 -> -2, т.к. Цел(-1.0001) = -2)
КонецЕсли;
КонецФункции
В этой скорректированной версии:
Цел() для отрицательных дробных чисел уже дает результат, округленный "от нуля" (например, Цел(-1.0001) равно -2).Выбирайте этот вариант, если ваша бизнес-логика требует именно такого поведения для отрицательных чисел. Если же вам нужен строгий математический "потолок" для всех чисел (включая отрицательные), то подход с Окр(Число + 0.5, 0, РежимОкругления.Окр15как10) или ?(ДробнаяЧасть=0, ЦелаяЧасть, ЦелаяЧасть + 1) также будет работать для отрицательных чисел, давая Ceiling(-10.1) = -10.
Задача округления чисел всегда в большую сторону часто возникает не только в серверном или клиентском коде, но и непосредственно в запросах 1С. Поскольку в запросах мы не имеем прямого доступа к функциям Окр() или Цел() в их синтаксисе языка 1С, нам приходится использовать аналогичные алгоритмы с помощью операторов SQL, которые транслируются механизмом 1С.
Для реализации логики округления до целого в большую сторону для положительных чисел в запросах, мы можем использовать конструкцию ВЫБОР КОГДА ИНАЧЕ КОНЕЦ в сочетании с функцией ЦЕЛ() (которая доступна в языке запросов и аналогична функции Floor в SQL):
ВЫБОР
КОГДА (ВашеЧисло - ЦЕЛ(ВашеЧисло)) > 0
ТОГДА ЦЕЛ(ВашеЧисло) + 1
ИНАЧЕ ВашеЧисло
КОНЕЦ
Этот фрагмент запроса работает точно так же, как и его аналог на языке 1С: если у числа есть дробная часть, то к его целой части прибавляется 1; в противном случае число остается без изменений. Это решение подходит для положительных чисел и реализует функцию "потолок".
Для более универсального решения, аналогичного использованию Окр(Число + 0.5, 0, РежимОкругления.Окр15как10), мы можем применить подход со смещением и функцией ВЫРАЗИТЬ():
ВЫРАЗИТЬ(ВашеЧисло + 0.499999999999 КАК ЧИСЛО(15, 0))
Разберем этот подход:
ВашеЧисло очень маленькую, но достаточную для смещения величину (0.499999999999). Количество девяток должно быть достаточным для обеспечения требуемой точности и гарантии смещения, даже если исходное число имело много знаков после запятой, близких к целому (например, 10.000000000001). Это смещение аналогично прибавлению 0.5 в коде на 1С, но с небольшой разницей, чтобы избежать граничных случаев с точностью 0.5, когда округление 0.5 может варьироваться в зависимости от конкретной реализации СУБД.ВЫРАЗИТЬ(): Затем мы используем функцию ВЫРАЗИТЬ() для явного преобразования числа к типу ЧИСЛО(15, 0). Параметры (15, 0) означают, что число должно иметь общую длину до 15 цифр и 0 знаков после запятой. В процессе такого преобразования 1С (или underlying SQL-сервер) выполняет стандартное математическое округление до указанной разрядности. За счет ранее добавленного смещения +0.499999... любое дробное число, которое нужно округлить вверх, после смещения становится больше X.5 (или X.499999...) и будет округлено в большую сторону до следующего целого. А целые числа после добавления 0.499999... станут X.499999... и будут округлены вниз, т.е. останутся целыми X.Этот метод является очень эффективным и часто используется в запросах для достижения требуемого "округления в большую сторону" для положительных чисел.
Мы рассмотрели несколько эффективных способов округления чисел всегда в большую сторону в 1С, как в коде, так и в запросах. Выбор конкретного метода зависит от ваших точных требований:
Критически важно всегда проверять выбранный метод на граничных значениях: целые числа (например, 10), числа с очень маленькой дробной частью (например, 10.000001), числа с дробной частью, близкой к 0.5 (например, 10.49999 и 10.500001), и, если применимо, отрицательные числа. Только тщательное тестирование гарантирует, что ваше решение будет работать корректно во всех возможных сценариях.