Как избавиться от лишней копейки при распределении сумм в 1С

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

При получении задач на пропорциональное распределение сумм (например, оклада 50 000 рублей по трем проектам с долями 1, 1, 1) программисты часто сталкиваются с погрешностью округления. При стандартном округлении каждого расчета до двух знаков после запятой (16 666,67 * 3 = 50 000,01) возникает лишняя копейка. В других условиях копейки, наоборот, может не хватать.

Разберем, почему возникает эта проблема и какие алгоритмы применяются в типовых решениях 1С для точного выравнивания итоговой суммы.

Суть проблемы и базовый принцип решения

Проблема возникает из-за того, что деление числа на части с округлением каждой строки до 2 знаков после запятой теряет или добавляет дробную часть. Простая математическая сумма отдельных строк перестает совпадать с исходным распределяемым значением.

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

Способ 1. Расчет остатка на каждом шаге (пошаговое уменьшение базы)

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

Проанализируем пример с распределением 50 000 рублей на 3 равные доли:

  1. Шаг 1 (Проект 1): Распределяемый остаток 50 000,00, остаток долей 3. 50 000 / 3 = 16 666,67. Остаток суммы к распределению: 50 000,00 - 16 666,67 = 33 333,33.
  2. Шаг 2 (Проект 2): Распределяемый остаток 33 333,33, остаток долей 2. 33 333,33 / 2 = 16 666,67. Остаток суммы к распределению: 33 333,33 - 16 666,67 = 16 666,66.
  3. Шаг 3 (Проект 3): Распределяемый остаток 16 666,66, остаток долей 1. На последнем шаге деление не выполняется, забирается весь оставшийся остаток: 16 666,66.

В результате получаем суммы: 16 666,67 + 16 666,67 + 16 666,66 = 50 000,00. Итоговая сумма сходится идеально.

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


ВсегоКРаспределению = 50000;
ВсегоБаза = ТаблицаПроектов.Итог("Доля");

ОстатокСуммы = ВсегоКРаспределению;
ОстатокБазы  = ВсегоБаза;

КоличествоСтрок = ТаблицаПроектов.Количество();
Индекс = 0;

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

Способ 2. Отнесение погрешности на строку с максимальной базой

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

Разберем этот алгоритм по шагам:

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

Рассмотрим пример кода для данного подхода:


ВсегоКРаспределению = 50000;
ВсегоБаза = ТаблицаПроектов.Итог("Доля");

СтрокаСМаксБазой = Неопределено;
МаксБаза = -1;
РассчитанныйИтог = 0;

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

// Корректировка копеек на максимальную строку
Разница = ВсегоКРаспределению - РассчитанныйИтог;
Если Разница <> 0 И СтрокаСМаксБазой <> Неопределено Тогда
    СтрокаСМаксБазой.Сумма = СтрокаСМаксБазой.Сумма + Разница;
КонецЕсли;

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

Если проект разрабатывается на базе конфигурации с Библиотекой Стандартных Подсистем (БСП) или типового решения (1С:УНФ, 1С:ERP, 1С:УТ, 1С:КА), не требуется писать алгоритмы выравнивания вручную.

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

Важные краевые случаи (Edge Cases)

При реализации собственного алгоритма распределения обратим внимание на следующие нюансы:

← На главную