При выполнении запросов к базам данных 1С 7.7 с помощью компоненты 1SQlite часто возникает ситуация, когда числовые поля с дробной частью, например, цена или сумма, возвращаются с "хвостом" из множества знаков после запятой. Вместо ожидаемого значения 44.66 мы можем получить 44.6599999999999. Это происходит даже если в самой 1С для реквизита установлена точность в 2 знака. Разберемся, почему так происходит и как с этим бороться.
Чтобы понять корень проблемы, нужно сделать небольшой экскурс в информатику. Мы, люди, привыкли к десятичной системе счисления. А компьютеры оперируют двоичной системой. Многие конечные десятичные дроби (например, 0.1 или 0.66) в двоичной системе превращаются в бесконечные периодические дроби. Это похоже на то, как дробь 1/3 в нашей десятичной системе становится бесконечной: 0.33333...
Поскольку память компьютера ограничена, для хранения таких "неточных" чисел используется общепринятый стандарт IEEE 754, описывающий числа с плавающей запятой (float, double). Этот стандарт позволяет хранить лишь приближенное значение бесконечной двоичной дроби. Именно эта погрешность аппроксимации и проявляется в виде "лишних" знаков после запятой.
Важно понимать: это не ошибка 1С или 1SQlite, а фундаментальная особенность работы практически всех языков программирования и систем при работе с нецелыми числами.
Теперь давайте проследим путь нашего числа от DBF-файла до результата запроса. В этом процессе участвуют три стороны: движок 1С 7.7, компонента 1SQlite и движок СУБД SQLite.
44.66.DECIMAL или NUMERIC в "больших" СУБД). Для дробных чисел SQLite использует тип REAL, который по своей сути является 8-байтным числом с плавающей запятой двойной точности (double), работающим по стандарту IEEE 754.44.66 в его ближайшее представление в формате double. В двоичной системе это будет то самое 44.6599999999999. Именно в этот момент и происходит потеря исходной точности.Таким образом, проблема возникает на "границе" между миром 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С вы получите данные в том виде, в котором ожидаете.