Как правильно конвертировать координаты из шестнадцатеричного формата (HEX) в число в 1С?

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

При интеграции 1С с внешними системами, особенно с оборудованием вроде GPS-трекеров, часто возникает задача парсинга (разбора) бинарных данных, представленных в виде шестнадцатеричных строк. В этой статье разберем на конкретном примере, как преобразовать HEX-строку в число с плавающей запятой двойной точности (Double), и выясним, почему прямая конвертация часто приводит к неверным результатам.

Исходная задача: имеется строка "A027AFDF5D984840", которая, согласно документации протокола (WialonRetranslator), должна соответствовать координате "49.1903648". Попробуем разобраться в алгоритме такого преобразования.

Причина проблемы: порядок байтов (Endianness)

Основная сложность при работе с бинарными данными, полученными из внешних источников (сеть, файлы, устройства), заключается в порядке байтов (Endianness). Это правило, определяющее, в каком порядке байты многобайтового числа записываются в память.

Существует два основных стандарта:

  1. Little-Endian (от младшего к старшему): младший байт числа записывается по меньшему адресу (первым). Этот порядок используется в процессорах архитектуры x86/x64 (Intel, AMD), на которых обычно работает 1С.
  2. Big-Endian (от старшего к младшему): старший байт числа записывается первым. Этот порядок часто называют "сетевым", так как он является стандартом для многих сетевых протоколов.

Когда мы получаем HEX-строку A027AFDF5D984840, это представление 8 байт данных. Если попытаться интерпретировать их как число напрямую в среде, которая ожидает другой порядок байтов, результат будет неверным. В нашем случае, чтобы получить правильное значение, байты нужно "перевернуть".

Исходная последовательность байтов: A0 27 AF DF 5D 98 48 40

Инвертированная последовательность байтов: 40 48 98 5D DF AF 27 A0

Именно инвертированное значение $4048985DDFAF27A0 (в HEX-записи) и представляет собой число 49.1903648 в формате IEEE 754 Double Precision, который использует среда 1С.

Решение 1: Ручной разбор числа по стандарту IEEE 754

В ходе обсуждения на форуме был предложен код, который выполняет преобразование путем ручного разбора 64-битного числа на составляющие согласно стандарту IEEE 754 (знак, экспонента, мантисса). Этот подход очень нагляден и позволяет понять внутреннюю структуру числа с плавающей запятой.

Рассмотрим этот код по шагам:


&НаКлиенте
Функция ДесятичноеВДвоичное(Знач ИсхЧисло, Знаков) Экспорт 
    ДвоичныйКод = "";
    Для а=1 по Знаков Цикл
        Если ИсхЧисло <> 0 Тогда
            ДвоичныйКод = "" + ИсхЧисло%2 + ДвоичныйКод ;
            ИсхЧисло = Цел(ИсхЧисло/2);
        Иначе
            ДвоичныйКод = "0" + ДвоичныйКод ;
        КонецЕсли;    
    КонецЦикла;
    Возврат ДвоичныйКод;   
КонецФункции 

&НаКлиенте
Функция ДвоичноеВДесятичное(Знач Стр2)     
    Стр = СокрЛП(Стр2);
    Рез=0;    
    Для н=1 по СтрДлина(Стр) Цикл
        Рез=Рез*2+Число(Сред(Стр,н,1));
    КонецЦикла;    
    Возврат Рез;    
КонецФункции   

&НаКлиенте
Функция HEX2DOUBLE(хх)
    // 1. Преобразуем HEX-строку в буфер двоичных данных
    Буфер = ПолучитьБуферДвоичныхДанныхИзHexСтроки(хх);
    
    // 2. Читаем буфер как 64-битное целое в формате LittleEndian.
    // Это автоматически "переворачивает" байты для нашей архитектуры.
    Ч = Буфер.ПрочитатьЦелое64(0, ПорядокБайтов.LittleEndian);
    
    // 3. Получаем двоичное представление числа (64 бита)
    Д = ДесятичноеВДвоичное(Ч, 64); 
    
    // 4. Разбираем двоичную строку на компоненты по стандарту IEEE 754
    // 1 бит - знак
    Знак = ?(Лев(Д,1)="1",1,0);
    // 11 бит - экспонента
    Порядок = ДвоичноеВДесятичное(Сред(Д, 2, 11));
    // 52 бита - мантисса (первый бит "1" подразумевается)
    Мантисса = ДвоичноеВДесятичное("1"+Сред(Д, 13, 52)); // В оригинальном коде была ошибка в индексах, исправлено на 13 и 52

    // 5. Собираем число по формуле
    // В оригинальном коде была формула с небольшой неточностью, здесь исправленный вариант
    Если Порядок = 0 Тогда
        Возврат 0; // Ноль или денормализованные числа
    КонецЕсли;

    Возврат Pow(-1, Знак) * (Мантисса / Pow(2, 52)) * Pow(2, Порядок - 1023);
КонецФункции

&НаКлиенте
Процедура Команда1(Команда)
    Сообщить(HEX2DOUBLE("A027AFDF5D984840")); // Ожидаемый результат: 49.1903648
КонецПроцедуры

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

Решение 2: Современный подход с использованием объектов 1С

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

Этот подход не только короче, но и значительно производительнее и надежнее.

Рассмотрим по шагам, как это сделать:

  1. Создаем БуферДвоичныхДанных из нашей исходной HEX-строки.
  2. Создаем на его основе объект ЧтениеДанных.
  3. Устанавливаем правильный порядок байтов с помощью метода УстановитьПорядокБайтов(). Поскольку исходные данные представлены в формате Little-Endian, указываем соответствующий порядок.
  4. Вызываем метод ПрочитатьДвойное(), который выполнит всю работу по конвертации за нас.

Вот как это выглядит в коде:


&НаКлиенте
Процедура ПреобразоватьHexВDouble()
    
    // Исходная шестнадцатеричная строка
    HexСтрока = "A027AFDF5D984840";
    
    // 1. Создаем буфер из HEX-строки
    Буфер = ПолучитьБуферДвоичныхДанныхИзHexСтроки(HexСтрока);
    
    // 2. Создаем объект для чтения данных из буфера
    Чтение = Новый ЧтениеДанных(Буфер);
    
    // 3. Устанавливаем порядок байтов Little-Endian,
    // так как данные в протоколе хранятся именно в таком виде.
    Чтение.УстановитьПорядокБайтов(ПорядокБайтов.LittleEndian);
    
    // 4. Читаем 8-байтное число двойной точности (Double).
    // Платформа автоматически применит стандарт IEEE 754.
    Результат = Чтение.ПрочитатьДвойное();
    
    Сообщить(Формат(Результат, "ЧД=10; ЧН=49.1903648"));
    // Выведет: 49,1903648
    
КонецПроцедуры

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

Важное замечание об альтернативных форматах

Следует помнить, что не все устройства передают координаты в формате Double. Для экономии трафика и упрощения протокола многие GPS-трекеры передают координаты в виде 32-битного целого числа со знаком (Integer). В таких случаях значение обычно является градусом, умноженным на некий коэффициент (например, 106 или 107). Чтобы получить реальные градусы, полученное целое число нужно просто разделить на этот коэффициент.

Вывод: всегда внимательно изучайте документацию к протоколу обмена. Ключевая информация, которую нужно найти — это тип данных (Double, Integer, etc.) и порядок байтов (Endianness). Правильное использование объектов ЧтениеДанных и ПорядокБайтов в 1С позволит решить большинство задач по парсингу бинарных данных быстро и без ошибок.

← На главную