Почему функция Окр() в 1С 7.7 выдает разные результаты при изменении порядка вычислений?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

В практике программирования на платформе 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. Однако система ведет себя иначе. Выясним причину такого поведения.

Техническая причина: Особенности хранения чисел в 1С 7.7

Для понимания этого феномена нам необходимо заглянуть во внутреннее устройство движка платформы 7.7. В отличие от более современной платформы 1С 8.х, где для финансовых расчетов используется тип Decimal (число с фиксированной точкой), "семерка" во многих промежуточных операциях полагается на стандарт IEEE 754 (числа с плавающей точкой двойной точности — Double).

Проанализируем по шагам, что происходит в памяти компьютера в первом варианте:

  1. Выполняется операция 411.79 / 6. В десятичной системе это бесконечная дробь: 68.63166666...
  2. Поскольку память компьютера ограничена, это число сохраняется с определенной точностью. При использовании типа Double происходит микроскопическое отсечение "хвоста" дроби. В памяти фактически оказывается число, чуть меньшее, чем истинное значение (например, 68.63166666666666).
  3. Затем полученное промежуточное значение умножается на 3. Накопленная погрешность также увеличивается в три раза.
  4. В итоге вместо точного значения 205.895 система получает число вроде 205.8949999999999.
  5. Функция Окр(..., 2, 1) анализирует третий знак после запятой. Видя там цифру 4 (из-за накопленной ошибки), она округляет число вниз, получая 205.89.

Теперь рассмотрим второй вариант:

  1. Сначала выполняется умножение: 411.79 * 3. Получается точное число 1235.37, которое не имеет бесконечного хвоста и идеально ложится в разрядную сетку.
  2. Затем происходит деление: 1235.37 / 6. Результат — ровно 205.895.
  3. Функция Окр() видит в третьем разряде честную пятерку и корректно округляет число до 205.9.

Сравнение с 1С 8.х и SQL Server

Интересно отметить, что в платформе 1С 8.3 такой проблемы не возникает. Разработчики перешли на использование десятичного формата хранения чисел (BCD — Binary Coded Decimal), который обрабатывает дроби так же, как человек на бумаге, исключая погрешности двоичного представления.

Однако, если вы используете SQL-версию 1С 7.7, вы можете заметить аналогичное поведение при выполнении прямых запросов. В SQL Server поведение зависит от типов данных констант. Если сервер интерпретирует числа как FLOAT, результат будет плавающим. Если как DECIMAL или MONEY — результат будет точным. 1С 7.7 при передаче данных из БД в программный код часто конвертирует значения, что и приводит к воспроизведению ошибки даже на мощных СУБД.

Рекомендации по предотвращению ошибок округления

Для того чтобы минимизировать риск "пропажи копеек" в конфигурациях на базе 1С 7.7, разберем основные правила безопасных вычислений:

  1. Приоритет умножения над делением. Это золотое правило для 1С 7.7. Всегда старайтесь сначала перемножить все числители, а в самом конце выполнять деление на знаменатель. Это максимально отсрочит момент появления бесконечных периодических дробей.

    Плохо: (Сумма / Всего) * Доля

    Хорошо: (Сумма * Доля) / Всего

  2. Использование поправки (эпсилон). Если порядок действий изменить невозможно, опытные разработчики применяют добавление исчезающе малого числа перед округлением. Это помогает "подтолкнуть" число вверх, если из-за погрешности Double оно стало чуть меньше нормы.
    
    // Добавляем микро-поправку перед округлением
    ЧислоДляОкр = 411.79 / 6 * 3;
    Результат = Окр(ЧислоДляОкр + 0.00000000001, 2, 1);
    
  3. Промежуточное округление. В очень длинных цепочках вычислений имеет смысл принудительно округлять промежуточные результаты до большой точности (например, до 6 или 10 знаков), чтобы отсечь мусорные значения в дальних разрядах, которые могут повлиять на итоговый результат.
  4. Проверка метода округления. Помните, что третий параметр функции Окр() крайне важен. Значение 1 гарантирует арифметическое округление (0.5 округляется до 1). Значение 0 реализует так называемое "банковское" округление (до ближайшего четного), что может еще больше запутать ситуацию, если вы ожидаете стандартного поведения.

Резюме

Мы выяснили, что странная работа функции Окр() в 1С 7.7 — это не программная ошибка (баг) в чистом виде, а следствие фундаментального ограничения представления дробных чисел в компьютерной памяти. Главный вывод: при написании кода для финансовых модулей в 1С 7.7 всегда контролируйте последовательность операций и отдавайте предпочтение умножению перед делением. Это простое правило убережет вас от длительного поиска "потерянных" копеек в бухгалтерском балансе.

← На главную