Как реализовать банковское округление в 1С

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

В финансовой и банковской сферах часто используется метод округления к ближайшему четному (так называемое банковское округление или "banker's rounding"). В отличие от стандартного школьного метода, где 0,5 всегда округляется вверх, банковское округление минимизирует накопленную погрешность при суммировании большого количества чисел. В этой статье мы подробно разберем, как реализовать этот алгоритм на языке 1С, проанализируем различные подходы и выберем наиболее эффективный.

Зачем нужно банковское округление?

Стандартный метод округления (0,5 -> 1) имеет небольшое смещение в большую сторону. При миллионах операций это смещение превращается в значительные суммы. Банковское округление работает по принципу: если дробная часть равна ровно 0,5, число округляется до ближайшего четного целого. Например:

В платформе 1С встроенная функция Окр() поддерживает режимы РежимОкругления.Окр15как10 (0,5 вниз) и РежимОкругления.Окр15как20 (0,5 вверх), но прямого режима "банковское" не существует. Поэтому нам придется комбинировать эти режимы.

Решение 1: Математический метод через остаток от деления

Рассмотрим наиболее изящный способ, предложенный в обсуждении. Суть идеи заключается в том, чтобы программно определять, является ли целая часть числа четной или нечетной, и на основании этого выбирать режим округления для встроенной функции Окр().

Проанализируем пример функции, которая реализует эту логику в одну строку:


Результат = Окр(Число, Разрядность, ?(Число * Pow(10, Разрядность) % 2 < 1, РежимОкругления.Окр15как10, РежимОкругления.Окр15как20));

Разберем по шагам, как это работает:

  1. Мы умножаем Число на 10 в степени Разрядность. Это позволяет нам "вытащить" цифру, по которой будет приниматься решение, в разряд единиц.
  2. С помощью оператора % 2 (остаток от деления на 2) мы проверяем, является ли полученное число четным.
  3. Если число четное (остаток меньше 1), мы применяем РежимОкругления.Окр15как10 (округляем 0,5 вниз, к этому самому четному).
  4. Если число нечетное, применяем РежимОкругления.Окр15как20 (округляем 0,5 вверх, к следующему четному).

Решение 2: Пошаговый алгоритм с предварительным обрезанием (для сложных случаев)

В ходе дискуссии выяснилось, что иногда требования заказчика подразумевают "банковское округление" только по третьему знаку после запятой, игнорируя последующие хвосты. Рассмотрим более детальный код, который учитывает этот нюанс и удобен для отладки:


Функция ПолучитьБанковскоеОкругление(Число, Разрядность) Экспорт
    
    // 1. Убираем лишние знаки за пределами точности принятия решения
    ОбрезанноеЧисло = Цел(Число * Pow(10, Разрядность + 1)) / Pow(10, Разрядность + 1);
    
    // 2. Определяем четность числа в целевом разряде
    ЧислоДляПроверкиЧетности = ОбрезанноеЧисло * Pow(10, Разрядность);
    
    // 3. Выбираем режим округления 1С
    // Если число четное, то 0.5 округляем вниз (в сторону четного)
    // Если нечетное, то 0.5 округляем вверх (тоже в сторону четного)
    Если ЧислоДляПроверкиЧетности % 2 < 1 Тогда
        НужныйРежим = РежимОкругления.Окр15как10;
    Иначе
        НужныйРежим = РежимОкругления.Окр15как20;
    КонецЕсли;
    
    Возврат Окр(ОбрезанноеЧисло, Разрядность, НужныйРежим);
    
КонецФункции

Этот вариант более надежен, так как использование Цел() позволяет избежать проблем с "хвостами" типа 0,500000001, которые при чистом банковском округлении должны были бы всегда округляться вверх, но по специфическим ТЗ часто должны считаться как 0,5.

Решение 3: Читаемый код через сравнение режимов

Выясним причину, по которой некоторые программисты предпочитают избегать оператора % (остаток от деления) при работе с плавающей точкой. Иногда это связано с особенностями хранения дробных чисел в памяти. Рассмотрим альтернативный подход, который использует только логику сравнения результатов встроенных функций 1С:


Если Окр(Число, Разрядность, РежимОкругления.Окр15как10) = Окр(Число, Разрядность, РежимОкругления.Окр15как20) Тогда
    // Число не пограничное (не заканчивается на 5), режим не важен
    Режим = РежимОкругления.Окр15как20;
Иначе
    // Мы попали ровно на середину (0.5). Проверяем результат на четность.
    РезультатВниз = Окр(Число, Разрядность, РежимОкругления.Окр15как10);
    Если (РезультатВниз * Pow(10, Разрядность)) % 2 = 0 Тогда
        Режим = РежимОкругления.Окр15как10;
    Иначе
        Режим = РежимОкругления.Окр15как20;
    КонецЕсли;
КонецЕсли;
Возврат Окр(Число, Разрядность, Режим);

Важные замечания:

Подводя итог, можно сказать, что наиболее универсальным и производительным решением для 1С является Решение 1 или Решение 2. Они позволяют избежать медленных строковых манипуляций и работают на уровне базовой арифметики платформы.

← На главную