При интеграции 1С с внешними системами, особенно с оборудованием вроде GPS-трекеров, часто возникает задача парсинга (разбора) бинарных данных, представленных в виде шестнадцатеричных строк. В этой статье разберем на конкретном примере, как преобразовать HEX-строку в число с плавающей запятой двойной точности (Double), и выясним, почему прямая конвертация часто приводит к неверным результатам.
Исходная задача: имеется строка "A027AFDF5D984840", которая, согласно документации протокола (WialonRetranslator), должна соответствовать координате "49.1903648". Попробуем разобраться в алгоритме такого преобразования.
Основная сложность при работе с бинарными данными, полученными из внешних источников (сеть, файлы, устройства), заключается в порядке байтов (Endianness). Это правило, определяющее, в каком порядке байты многобайтового числа записываются в память.
Существует два основных стандарта:
Когда мы получаем 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С.
В ходе обсуждения на форуме был предложен код, который выполняет преобразование путем ручного разбора 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С есть более простой и надежный способ.
Платформа 1С предоставляет встроенные объекты для работы с двоичными данными, которые значительно упрощают задачу. Вместо ручного разбора бит можно использовать связку объектов БуферДвоичныхДанных и ЧтениеДанных.
Этот подход не только короче, но и значительно производительнее и надежнее.
Рассмотрим по шагам, как это сделать:
БуферДвоичныхДанных из нашей исходной HEX-строки.ЧтениеДанных.УстановитьПорядокБайтов(). Поскольку исходные данные представлены в формате Little-Endian, указываем соответствующий порядок.ПрочитатьДвойное(), который выполнит всю работу по конвертации за нас.Вот как это выглядит в коде:
&НаКлиенте
Процедура Преобразовать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С позволит решить большинство задач по парсингу бинарных данных быстро и без ошибок.