Как правильно округлить проценты в 1С чтобы их сумма всегда была равна 100%

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

При расчете долей и процентных соотношений в прикладных решениях 1С разработчики часто сталкиваются с классической проблемой: при независимом математическом округлении каждого значения до двух знаков после запятой общая сумма процентов по группе строк дает 100.01% или 99.99%. Например, если распределить показатель между двумя позициями с точными значениями 78.125% и 21.875%, стандартное округление превратит их в 78.13% и 21.88%, что в сумме дает расхождение.

Выясним причину этой проблемы, проанализируем особенности округления в платформе 1С:Предприятие и разберем по шагам надежные практические способы решения — от изменения логики хранения данных до готовых программных алгоритмов.

Почему возникает погрешность при стандартном округлении

В платформе 1С округление во встроенном языке и в языке запросов работает по-разному:

  1. Во встроенном языке функция Окр() позволяет задать режим: РежимОкругления.Окр15как20 (арифметическое округление) или РежимОкругления.Окр15как10 (банковское округление к ближайшему четному числу).
  2. В языке запросов конструкция ВЫРАЗИТЬ(... КАК ЧИСЛО(Длина, Точность)) транслируется непосредственно в функции СУБД, где алгоритм округления фиксирован.

Однако ни математическое, ни банковское округление отдельных строк не гарантирует сохранение общей суммы в 100%, поскольку округление каждого слагаемого выполняется изолированно и не учитывает баланс всей совокупности. Для получения идеального итога требуется алгоритмическое распределение остатка погрешности.

Способ 1. Пропорциональное распределение по уменьшающейся базе во встроенном языке

Рассмотрим алгоритм распределения, при котором сумма 100% распределяется последовательно по строкам таблицы значений с непрерывным пересчетом остатка базы и остатка процентов. За счет динамического уменьшения остатка погрешность автоматически нивелируется на последних строках цикла.

Посмотрим на пример реализации данного алгоритма:


// ТЗ - исходная ТаблицаЗначений с колонками "Сумма" и "Процент"
БазаРаспределения = ТЗ.Итог("Сумма");
СуммаРаспределения = 100;

Для Каждого Стр Из ТЗ Цикл
    Если БазаРаспределения = 0 Тогда
        Стр.Процент = 0;
    Иначе
        // Округляем до 2 знаков текущую долю от оставшейся суммы
        Стр.Процент = Окр(СуммаРаспределения * Стр.Сумма / БазаРаспределения, 2);
        
        // Уменьшаем распределяемую сумму и базу на обработанные значения
        СуммаРаспределения = СуммаРаспределения - Стр.Процент;
        БазаРаспределения = БазаРаспределения - Стр.Сумма;
    КонецЕсли;
КонецЦикла;

Этот код выполняется за минимальное время даже на массивах в десятки тысяч строк и гарантирует, что итоговая сумма процентов по всем строкам таблицы будет строго равна 100.00%.

Способ 2. Использование типовой функции БСП

Если в конфигурацию интегрирована Библиотека стандартных подсистем (БСП), не требуется писать собственные циклы пересчета. В общем модуле ОбщегоНазначенияКлиентСервер предусмотрена специализированная процедура.

Разберем вызов этой функции:


// МассивКоэффициентов содержит исходные количественные или суммовые показатели
МассивКоэффициентов = Новый Массив;
МассивКоэффициентов.Добавить(78.125);
МассивКоэффициентов.Добавить(21.875);

// Распределяем 100% с точностью 2 знака после запятой
МассивПроцентов = ОбщегоНазначенияКлиентСервер.РаспределитьСуммуПропорциональноКоэффициентам(100, МассивКоэффициентов, 2);

// На выходе получаем массив значений, сумма которых строго равна 100

Функция БСП использует метод наибольших остатков (алгоритм Гамильтона), благодаря чему погрешность в 0.01% относится не на случайную или последнюю строку, а на элемент с наибольшей отброшенной дробной частью, что обеспечивает максимальную математическую справедливость.

Способ 3. Расчет процентов в запросе через накопительный итог

Если данные необходимо получить сразу готовым запросом без постобработки в коде 1С, применим технику расчета через накопительный итог. Разница между текущей суммой накопительного процента и предыдущей дает корректный процент для каждой строки.

Рассмотрим пример запроса для распределения остатков номенклатуры по складам:


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстатки.Склад КАК Склад,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК КоличествоОстаток
ПОМЕСТИТЬ ВТ_Остатки
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки КАК ТоварыНаСкладахОстатки
ГДЕ
    ТоварыНаСкладахОстатки.КоличествоОстаток > 0
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Остатки.Номенклатура КАК Номенклатура,
    СУММА(ВТ_Остатки.КоличествоОстаток) КАК КоличествоВсего
ПОМЕСТИТЬ ВТ_ИтогиНоменклатуры
ИЗ
    ВТ_Остатки КАК ВТ_Остатки
СГРУППИРОВАТЬ ПО
    ВТ_Остатки.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    ВТ_Остатки.Номенклатура КАК Номенклатура,
    ВТ_Остатки.Склад КАК Склад,
    ВТ_Остатки.КоличествоОстаток / ВТ_ИтогиНоменклатуры.КоличествоВсего * 100 КАК ПроцентТочный
ПОМЕСТИТЬ ВТ_Проценты
ИЗ
    ВТ_Остатки КАК ВТ_Остатки
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ИтогиНоменклатуры КАК ВТ_ИтогиНоменклатуры
        ПО ВТ_Остатки.Номенклатура = ВТ_ИтогиНоменклатуры.Номенклатура
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Проценты.Номенклатура КАК Номенклатура,
    Проценты.Склад КАК Склад,
    ВЫРАЗИТЬ(СУММА(ПроцентыДо.ПроцентТочный) КАК ЧИСЛО(10, 2)) КАК НакопленныйПроцент
ПОМЕСТИТЬ ВТ_Накопительно
ИЗ
    ВТ_Проценты КАК Проценты
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Проценты КАК ПроцентыДо
        ПО Проценты.Номенклатура = ПроцентыДо.Номенклатура
        И Проценты.Склад >= ПроцентыДо.Склад
СГРУППИРОВАТЬ ПО
    Проценты.Номенклатура,
    Проценты.Склад
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Накопительно.Номенклатура КАК Номенклатура,
    Накопительно.Склад КАК Склад,
    Накопительно.НакопленныйПроцент - ЕСТЬNULL(МАКСИМУМ(Предыдущие.НакопленныйПроцент), 0) КАК Процент
ИЗ
    ВТ_Накопительно КАК Накопительно
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Накопительно КАК Предыдущие
        ПО Накопительно.Номенклатура = Предыдущие.Номенклатура
        И Накопительно.Склад > Предыдущие.Склад
СГРУППИРОВАТЬ ПО
    Накопительно.Номенклатура,
    Накопительно.Склад,
    Накопительно.НакопленныйПроцент

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

Способ 4. Архитектурный подход к хранению и расчету процентов

Проанализируем методические рекомендации по проектированию структуры метаданных:

  1. Отказ от хранения процентов в регистрах: Рекомендуется сохранять в регистрах сведений и накопления абсолютные величины (количество, сумму или базовые коэффициенты распределения), а расчет процентов производить динамически при формировании печатной формы или отчета.
  2. Использование возможностей СКД: При построении отчетов на базе Системы компоновки данных процент рассчитывается выражением ресурса вида Сумма(Количество) / ВычислитьВыражение("Сумма(Количество)", "ОбщийИтог") * 100. В этом случае СКД автоматически выведет в строках округленные значения, а в поле общих итогов корректно отобразит 100%.
  3. Избыточная точность: Если хранение рассчитанного процента в регистре обязательно, увеличьте точность реквизита или ресурса с 2 до 4–6 знаков после запятой, выполняя округление до 2 знаков только в момент визуального вывода пользователю.
← На главную