В практике разработки на платформе 1С часто возникает задача распределения сумм или показателей (например, часов или веса), когда после вычислений появляются дробные части. Если просто применить функцию Окр() к каждой строке, итоговая сумма по колонке может «поплыть» из-за накопленной погрешности. Рассмотрим ситуацию, когда нам нужно округлить только те числа, которые находятся максимально близко к целым (например, 51.99 или 16.01), при этом сохранив общую сумму неизменной.
В этой статье мы разберем несколько подходов: от простых условий внутри цикла до профессиональных алгоритмов распределения остатка, используемых в типовых конфигурациях.
Проанализируем ситуацию, описанную в обсуждении. У нас есть числа 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), чтобы избежать проблем с точностью представления чисел в памяти.
Разберем подход, предложенный в сообщениях 8 и 9. Этот метод идеально подходит, если нужно распределить сумму между двумя показателями или гарантировать, что последняя строка всегда «дозакроет» итог до нужного значения. Схема проста: мы округляем все элементы, кроме последнего, а последний вычисляем как разницу между общим итогом и суммой уже округленных строк.
Посмотрим на пример реализации:
ВсегоПоПлану = 96;
НакопленнаяСумма = 0;
КоличествоСтрок = ТаблицаЗначений.Количество();
Для Инд = 0 По КоличествоСтрок - 2 Цикл // Обходим все строки, кроме последней
ТекущаяСтрока = ТаблицаЗначений[Инд];
ТекущаяСтрока.Часы = Окр(ТекущаяСтрока.Часы, 0);
НакопленнаяСумма = НакопленнаяСумма + ТекущаяСтрока.Часы;
КонецЦикла;
// Последней строке отдаем остаток
ТаблицаЗначений[КоличествоСтрок - 1].Часы = ВсегоПоПлану - НакопленнаяСумма;
Этот метод гарантирует, что Итог("Часы") всегда будет равен ВсегоПоПлану. Однако будьте осторожны: если погрешность была большой, в последнюю строку может «улететь» значительная разница, что сделает данные некорректными.
Если нам нужно округлить большое количество строк и мы хотим распределить «лишние копейки» максимально равномерно, проанализируем метод накопленной ошибки. Суть в том, что на каждом шаге мы учитываем разницу, возникшую на предыдущем шаге.
Рассмотрим пошагово:
ИтогДоОкругления = 0;
ИтогПослеОкругления = 0;
Для Каждого Строка Из ТаблицаЗначений Цикл
ИтогДоОкругления = ИтогДоОкругления + Строка.Часы;
НовоеЗначениеЧасов = Окр(ИтогДоОкругления - ИтогПослеОкругления, 0);
Строка.Часы = НовоеЗначениеЧасов;
ИтогПослеОкругления = ИтогПослеОкругления + НовоеЗначениеЧасов;
КонецЦикла;
Этот алгоритм — самый профессиональный. Он не требует сортировки и автоматически распределяет разницу в 1 копейку или 1 час между строками так, чтобы общий итог в ТаблицаЗначений сошелся идеально.
Если вы работаете в современной конфигурации на базе БСП (Библиотека стандартных подсистем), нет смысла изобретать велосипед. Проанализируем возможности общего модуля ОбщегоНазначения. В нем часто присутствует функция РаспределитьСуммуПропорционально().
Она принимает массив коэффициентов и общую сумму. Функция сама рассчитает доли, округлит их и распределит остатки (те самые копейки) по алгоритму наибольшего остатка. Это позволяет избежать ручного написания циклов и гарантирует отсутствие математических ошибок.
Для максимально точного распределения, когда важно, в какую именно строку попадет округление, можно использовать следующую методику:
Пример: Если у нас есть 10 строк с дробной частью 0.99, а не хватает всего 2 единиц до итога, то только две строки с самыми большими дробными частями получат «прибавку», остальные останутся округленными вниз. Это самый справедливый с точки зрения математики метод.
Выясним, какой метод подходит вам: если нужно просто убрать «визуальные» погрешности в 0.01, используйте Способ 1. Если же стоит задача строгого соблюдения баланса в финансовых или учетных документах, вашим выбором должен стать Способ 3 или использование типовых механизмов БСП.