В практике программирования на платформе 1С:Предприятие 7.7 разработчики часто сталкиваются с труднообъяснимыми на первый взгляд погрешностями в расчетах. Типичная ситуация: при вычислении суммы, где участвуют деление и последующее умножение, итоговый результат после округления может отличаться на одну копейку в зависимости от того, в какой последовательности выполнялись арифметические действия. Рассмотрим, почему это происходит и как обеспечить точность финансовых показателей.
Давайте проанализируем классический пример, который ставит в тупик многих специалистов. Допустим, нам необходимо вычислить значение выражения и округлить его до двух знаков после запятой. Проведем эксперимент с использованием функции Окр() в двух вариантах написания одной и той же математической формулы:
Вариант №1 (Сначала деление, потом умножение):
Результат1 = Окр(411.79 / 6 * 3, 2, 1);
// В 1С 7.7 Результат1 будет равен 205.89
Вариант №2 (Сначала умножение, потом деление):
Результат2 = Окр(411.79 * 3 / 6, 2, 1);
// В 1С 7.7 Результат2 будет равен 205.9
С точки зрения чистой математики оба выражения идентичны и должны давать 205.895, что при арифметическом округлении до двух знаков превращается в 205.90. Однако система ведет себя иначе. Выясним причину такого поведения.
Для понимания этого феномена нам необходимо заглянуть во внутреннее устройство движка платформы 7.7. В отличие от более современной платформы 1С 8.х, где для финансовых расчетов используется тип Decimal (число с фиксированной точкой), "семерка" во многих промежуточных операциях полагается на стандарт IEEE 754 (числа с плавающей точкой двойной точности — Double).
Проанализируем по шагам, что происходит в памяти компьютера в первом варианте:
411.79 / 6. В десятичной системе это бесконечная дробь: 68.63166666...Double происходит микроскопическое отсечение "хвоста" дроби. В памяти фактически оказывается число, чуть меньшее, чем истинное значение (например, 68.63166666666666).3. Накопленная погрешность также увеличивается в три раза.205.895 система получает число вроде 205.8949999999999.Окр(..., 2, 1) анализирует третий знак после запятой. Видя там цифру 4 (из-за накопленной ошибки), она округляет число вниз, получая 205.89.Теперь рассмотрим второй вариант:
411.79 * 3. Получается точное число 1235.37, которое не имеет бесконечного хвоста и идеально ложится в разрядную сетку.1235.37 / 6. Результат — ровно 205.895.Окр() видит в третьем разряде честную пятерку и корректно округляет число до 205.9.Интересно отметить, что в платформе 1С 8.3 такой проблемы не возникает. Разработчики перешли на использование десятичного формата хранения чисел (BCD — Binary Coded Decimal), который обрабатывает дроби так же, как человек на бумаге, исключая погрешности двоичного представления.
Однако, если вы используете SQL-версию 1С 7.7, вы можете заметить аналогичное поведение при выполнении прямых запросов. В SQL Server поведение зависит от типов данных констант. Если сервер интерпретирует числа как FLOAT, результат будет плавающим. Если как DECIMAL или MONEY — результат будет точным. 1С 7.7 при передаче данных из БД в программный код часто конвертирует значения, что и приводит к воспроизведению ошибки даже на мощных СУБД.
Для того чтобы минимизировать риск "пропажи копеек" в конфигурациях на базе 1С 7.7, разберем основные правила безопасных вычислений:
Плохо: (Сумма / Всего) * Доля
Хорошо: (Сумма * Доля) / Всего
Double оно стало чуть меньше нормы.
// Добавляем микро-поправку перед округлением
ЧислоДляОкр = 411.79 / 6 * 3;
Результат = Окр(ЧислоДляОкр + 0.00000000001, 2, 1);
Окр() крайне важен. Значение 1 гарантирует арифметическое округление (0.5 округляется до 1). Значение 0 реализует так называемое "банковское" округление (до ближайшего четного), что может еще больше запутать ситуацию, если вы ожидаете стандартного поведения.Мы выяснили, что странная работа функции Окр() в 1С 7.7 — это не программная ошибка (баг) в чистом виде, а следствие фундаментального ограничения представления дробных чисел в компьютерной памяти. Главный вывод: при написании кода для финансовых модулей в 1С 7.7 всегда контролируйте последовательность операций и отдавайте предпочтение умножению перед делением. Это простое правило убережет вас от длительного поиска "потерянных" копеек в бухгалтерском балансе.