Как правильно рассчитать скидку в валюте и избежать расхождения копеек в 1С?

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

При работе с мультивалютными документами в 1С разработчики часто сталкиваются с неприятной проблемой: при расчете скидок в иностранной валюте и последующем пересчете сумм обратно в рубли «разъезжаются» копейки. Обычные математические формулы, которые отлично работают на бумаге, в коде 1С начинают давать погрешности. Происходит это из-за двойного округления и особенностей хранения чисел с плавающей точкой.

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

Выясняем причину: почему возникает погрешность?

Давайте проанализируем ситуацию, которую описал автор темы. Исходный код выглядит следующим образом:


СуммаСкидкиВВалюте     = ТекДанные.СуммаВВалюте * ТекДанные.Скидка / 100;
ТекДанные.СуммаВВалюте = ТекДанные.СуммаВВалюте - СуммаСкидкиВВалюте;

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

  1. Отсутствие явного округления в коде. В платформе 1С при выполнении математических операций деления и умножения числа могут рассчитываться с точностью до 15 знаков после запятой (так называемый тип float). Если мы не округлим результат принудительно, в базу данных запишется число с «невидимыми» микроскопическими долями (например, 99.990000000002 вместо 99.99). При умножении этой суммы на курс валюты погрешность накопится и превратится в реальную ошибку в рублевых копейках.
  2. Проблема двойного (и тройного) округления. Денежные суммы в 1С всегда должны быть округлены до 2 знаков после запятой. Если мы сначала пересчитываем рубли в валюту (происходит округление №1), затем рассчитываем скидку в валюте (округление №2), а потом пытаемся перевести полученную валютную сумму со скидкой обратно в рубли по курсу (округление №3) — математическое равенство нарушается. Итоговые рубли «до скидки минус сама скидка» никогда не совпадут с результатом обратного пересчета валюты.

Рассмотрим наглядный пример. Пусть исходная сумма в рублях равна 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. Применяем правило явного округления

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

В платформе 1С для финансовых расчетов рекомендуется использовать специальный режим округления — РежимОкругления.Окр15_2 (так называемое бухгалтерское округление). Перепишем исходную формулу автора с учетом этого правила:


// Правильный расчет скидки в валюте с явным округлением
СуммаСкидкиВВалюте = Окр(ТекДанные.СуммаВВалюте * ТекДанные.Скидка / 100, 2, РежимОкругления.Окр15_2);
ТекДанные.СуммаВВалюте = ТекДанные.СуммаВВалюте - СуммаСкидкиВВалюте;

Этот шаг избавит базу данных от хранения чисел с плавающей точкой и исключит появление дробных «хвостов» глубоко после запятой.

---

Шаг 2. Определяем базовую валюту (Приоритет расчетов)

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

Давайте разберем два возможных сценария в зависимости от условий договора с контрагентом:

Сценарий А: Договор ведется в валюте (или у.е.)

Если договор валютный, то первичными являются показатели в иностранной валюте. Расчет выполняем строго по следующей схеме:

  1. Вводим сумму в валюте.
  2. Рассчитываем сумму скидки в валюте и округляем до 2 знаков.
  3. Вычисляем итоговую сумму в валюте со скидкой.
  4. И только после этого пересчитываем полученный итоговый результат в рубли по курсу для целей регламентированного учета.

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


// 1. Расчет показателей в валюте (первичные данные)
СуммаСкидкиВВалюте = Окр(ТекДанные.СуммаВВалюте * ТекДанные.Скидка / 100, 2, РежимОкругления.Окр15_2);
ТекДанные.СуммаВВалюте = ТекДанные.СуммаВВалюте - СуммаСкидкиВВалюте;

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

Сценарий Б: Договор ведется в рублях

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

  1. Все скидки и итоговые суммы рассчитываются строго в рублях с округлением до 2 знаков.
  2. Валютные колонки заполняются исключительно как справочные (производные) путем деления готовой рублевой суммы на курс.

Рассмотрим код для рублевого договора:


// 1. Расчет показателей в рублях (первичные данные)
СуммаСкидкиВРублях = Окр(ТекДанные.СуммаВРублях * ТекДанные.Скидка / 100, 2, РежимОкругления.Окр15_2);
ТекДанные.СуммаВРублях = ТекДанные.СуммаВРублях - СуммаСкидкиВРублях;

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

При таком подходе у вас никогда не возникнет расхождений в копейках по основному учету. Вспомогательная валюта может слегка отличаться от «идеального» математического пересчета, но для учета это не имеет значения, так как первичная валюта договора сошлась идеально.

---

Как данная проблема решается в типовых конфигурациях 1С?

Разработчики фирмы «1С» в современных ERP-системах (1С:ERP, 1С:Комплексная автоматизация, 1С:Управление торговлей) применяют жесткие стандарты для минимизации погрешностей округления. Давайте проанализируем их лучшие практики:

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

← На главную