При работе с мультивалютными документами в 1С разработчики часто сталкиваются с неприятной проблемой: при расчете скидок в иностранной валюте и последующем пересчете сумм обратно в рубли «разъезжаются» копейки. Обычные математические формулы, которые отлично работают на бумаге, в коде 1С начинают давать погрешности. Происходит это из-за двойного округления и особенностей хранения чисел с плавающей точкой.
В этой статье мы подробно разберем причины возникновения таких ошибок, проанализируем математическую суть погрешностей и выработаем единый правильный алгоритм расчета скидок в валюте, который исключит расхождение копеек в регламентированном и управленческом учете.
Давайте проанализируем ситуацию, которую описал автор темы. Исходный код выглядит следующим образом:
СуммаСкидкиВВалюте = ТекДанные.СуммаВВалюте * ТекДанные.Скидка / 100;
ТекДанные.СуммаВВалюте = ТекДанные.СуммаВВалюте - СуммаСкидкиВВалюте;
На первый взгляд, формула абсолютно верна. Однако при ее использовании на практике неизбежно возникнут расхождения с рублевой суммой. Выделим две главные причины, почему это происходит:
99.990000000002 вместо 99.99). При умножении этой суммы на курс валюты погрешность накопится и превратится в реальную ошибку в рублевых копейках.Рассмотрим наглядный пример. Пусть исходная сумма в рублях равна 100 руб., курс валюты составляет 85.50, а скидка — 5%.
Если мы сначала переведем рубли в валюту: 100 / 85.50 = 1.16959... После округления до 2 знаков получаем 1.17 у.е.
Рассчитаем скидку в валюте: 1.17 * 5 / 100 = 0.0585. После округления — 0.06 у.е.
Итоговая сумма в валюте со скидкой: 1.17 - 0.06 = 1.11 у.е.
Переведем результат обратно в рубли: 1.11 * 85.50 = 94.905. После округления — 94.91 руб.
А теперь посчитаем то же самое напрямую в рублях: 100 - (100 * 5 / 100) = 95.00 руб. Разница составила 9 копеек просто «из воздуха»!
---
Первое и самое важное правило при работе с любыми денежными показателями в 1С — всегда использовать функцию Окр(). Каждое действие (умножение на процент, вычитание, деление на курс) должно завершаться округлением до 2 знаков после запятой.
В платформе 1С для финансовых расчетов рекомендуется использовать специальный режим округления — РежимОкругления.Окр15_2 (так называемое бухгалтерское округление). Перепишем исходную формулу автора с учетом этого правила:
// Правильный расчет скидки в валюте с явным округлением
СуммаСкидкиВВалюте = Окр(ТекДанные.СуммаВВалюте * ТекДанные.Скидка / 100, 2, РежимОкругления.Окр15_2);
ТекДанные.СуммаВВалюте = ТекДанные.СуммаВВалюте - СуммаСкидкиВВалюте;
Этот шаг избавит базу данных от хранения чисел с плавающей точкой и исключит появление дробных «хвостов» глубоко после запятой.
---
Для того чтобы избежать коллизий при пересчете валют, необходимо раз и навсегда определить первичную валюту документа (валюту взаиморасчетов). Попытка параллельно считать скидку и в рублях, и в валюте — это методологическая ошибка.
Давайте разберем два возможных сценария в зависимости от условий договора с контрагентом:
Если договор валютный, то первичными являются показатели в иностранной валюте. Расчет выполняем строго по следующей схеме:
Посмотрим, как выглядит программная реализация этого сценария:
// 1. Расчет показателей в валюте (первичные данные)
СуммаСкидкиВВалюте = Окр(ТекДанные.СуммаВВалюте * ТекДанные.Скидка / 100, 2, РежимОкругления.Окр15_2);
ТекДанные.СуммаВВалюте = ТекДанные.СуммаВВалюте - СуммаСкидкиВВалюте;
// 2. Пересчет итоговой суммы в рубли по курсу (производные данные)
// Кратность валюты обязательно должна учитываться в расчете!
Если КратностьВалюты <> 0 Тогда
ТекДанные.СуммаВРублях = Окр((ТекДанные.СуммаВВалюте * КурсВалюты) / КратностьВалюты, 2, РежимОкругления.Окр15_2);
КонецЕсли;
Если договор рублевый, но в табличной части для справки или по требованию пользователя выводится валютная сумма, то первичными являются рубли. Схема расчета меняется на противоположную:
Рассмотрим код для рублевого договора:
// 1. Расчет показателей в рублях (первичные данные)
СуммаСкидкиВРублях = Окр(ТекДанные.СуммаВРублях * ТекДанные.Скидка / 100, 2, РежимОкругления.Окр15_2);
ТекДанные.СуммаВРублях = ТекДанные.СуммаВРублях - СуммаСкидкиВРублях;
// 2. Получение справочной суммы в валюте
Если КурсВалюты <> 0 Тогда
ТекДанные.СуммаВВалюте = Окр((ТекДанные.СуммаВРублях * КратностьВалюты) / КурсВалюты, 2, РежимОкругления.Окр15_2);
КонецЕсли;
При таком подходе у вас никогда не возникнет расхождений в копейках по основному учету. Вспомогательная валюта может слегка отличаться от «идеального» математического пересчета, но для учета это не имеет значения, так как первичная валюта договора сошлась идеально.
---
Разработчики фирмы «1С» в современных ERP-системах (1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей) применяют жесткие стандарты для минимизации погрешностей округления. Давайте проанализируем их лучшие практики:
Цена * Количество = Сумма может давать копейки погрешности после применения скидки. Поэтому программа сначала рассчитывает новую сумму строки со скидкой, округляет ее, а затем «обратным делением» вычисляет новую цену за единицу: НоваяЦена = Окр(НоваяСумма / Количество, 2).Подводя итог, можно сказать: чтобы расчет скидки в валюте был правильным, всегда используйте явное округление Окр() с точностью 2 знака, четко разделяйте первичную валюту расчетов и производную, и производите пересчет только в одну сторону — от первичной к производной.