В работе программиста 1С 7.7, особенно при поддержке конфигурации "Торговля и Склад" (ТиС), рано или поздно возникает странная ситуация. При расчете сумм списания или налогов функция Окр() ведет себя нелогично: математически верное число, оканчивающееся на "5", округляется вниз вместо ожидаемого округления вверх. Например, выражение Окр(117.415, 2, 1) внезапно возвращает 117.41.
В этой статье мы подробно разберем, почему возникает этот "баг", как его увидеть в отладчике и какими способами можно гарантировать точность взаиморасчетов и складских остатков.
Разберем ситуацию, описанную в теме. У нас есть произведение ТаблИтогов.СуммаБезНДС * КоэффСписания. Допустим, результат выглядит как 117.415. Однако при попытке округлить его до двух знаков мы получаем 117.41. Если же мы напишем в коде явно Окр(117.415, 2, 1), результат будет корректным — 117.42.
В чем секрет? Все дело в том, как платформа 1С 7.7 хранит числа в оперативной памяти. В отличие от базы данных, где числа хранятся с фиксированной точностью (например, 15 знаков всего и 2 после запятой), для промежуточных вычислений в памяти используется тип Double (число с плавающей точкой двойной точности по стандарту IEEE 754).
Проанализируем математическую суть: десятичные дроби не всегда могут быть точно представлены в двоичной системе счисления. Число 117.415 в памяти компьютера на самом деле может выглядеть как 117.4149999999999999. Для функции Окр() это число меньше, чем 117.415, поэтому по правилам математики оно округляется до ближайшего меньшего — до 117.41.
Многие разработчики впадают в ступор, когда видят в табло отладчика значение 117.415, но получают результат округления 117.41. Посмотрим на ситуацию глубже: отладчик 1С 7.7 при выводе значений часто делает собственное неявное округление, чтобы "не пугать" пользователя длинными хвостами из девяток. Чтобы увидеть реальное значение переменной, рекомендуется использовать функцию Формат() с большой точностью или преобразовать число в строку.
Рассмотрим пример кода для проверки реального значения:
ТекБезНДС = ТаблИтогов.СуммаБезНДС * КоэффСписания;
// Посмотрим значение с точностью до 15 знаков после запятой
Сообщить("Реальное значение: " + Формат(ТекБезНДС, "Ч0.15"));
Скорее всего, вместо 117.415 вы увидите нечто вроде 117.414999999999. Это и объясняет поведение функции округления.
Одним из самых простых и эффективных способов решения проблемы, предложенных в обсуждении, является двойное округление. Мы сначала округляем число до промежуточной точности (например, до 3 или 4 знаков), тем самым "подтягивая" хвост из девяток к пятерке, а затем уже округляем до целевых 2 знаков.
Посмотрим на пример реализации в модуле проведения документа (например, в процедуре глСписаниеПартийТМЦ):
// Вместо прямой записи:
// РегПартии.СуммаБезНДС = Окр(ТаблИтогов.СуммаБезНДС * КоэффСписания, 2, 1);
// Используем промежуточную переменную с двойным округлением:
ТекСуммаБезНДС = Окр(ТаблИтогов.СуммаБезНДС * КоэффСписания, 3, 1);
// Теперь в ТекСуммаБезНДС гарантированно лежит 117.415
РегПартии.СуммаБезНДС = Окр(ТекСуммаБезНДС, 2, 1);
// Результат: 117.42, что и требовалось.
Важный момент: Промежуточная точность должна быть на один-два знака больше целевой, но меньше точности множителей, чтобы не потерять значимые данные.
В профессиональной среде разработчиков часто применяется метод добавления ничтожно малого числа (эпсилон) перед округлением. Это число должно быть достаточно малым, чтобы не повлиять на значащие разряды, но достаточно большим, чтобы "превратить" ...4999999 в ...5000000.
Проанализируем пример:
Погрешность = 0.0000000001; // Тот самый эпсилон
ТекЗначение = ТаблИтогов.СуммаБезНДС * КоэффСписания;
Результат = Окр(ТекЗначение + Погрешность, 2, 1);
Этот метод считается более изящным, так как он работает в один проход функции округления и страхует от любых микроскопических потерь точности Double.
Если мы хотим полностью исключить влияние специфики Double, мы можем принудительно отсечь "мусорные" знаки, преобразовав число в строку и обратно. Механизм форматирования в 1С 7.7 работает достаточно стабильно в плане отсечения лишних разрядов.
Рассмотрим по шагам:
Формат() с нужным количеством знаков (например, 3).
ТекЗначение = ТаблИтогов.СуммаБезНДС * КоэффСписания;
// Очищаем "хвост" через строку
ЧистоеЗначение = Число(Формат(ТекЗначение, "Ч0.3"));
РегПартии.СуммаБезНДС = Окр(ЧистоеЗначение, 2, 1);
При внесении изменений в механизмы округления типовой конфигурации "Торговля и Склад", особенно в глобальном модуле или процедурах списания партий, нужно быть предельно осторожными. Рассмотрим основные риски:
1. Расхождение с печатными формами. Если вы измените алгоритм проведения, но оставите стандартный расчет в печатной форме (например, в ТОРГ-12 или Счет-фактуре), у клиента могут возникнуть расхождения в копейках между бумагой и данными базы.
2. Проблема закрытия регистров. В 1С 7.7 крайне важно, чтобы при списании последней единицы товара остаток по сумме тоже закрывался в ноль. Если ваш новый алгоритм округления "накинет" лишнюю копейку, на регистре может остаться "зависшее" сальдо. Поэтому, как справедливо замечено в обсуждении (сообщение 19), при полном списании остатков лучше не использовать КоэффСписания, а списывать остаток суммы ресурса напрямую.
3. Расчет НДС. Копейки в НДС — самая чувствительная тема для бухгалтерии. Любое изменение логики должно быть согласовано с учетной политикой. Часто бывает, что сумма НДС "от итога" документа не совпадает с суммой "построчно" именно из-за таких нюансов округления.
Мы проанализировали ситуацию и выяснили, что ошибка округления в 1С 7.7 — это не программный баг в чистом виде, а особенность работы с числами с плавающей точкой. Для решения проблемы мы рекомендуем:
Используя эти методы, вы обеспечите математическую точность ваших отчетов и отсутствие претензий со стороны бухгалтерии из-за "потерянных" или "лишних" копеек в учете.