Почему запрос 1SQLite в 1С 7.7 возвращает число 44.659... вместо 44.66 и как это исправить?

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

При выполнении запросов к базам данных 1С 7.7 с помощью компоненты 1SQlite часто возникает ситуация, когда числовые поля с дробной частью, например, цена или сумма, возвращаются с "хвостом" из множества знаков после запятой. Вместо ожидаемого значения 44.66 мы можем получить 44.6599999999999. Это происходит даже если в самой 1С для реквизита установлена точность в 2 знака. Разберемся, почему так происходит и как с этим бороться.

Причина №1: Фундаментальная проблема представления чисел в компьютере

Чтобы понять корень проблемы, нужно сделать небольшой экскурс в информатику. Мы, люди, привыкли к десятичной системе счисления. А компьютеры оперируют двоичной системой. Многие конечные десятичные дроби (например, 0.1 или 0.66) в двоичной системе превращаются в бесконечные периодические дроби. Это похоже на то, как дробь 1/3 в нашей десятичной системе становится бесконечной: 0.33333...

Поскольку память компьютера ограничена, для хранения таких "неточных" чисел используется общепринятый стандарт IEEE 754, описывающий числа с плавающей запятой (float, double). Этот стандарт позволяет хранить лишь приближенное значение бесконечной двоичной дроби. Именно эта погрешность аппроксимации и проявляется в виде "лишних" знаков после запятой.

Важно понимать: это не ошибка 1С или 1SQlite, а фундаментальная особенность работы практически всех языков программирования и систем при работе с нецелыми числами.

Причина №2: Цепочка преобразований данных в 1SQlite

Теперь давайте проследим путь нашего числа от DBF-файла до результата запроса. В этом процессе участвуют три стороны: движок 1С 7.7, компонента 1SQlite и движок СУБД SQLite.

  1. Чтение из DBF. Движок 1С читает данные из DBF-файла. Внутри 1С числовые типы данных с фиксированной точкой (как в реквизитах документов) хранятся точно, без потерь. На этом этапе у нас все еще есть корректное значение 44.66.
  2. Передача в SQLite. Компонента 1SQlite выступает посредником. Она получает точное число от 1С и должна передать его движку SQLite для обработки запроса. И здесь возникает ключевой момент: в самой СУБД SQLite нет специализированного типа данных для хранения десятичных чисел с фиксированной точностью (таких как DECIMAL или NUMERIC в "больших" СУБД). Для дробных чисел SQLite использует тип REAL, который по своей сути является 8-байтным числом с плавающей запятой двойной точности (double), работающим по стандарту IEEE 754.
  3. Потеря точности. 1SQlite вынуждена преобразовать точное десятичное число 44.66 в его ближайшее представление в формате double. В двоичной системе это будет то самое 44.6599999999999. Именно в этот момент и происходит потеря исходной точности.
  4. Возврат результата. SQLite выполняет все вычисления с уже неточным числом и возвращает результат обратно в 1SQlite, а та, в свою очередь, передает его в 1С в виде итоговой таблицы значений.

Таким образом, проблема возникает на "границе" между миром 1С, где арифметика десятичная и точная, и миром SQLite, где арифметика двоичная и приближенная.

Практическое решение: явная типизация полей в запросе

К счастью, решение проблемы довольно простое и элегантное. Необходимо явно указать компоненте 1SQlite, с какой точностью мы хотим получить числовое поле в итоговом результате. Это делается с помощью специального синтаксиса прямо в тексте запроса.

Посмотрим на пример. Изначальный запрос мог выглядеть так:


лТекстЗапроса = "
|SELECT
|   ...
|   ДокРеализацияТЧ.Цена,
|   ДокРеализацияТЧ.Сумма,
|   ДокРеализацияТЧ.СуммаНДС,
|   ...
|FROM
|   ДокументСтроки_Реализация as ДокРеализацияТЧ
|...";

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


лТекстЗапроса = "
|SELECT
|   ...
|   ДокРеализацияТЧ.Цена AS [Цена :Число.15.2],
|   ДокРеализацияТЧ.Сумма AS [Сумма :Число.15.2],
|   ДокРеализацияТЧ.СуммаНДС AS [СуммаНДС :Число.15.2],
|   ...
|FROM
|   ДокументСтроки_Реализация as ДокРеализацияТЧ
|...";

Как это работает?

Синтаксис [ИмяПоля :Число.Длина.Точность] является инструкцией не для SQLite, а именно для компоненты 1SQlite. Когда SQLite завершит свою работу и вернет неточное значение 44.6599999999999, 1SQlite увидит эту инструкцию и перед тем, как поместить значение в итоговую таблицу для 1С, выполнит принудительное округление до указанной точности (в нашем случае — до 2 знаков после запятой). В результате в 1С попадет уже корректное значение 44.66.

Это самый правильный и эффективный способ "скрыть" внутренние особенности преобразования типов и всегда получать ожидаемый результат.

Дополнительный нюанс: выгрузка данных с помощью "УложитьТЗ"

Стоит отметить, что та же проблема может возникнуть и при обратной операции — например, при выгрузке таблицы значений во внешний файл SQLite с помощью метода УложитьТЗ. В этом случае точные числа из 1С снова будут преобразованы в тип REAL SQLite, и в файле базы данных они сохранятся в неточном виде. Если затем вы будете читать эти данные, вам снова потребуется округление при чтении.

Итог: проблема неточности дробных чисел в 1SQlite — это не баг, а следствие фундаментальных принципов работы с числами с плавающей запятой. Лучшая практика — всегда явно типизировать числовые поля с дробной частью в SELECT-выражениях ваших запросов. Это гарантирует, что в 1С вы получите данные в том виде, в котором ожидаете.

← На главную