Почему числовые значения из Access меняются при получении в 1С через ADODB и как это исправить

Программист 1С v8.3 (Обычные формы)
← На главную

При интеграции 1С с внешними базами данных Microsoft Access через механизм ADODB.Connection программисты часто сталкиваются с неприятным эффектом: точные дробные числа (например, 0.005) при получении в 1С превращаются в длинные последовательности знаков после запятой (0.00499999988824129). Это создает сложности при сравнении значений, проведении расчетов и записи данных в информационную базу.

В этой статье мы подробно разберем, почему возникает такая погрешность, и рассмотрим несколько эффективных способов решения проблемы на уровне базы данных Access, SQL-запроса и кода 1С.

Выясним причину возникновения погрешности

Проанализируем ситуацию: корень проблемы кроется не в ошибке 1С или драйвера ADO, а в фундаментальных различиях хранения чисел в памяти компьютера. В Microsoft Access типы данных "Одинарное с плавающей точкой" (Single) и "Двойное с плавающей точкой" (Double) соответствуют стандарту IEEE 754.

Эти числа хранятся в двоичной системе счисления. К сожалению, многие десятичные дроби (включая 0.005) невозможно представить в двоичном виде абсолютно точно. Они превращаются в бесконечную периодическую дробь. Компьютер вынужден обрезать эту дробь, сохраняя лишь ближайшее максимально похожее число. Когда 1С обращается к COM-объекту Recordset, она получает это "приближенное" значение. Поскольку тип Число в 1С основан на десятичном представлении с фиксированной точностью, платформа честно отображает все те микроскопические отклонения, которые накопились при хранении числа в двоичном формате Double или Single.

Важно понимать: замена Single на Double (как было замечено в обсуждении темы) не решает проблему принципиально. Она лишь отодвигает "хвост" из девяток или единиц в более дальние разряды (например, в 15-й знак после запятой вместо 7-го), но сама природа неточного хранения остается неизменной.

Решение 1: Изменение типа данных в Access (наиболее надежный способ)

Если у нас есть возможность изменять структуру исходной базы данных Access, это самый правильный путь. Рассмотрим, на какие типы стоит заменить "плавающую точку":

  1. Тип "Десятичное" (Decimal): Этот тип данных хранит числа в десятичном формате. В отличие от Double, он не страдает от двоичной аппроксимации. При настройке поля укажите параметр "Шкала" (Scale), который жестко определяет количество знаков после запятой. ADO корректно передаст такое число в 1С без искажений.
  2. Тип "Денежный" (Currency): Несмотря на название, этот тип отлично подходит для любых точных расчетов. Внутри он представляет собой целое число, которое всегда делится на 10 000. Это обеспечивает фиксированную точность до 4 знаков после запятой. Если четырех знаков достаточно для ваших задач, Currency — самый стабильный вариант для передачи через COM-соединение.

Решение 2: Преобразование в строку в SQL-запросе

Зачастую программист не имеет доступа к изменению структуры таблиц (например, база принадлежит стороннему ПО). В этом случае мы можем принудительно преобразовать число в строку еще на стороне драйвера Access. Рассмотрим, как изменить исходный код:

Вместо прямого выбора поля SELECT * FROM FinTool, мы используем встроенную функцию Access SQL — CStr(). Проанализируем пример кода:


Конект = Новый COMОбъект("ADODB.Connection");  
Конект.Provider = "MSDASQL.1";
Конект.Open("Driver={Microsoft Access Driver (*.mdb)};Dbq=" + СокрЛП(ИмяФайла) + ";");

// Преобразуем числовое поле в строку прямо в запросе
ТекстЗапроса = "SELECT CStr(AccCouponInc) КАК AccCouponIncString FROM FinTool WHERE FinToolID='4043'";
Recordset = Конект.Execute(ТекстЗапроса);

Если Не Recordset.EOF Тогда
    // Получаем строку "0.005" и преобразуем её в число 1С
    ЗначениеИзБазы = Число(Recordset.Fields("AccCouponIncString").Value);
    Сообщить(ЗначениеИзБазы); // Теперь здесь будет ровно 0.005
КонецЕсли;

Этот метод полностью исключает влияние механизмов передачи Double, так как данные передаются в текстовом виде, который 1С интерпретирует однозначно.

Решение 3: Округление на стороне SQL

Если мы заранее знаем максимальную точность данных (например, не более 6 знаков), мы можем применить функцию ROUND() непосредственно в тексте запроса. Разберем этот вариант:


// Округляем до 6 знаков после запятой средствами SQL
ТекстЗапроса = "SELECT ROUND(AccCouponInc, 6) AS FixedValue FROM FinTool";
Recordset = Конект.Execute(ТекстЗапроса);

Хотя автор темы сомневался в округлении из-за разной точности данных, использование заведомо большего числа знаков в функции ROUND (например, 8 или 10) позволит отсечь "мусорные" разряды, возникшие из-за специфики плавающей точки, сохранив при этом полезную точность.

Решение 4: Программная "очистка" числа в 1С

Если вы уже получили "грязное" число в переменную 1С и не хотите менять запрос, можно воспользоваться особенностью преобразования типов в самой платформе. Рассмотрим небольшой лайфхак. При преобразовании числа в строку 1С часто выполняет внутреннее сглаживание микроскопических погрешностей.

Посмотрим на пример функции для "очистки" таких чисел:


Функция ОчиститьЧислоADO(Значение)
    // Преобразуем в строку и обратно в число для отсечения погрешностей Double
    Возврат Число(Строка(Значение));
КонецФункции

// Применение
Поле = Recordset.Fields("AccCouponInc").Value; // Получили 0,00499999988824129
ТочноеЗначение = ОчиститьЧислоADO(Поле);       // Стало 0,005

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

Решение 5: Обновление драйвера и провайдера

В исходном коде используется устаревший провайдер MSDASQL.1 (мост для ODBC). Проанализируем возможность перехода на более современный Microsoft.ACE.OLEDB. Современные версии драйверов (например, версии 12.0 или 16.0) более корректно обрабатывают сопоставление типов данных между Access и COM-окружением.

Пример изменения строки подключения:


// Использование ACE OLEDB провайдера
Конект.Provider = "Microsoft.ACE.OLEDB.12.0";
Конект.ConnectionString = "Data Source=" + СокрЛП(ИмяФайла) + ";Persist Security Info=False;";
Конект.Open();

Подведем итоги

Для предотвращения искажения числовых данных при работе с ADODB.Connection мы рекомендуем придерживаться следующей стратегии:

  1. Если база данных ваша — используйте тип данных Decimal или Currency в Access.
  2. Если база чужая — используйте преобразование CStr() в тексте SQL-запроса.
  3. В качестве "быстрой заплатки" применяйте двойное преобразование Число(Строка(Значение)) в коде 1С.

Помните, что работа с типами с плавающей точкой (Single/Double) всегда требует особого внимания к округлению, независимо от используемого языка программирования.

← На главную