Как округлить числа в таблице значений, чтобы не изменился общий итог?

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

В практике разработки на платформе 1С часто возникает задача распределения сумм или показателей (например, часов или веса), когда после вычислений появляются дробные части. Если просто применить функцию Окр() к каждой строке, итоговая сумма по колонке может «поплыть» из-за накопленной погрешности. Рассмотрим ситуацию, когда нам нужно округлить только те числа, которые находятся максимально близко к целым (например, 51.99 или 16.01), при этом сохранив общую сумму неизменной.

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

Способ 1: Условное округление по порогу отклонения

Проанализируем ситуацию, описанную в обсуждении. У нас есть числа 51.99, 28 и 16.01, сумма которых составляет ровно 96. Нам нужно превратить их в 52, 28 и 16. При этом мы не хотим трогать числа типа 6.56, так как они не являются «почти целыми».

Самый простой вариант — проверить разницу между числом и его округленным значением. Если эта разница меньше определенного порога (например, 0.01), мы выполняем округление. Выясним, как это реализовать в коде:


Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл
    ТекущееЗначение = СтрокаТЗ.Часы;
    ОкругленноеЗначение = Окр(ТекущееЗначение, 0);
    
    // Вычисляем абсолютное отклонение
    Отклонение = ТекущееЗначение - ОкругленноеЗначение;
    
    // Если разница составляет 0.01 или -0.01 (одна сотая)
    Если Абс(Отклонение) <= 0.011 Тогда
        СтрокаТЗ.Часы = ОкругленноеЗначение;
    КонецЕсли;
КонецЦикла;

Важный нюанс: при сравнении чисел с плавающей точкой в 1С стоит использовать функцию Абс() (модуль числа) и сравнивать с чуть большим значением (0.011 вместо 0.01), чтобы избежать проблем с точностью представления чисел в памяти.

Способ 2: Метод балансирующего слагаемого (Б = С - А)

Разберем подход, предложенный в сообщениях 8 и 9. Этот метод идеально подходит, если нужно распределить сумму между двумя показателями или гарантировать, что последняя строка всегда «дозакроет» итог до нужного значения. Схема проста: мы округляем все элементы, кроме последнего, а последний вычисляем как разницу между общим итогом и суммой уже округленных строк.

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


ВсегоПоПлану = 96;
НакопленнаяСумма = 0;
КоличествоСтрок = ТаблицаЗначений.Количество();

Для Инд = 0 По КоличествоСтрок - 2 Цикл // Обходим все строки, кроме последней
    ТекущаяСтрока = ТаблицаЗначений[Инд];
    ТекущаяСтрока.Часы = Окр(ТекущаяСтрока.Часы, 0);
    НакопленнаяСумма = НакопленнаяСумма + ТекущаяСтрока.Часы;
КонецЦикла;

// Последней строке отдаем остаток
ТаблицаЗначений[КоличествоСтрок - 1].Часы = ВсегоПоПлану - НакопленнаяСумма;

Этот метод гарантирует, что Итог("Часы") всегда будет равен ВсегоПоПлану. Однако будьте осторожны: если погрешность была большой, в последнюю строку может «улететь» значительная разница, что сделает данные некорректными.

Способ 3: Алгоритм накопленной ошибки (равномерное распределение)

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

Рассмотрим пошагово:

  1. Заводим переменную для хранения суммы до округления и после округления.
  2. В цикле прибавляем точное значение к «точному итогу».
  3. Вычисляем значение текущей строки как разность между округленным «точным итогом» и уже распределенным «округленным итогом».

ИтогДоОкругления = 0;
ИтогПослеОкругления = 0;

Для Каждого Строка Из ТаблицаЗначений Цикл
    ИтогДоОкругления = ИтогДоОкругления + Строка.Часы;
    НовоеЗначениеЧасов = Окр(ИтогДоОкругления - ИтогПослеОкругления, 0);
    
    Строка.Часы = НовоеЗначениеЧасов;
    ИтогПослеОкругления = ИтогПослеОкругления + НовоеЗначениеЧасов;
КонецЦикла;

Этот алгоритм — самый профессиональный. Он не требует сортировки и автоматически распределяет разницу в 1 копейку или 1 час между строками так, чтобы общий итог в ТаблицаЗначений сошелся идеально.

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

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

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

Способ 5: Метод «Наибольшего остатка» (Метод Гамильтона)

Для максимально точного распределения, когда важно, в какую именно строку попадет округление, можно использовать следующую методику:

  1. Округляем все значения в меньшую сторону (отбрасываем дробную часть).
  2. Считаем общую сумму полученных целых чисел.
  3. Вычисляем разницу (сколько единиц нам не хватает до целевого итога).
  4. Сортируем строки по убыванию их дробной части.
  5. Добавляем по единице к первым строкам в отсортированном списке, пока не покроем разницу.

Пример: Если у нас есть 10 строк с дробной частью 0.99, а не хватает всего 2 единиц до итога, то только две строки с самыми большими дробными частями получат «прибавку», остальные останутся округленными вниз. Это самый справедливый с точки зрения математики метод.

Выясним, какой метод подходит вам: если нужно просто убрать «визуальные» погрешности в 0.01, используйте Способ 1. Если же стоит задача строгого соблюдения баланса в финансовых или учетных документах, вашим выбором должен стать Способ 3 или использование типовых механизмов БСП.

← На главную