У меня эта проблема возникла в конфигурации 1С:Комплексная автоматизация 8 на платформе 8.2. Для решения я создал свой общий модуль и прописал в нем следующие функции:
Функция РасчетНОД (Число1, Число2) Экспорт
Если Число1 >= Число2 Тогда
Первое = Число1;
Второе = Число2;
Иначе
Первое = Число2;
Второе = Число1;
КонецЕсли;
Остаток = Первое % Второе;
Если Остаток > 0 Тогда
Первое = Второе;
Второе = Остаток;
НОД = РасчетНОД(Первое, Второе);
Иначе
НОД = Второе;
КонецЕсли;
Возврат НОД;
КонецФункции
Функция Эйлера (Число) Экспорт
ЧислаЭйлера = Новый Массив;
Количество =0;
ЧислаЭйлера.Добавить(Количество);
Для Номер = 1 По Число Цикл
Если РасчетНОД(Число, Номер) = 1 Тогда
Количество = Количество + 1;
ЧислаЭйлера.Добавить(?(Номер>Число/2, Номер-Число, Номер));
КонецЕсли;
КонецЦикла;
ЧислаЭйлера[0] = Количество;
Возврат ЧислаЭйлера;
КонецФункции
Функция СтепеньПоМодулю (Основание, Степень, Модуль) Экспорт
Основание = Основание % Модуль;
Число = Основание;
Для Индекс = 2 По Степень Цикл
Число = Основание * Число;
Число = Число % Модуль;
КонецЦикла;
Возврат Число;
КонецФункции
Функция РешениеСравнения (Коэффициент, Сравнение, Модуль) Экспорт
НОД = РасчетНОД(Коэффициент, Модуль);
Если Сравнение%НОД = 0 Тогда
СравнениеНОД = Цел(Сравнение/НОД);
КоэффициентНОД = Цел(Коэффициент/НОД);
МодульНОД = Цел(Модуль/НОД);
Степень = Эйлера(МодульНОД)[0] - 1;
Решение = СравнениеНОД * СтепеньПоМодулю(КоэффициентНОД, Степень, МодульНОД);
Решение = Решение % МодульНОД;
КонецЕсли;
Возврат Решение;
КонецФункции
Функция РешениеСравненияЛинейное (Коэффициент1, Коэффициент2, Сравнение, НОД, Модуль) Экспорт
МГлавный = Новый Массив;
МОсновной = Новый Массив;
Коэффициент1НОД = Цел(Коэффициент1/НОД);
Коэффициент2НОД = Цел(Коэффициент2/НОД);
МодульНОД = Цел(Модуль/НОД);
СравнениеНОД = Цел(Сравнение/НОД);
НОД1 = РасчетНОД(Коэффициент1НОД, МодульНОД);
НОД2 = РасчетНОД(Коэффициент2НОД, МодульНОД);
МГлавный = Эйлера(МодульНОД);
Если НОД2 > 1 Тогда
Коэф = РешениеСравнения(НОД1, СравнениеНОД, НОД2);
Иначе
Коэф = 1;
КонецЕсли;
Счетчик = Цел(МодульНОД/НОД2);
Для Номер = 0 ПО Счетчик-1 Цикл
Коэф2 = МГлавный.Найти(?((Коэф + Номер*НОД2)>МодульНОД/2, (Коэф + Номер*НОД2)-МодульНОД, (Коэф + Номер*НОД2)));
Если ТипЗнч(Коэф2)<>Тип("Неопределено") Тогда
МОсновной.Добавить(МГлавный[Коэф2]);
КонецЕсли;
КонецЦикла;
Ответ = Новый Массив(2);
Первый = Новый Массив;
Второй = Новый Массив;
Результат = Новый Массив;
Модуль1 = Цел(МодульНОД/НОД1);
Модуль2 = Цел(МодульНОД/НОД2);
Степень1 = Эйлера(Модуль1)[0]-1;
Степень2 = Эйлера(Модуль2)[0]-1;
Коэффициент1НОД1 = Цел(Коэффициент1НОД/НОД1);
Коэффициент2НОД2 = Цел(Коэффициент2НОД/НОД2);
Для Номер = 0 ПО МОсновной.Количество()-1 Цикл
Решение1 = СтепеньПоМодулю(Коэффициент1НОД1, Степень1, Модуль1) * МОсновной.Получить(Номер) % Модуль1;
Решение2 = СтепеньПоМодулю(Коэффициент2НОД2, Степень2, Модуль2) * Цел((СравнениеНОД-НОД1*МОсновной.Получить(Номер))/НОД2) % Модуль2;
Первый.Добавить(?(Решение1>Модуль/2, Решение1-Модуль, Решение1));
Второй.Добавить(?(Решение2>Модуль/2, Решение2-Модуль, Решение2));
Результат.Добавить(Коэффициент1*Первый[Номер] + Коэффициент2*Второй[Номер]);
КонецЦикла;
Ответ[0] = Первый[0];
Ответ[1] = Второй[0];
Для Номер = 1 ПО Результат.Количество()-1 Цикл
Если Pow(Результат[Номер-1], 2) > Pow(Результат[Номер], 2) Тогда
Ответ[0] = Первый[Номер];
Ответ[1] = Второй[Номер];
КонецЕсли;
КонецЦикла;
Возврат Ответ;
КонецФункции
Пояснения:
1. Функция РасчетНОД (Число1, Число2).
- возвращает Наибольший Общий Делитель двух чисел.
2. Функция Эйлера (Число).
- возвращает функцию Эйлера (количество чисел взаимопростых с данным и меньших его) для числа в следующем виде: Массив[КоличествоЧиселВзаимопростыхСДанным, МножествоЭтихСамыхЧиселВзаимопростыхСДанным], причем эти числа представляются в виде ?(Номер>Число/2, Номер-Число, Номер), где Номер - эти самые числа.
3. Функция СтепеньПоМодулю (Основание, Степень, Модуль).
- возвращает остаток от деления степени основания на текущий модуль.
4. Функция РешениеСравнения (Коэффициент, Сравнение, Модуль).
- возвращает решение линейного сравнения с одной переменной: Коэффициент*Решение = Сравнение (mod Модуль).
5. Функция РешениеСравненияЛинейное (Коэффициент1, Коэффициент2, Сравнение, НОД, Модуль).
- возвращает решение линейного сравнения с двумя переменными: Коэффициент1*Ответ1+Коэффициент2*Ответ2 = Сравнение (mod Модуль), при этом НОД = НОД(Коэффициент1, Коэффициент2, Модуль) (общий делитель трех чисел).
Далее идут процедуры которые вставляются непосредственно в объект, где надо округлять. Первая процедура вешается на созданую кнопку на форме объекта, а вторая - прописывается в модуле объекта (в моем случае это документ ЗаказПокупателя). Выделенное жирным заменяется согласно Вашему объекту.
//Получаем общую сумму и если в ней есть копейки, то будем округлять их до рублей.
//
Процедура КоманднаяПанельТоварыДействиеОкруглитьСтоимость(Кнопка)
Сумма = Товары.Итог("Сумма");
Если Сумма*100%100 <> 0 Тогда
Ответ = Вопрос("Перед округлением цен надо записать документ! Записать?", РежимДиалогаВопрос.ДаНет);
Если Ответ = КодВозвратаДиалога.Да Тогда
ЭтотОбъект.Записать();
ОкруглитьСуммуЗаказа();
КонецЕсли;
КонецЕсли;
КонецПроцедуры
//Для округления будем стараться выбирать товары с наибольшей ценой для уменьшения погрешности разницы округленной цены и текущей.
//Наценку будем брать так, чтобы она по модулю не превышала 50 копеек, опять же для уменьшения погрешности.
//
Процедура ОкруглитьСуммуЗаказа() Экспорт
Массив = Новый Массив(2);
Результаты = Новый Массив;
Запрос = Новый Запрос;
Текст = "ВЫБРАТЬ
| ЗаказПокупателяТовары.НомерСтроки КАК НомерСтроки,
| ЗаказПокупателяТовары.Количество,
| ЗаказПокупателяТовары.Цена КАК Цена,
| ЗаказПокупателяТовары.Сумма КАК Сумма
|ИЗ
| Документ.ЗаказПокупателя.Товары КАК ЗаказПокупателяТовары
|ГДЕ
| ЗаказПокупателяТовары.Ссылка = &Ссылка
|
|УПОРЯДОЧИТЬ ПО
| Цена УБЫВ
|ИТОГИ
| КОЛИЧЕСТВО(НомерСтроки),
| СУММА(Сумма)
|ПО
| ОБЩИЕ";
Запрос.Текст = Текст;
Запрос.УстановитьПараметр("Ссылка", Ссылка);
Выборка = Запрос.Выполнить().Выбрать(); //Вытаскиваем список всех товаров упорядоченых по убыванию цены
Если Выборка.Следующий() Тогда
СуммаИтого = Выборка.Сумма*100%100; //Получаем общую сумму и количество товаров
КоличествоИтого = Выборка.НомерСтроки;
КонецЕсли;
Если Выборка.Следующий() Тогда
Количество1 = Выборка.Количество%100; //Получаем первый, то есть самый дорогой товар
Цена1 = Выборка.Цена*100;
Строка1 = Выборка.НомерСтроки;
КонецЕсли;
НОД1 = АналитическийМодуль.РасчетНОД(Количество1, 100);
Если СуммаИтого%НОД1=0 Тогда
Наценка = АналитическийМодуль.РешениеСравнения(Количество1, СуммаИтого, 100);
Наценка1 = -(?(Наценка>50, Наценка-100, Наценка));
//Если количество самого дорогого товара подходит для решения сравнения,
//то есть его цену можно исправить так, чтобы исчезли копейки в сумме,
//то нам больше ничего и не надо, иначе - ищем второй подходящий товар
//(не трудно понять, что такой товар найдется для решения сравнения с двумя переменными,
//еслиб не нашлось, то текущей суммы бы не получилось при таких данных параметрах позиций товаров):
Иначе
Пока Выборка.Следующий() Цикл
Количество2 = Выборка.Количество%100;
НОД2 = АналитическийМодуль.РасчетНОД(Количество2, НОД1);
Если (СуммаИтого%НОД2=0) Тогда
Массив = АналитическийМодуль.РешениеСравненияЛинейное(Количество1, Количество2, СуммаИтого, НОД2, 100);
Строка2 = Выборка.НомерСтроки;
Наценка1 = -(?(Массив[0]>50, Массив[0]-100, Массив[0]));
Наценка2 = -(?(Массив[1]>50, Массив[1]-100, Массив[1]));
Прервать;
КонецЕсли;
КонецЦикла;
КонецЕсли;
ТекСтрока = Товары.Получить(Строка1-1);
ТекСтрока.Цена = ТекСтрока.Цена + Наценка1/100;
ОбработкаТабличныхЧастей.РассчитатьСуммуТабЧасти(ТекСтрока, ЭтотОбъект);
Если ТипЗнч(Строка2) <> Тип("Неопределено") Тогда
ТекСтрока = Товары.Получить(Строка2-1);
ТекСтрока.Цена = ТекСтрока.Цена + Наценка2/100;
ОбработкаТабличныхЧастей.РассчитатьСуммуТабЧасти(ТекСтрока, ЭтотОбъект);
КонецЕсли;
КонецПроцедуры
- данная система округления работает с "чистыми" ценами, без учета ручных и автоматических скидок, проставляемых отдельно в виде %, поэтому если на некоторые товары действует скидка, то приходится в ручную расчитывать цену со скидкой и прописывать её в колонку "Цена", а колонку "Скидка" не трогать.