Почему не записывается числовое значение из DBF-файла в таблицу значений 1С и как это исправить?

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

При разработке обработок загрузки или обмена данными программисты часто используют чтение файлов формата DBF с помощью встроенного объекта платформы XBase. На первый взгляд, это простой и надежный механизм. Однако при попытке записать считанные числа в строго типизированную колонку таблицы значений можно столкнуться с крайне неприятным поведением: в отладчике переменная содержит корректное число (например, 1), но после записи в ячейку таблицы значений там оказывается обыкновенный 0.

В этой статье мы подробно разберем, почему возникает этот феномен, как особенности представления чисел с плавающей точкой влияют на платформу 1С:Предприятие, и рассмотрим несколько надежных способов обхода этой проблемы.

Симптомы проблемы: разбираем исходный код

Рассмотрим классический пример кода, в котором воспроизводится данное поведение. Мы создаем таблицу значений, жестко типизируем ее колонку под однозначное целое число и пытаемся заполнить строку данными из DBF-файла:


КЧ_1_ = Новый КвалификаторыЧисла(1, 0); // Длина 1, точность 0 (целое число от 0 до 9)
Массив = Новый Массив;
Массив.Добавить(Тип("Число"));
ОписаниеТиповЧ_1_ = Новый ОписаниеТипов(Массив, , , КЧ_1_);

ТЗ_ = Новый ТаблицаЗначений;
ТЗ_.Колонки.Добавить("OBRAZ", ОписаниеТиповЧ_1_);

НС = ТЗ_.Добавить();
НС.OBRAZ = Число(ДБФ1.OBRAZ); // Здесь в ТЗ записывается 0, хотя в DBF лежит 1

При анализе ситуации разработчики обычно проверяют следующие моменты:

  1. Что возвращает база данных? В отладчике значение свойства ДБФ1.OBRAZ равно 1, тип значения — Число.
  2. Помогает ли принудительное приведение типов? Использование конструкции Число(ДБФ1.OBRAZ) не меняет ситуацию — в таблице значений все равно остается 0.
  3. Работает ли прямая запись литерала? Если написать НС.OBRAZ = 1;, то значение успешно записывается.

Ситуация кажется парадоксальной: и там, и там число 1, но в одном случае оно записывается в колонку таблицы значений, а в другом — игнорируется платформой и сбрасывается в дефолтное значение ячейки (ноль).

Выясняем техническую причину проблемы

Корни этой проблемы лежат на стыке двух систем: внутреннего представления вещественных чисел в драйвере XBase и строгого контроля типов со стороны платформы 1С:Предприятие.

1. Особенности представления чисел в двоичной системе (IEEE 754)

Объект XBase считывает числовые поля (тип numeric в DBF) как числа двойной точности с плавающей точкой (double precision float). В двоичной системе счисления невозможно абсолютно точно представить большинство десятичных дробей. Из-за этого число, которое физически записано в файле DBF как целая единица 1, после считывания в оперативную память драйвером базы данных превращается в бесконечную дробь вида 0.9999999999999999 или 1.0000000000000001.

2. Иллюзия отладчика 1С

Почему же в табло отладчика мы видим красивую и точную единицу? Дело в том, что отладчик 1С производит автоматическое дружественное округление числовых значений перед выводом их на экран программисту. Он скрывает микроскопическую погрешность, чтобы не перегружать интерфейс. В итоге разработчик уверен, что оперирует чистой единицей, хотя в системной памяти переменная содержит "хвост" из девяток.

3. Поведение функции Число()

Функция Число() предназначена для преобразования типов (например, из строки в число). Если на вход функции передается значение, которое уже имеет тип Число, платформа возвращает его "как есть" без каких-либо математических округлений или модификаций. Таким образом, конструкция Число(ДБФ1.OBRAZ) возвращает все то же неточное значение 0.9999999999999999.

4. Жесткие ограничения Квалификаторов Числа

Когда мы создаем описание типов с квалификаторами КвалификаторыЧисла(1, 0), мы принудительно заставляем ячейку таблицы значений принимать только числа, состоящие максимум из одного разряда и не имеющие дробной части. При попытке присвоить этой ячейке значение 0.9999999999999999 происходит следующее:

  1. Платформа пытается вписать число в рамки ограничений.
  2. Для обеспечения точности 0 (целое число) платформа отсекает дробную часть (производит операцию, аналогичную функции Цел()).
  3. После отсечения дробной части от числа 0.9999999999999999 остается только целая часть, то есть 0. Именно этот ноль мы и наблюдаем в таблице.

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

Варианты решения проблемы

Для того чтобы обойти особенности работы с плавающей точкой и заставить платформу корректно записывать данные в типизированную таблицу значений, мы можем использовать несколько проверенных подходов. Рассмотрим их по шагам.

Способ 1. Явное математическое округление (Рекомендуемый)

Самый простой, быстрый и логически правильный способ — принудительно округлить считанное из DBF значение до целого числа с помощью встроенной функции 1С Окр(). Математическое округление превратит дробь 0.9999999999999999 в честную целую единицу 1, которая без проблем запишется в ячейку с жестким ограничением типа.

Изменим строку записи значения в нашем коде следующим образом:


// Вместо НС.OBRAZ = Число(ДБФ1.OBRAZ);
НС.OBRAZ = Окр(ДБФ1.OBRAZ, 0);

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

Способ 2. Преобразование через строковый тип

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

Рассмотрим два варианта реализации этого способа:

Вариант А. Простое приведение к строке


// Приводим число из DBF к строке "1", а затем функция Число() делает из нее точное число 1
НС.OBRAZ = Число(Строка(ДБФ1.OBRAZ));

Вариант Б. Применение функции Формат

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


// Преобразуем число в строку без разделителей групп и дробной части
СтрокаБезПогрешности = Формат(ДБФ1.OBRAZ, "ЧГ=0"); 
НС.OBRAZ = Число(СтрокаБезПогрешности);

Способ 3. Изменение квалификаторов числа в описании типов

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

Изменим инициализацию квалификатора числа:


// Задаем длину 3 знака вместо 1, точность оставляем 0
КЧ_1_ = Новый КвалификаторыЧисла(3, 0); 
Массив = Новый Массив;
Массив.Добавить(Тип("Число"));
ОписаниеТиповЧ_1_ = Новый ОписаниеТипов(Массив, , , КЧ_1_);

ТЗ_ = Новый ТаблицаЗначений;
ТЗ_.Колонки.Добавить("OBRAZ", ОписаниеТиповЧ_1_);

НС = ТЗ_.Добавить();
// Теперь значение запишется корректно, так как платформе хватит буфера для размещения числа
НС.OBRAZ = ДБФ1.OBRAZ; 

Важное примечание: данный способ решает следствие, но не причину проблемы. Рекомендуется использовать его только в тех случаях, когда вы действительно предполагаете, что в DBF-файле могут появиться числа большей разрядности.

Способ 4. Отказ от жесткой типизации колонки

Если создаваемая таблица значений используется исключительно локально внутри обработки (например, для вывода информации на форму или промежуточного накопления данных), вы можете полностью отказаться от типизации колонки при ее добавлении. Без указания квалификаторов ячейка таблицы значений будет иметь тип Произвольный и сможет хранить абсолютно любые числа без потери точности.


ТЗ_ = Новый ТаблицаЗначений;
// Добавляем колонку без явного указания ОписанияТипов
ТЗ_.Колонки.Добавить("OBRAZ"); 

НС = ТЗ_.Добавить();
// Значение запишется без ошибок, сохранив внутреннюю точность
НС.OBRAZ = ДБФ1.OBRAZ; 

Однако помните, что у этого способа есть существенный недостаток. Если в дальнейшем вы планируете передавать эту таблицу значений в качестве параметра в запрос (механизм Новый ОписаниеТипов...), отсутствие жесткой типизации колонок вызовет ошибку выполнения запроса. Поэтому для построения надежных алгоритмов обмена всегда лучше использовать Способ 1 (Округление).

Резюме и рекомендации

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

  1. Не доверяйте слепо показаниям отладчика 1С при работе со сторонними источниками данных (DBF, Excel, текстовые макеты). Отладчик сглаживает реальную картину вещественных чисел.
  2. Помните, что функция Число() не округляет переданный ей числовой аргумент, а возвращает его исходное неточное представление.
  3. Всегда производите явное математическое округление функцией Окр() при переносе числовых данных из объектов XBase во внутренние типизированные структуры 1С:Предприятия.
← На главную