Управление порядком байтов в целых числах

Исторически так сложилось, что в различных информационных системах, на аппаратном уровне, применяется разный порядок байтов при представлении чисел в памяти. Поэтому в задачах интеграции MQL-программ с "внешним миром", в частности, при реализации сетевых коммуникационных протоколов или чтении/записи файлов распространенных форматов может потребоваться изменить порядок байтов.

В компьютерах, работающих под управлением Windows, применяется порядок "от младшего к старшему" (little-endian), то есть первым в ячейке памяти, выделенной под переменную, лежит младший байт, после него — байт с более старшими разрядами и так далее. Альтернативный порядок "от старшего к младшему" (big-endian) широко используется в сети Интернет. В этом случае первым байтом в ячейке памяти идет байт со старшими разрядами, а последним располагается байт с младшими. Именно этот порядок похож на то, как мы записываем числа "слева-направо" в обычной жизни. Например, значение 1234 начинается с цифры 1 и обозначает тысячи, цифра 2 следом обозначает сотни, цифра 3 — десятки, и последняя 4 — это просто четыре (младший разряд).

Посмотрим, каков по умолчанию порядок байтов в MQL5. Для этого обратимся к скрипту MathSwap.mq5.

В нем описан шаблон объединения, который позволяет преобразовать целое число в массив байтов:

template<typename T>
union ByteOverlay
{
   T value;
   uchar bytes[sizeof(T)];
   ByteOverlay(const T v) : value(v) { }
   void operator=(const T v) { value = v; }
};

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

Опишем в OnStart переменную uint со значением 0x12345678 (учтите, что цифры шестнадцатеричные — в такой записи они точно соответствуют границам байтов: каждые 2 цифры это отдельный байт). Преобразуем число в массив и выведем его в журнал.

void OnStart()
{
   const uint ui = 0x12345678;
   ByteOverlay<uintbo(ui);
   ArrayPrint(bo.bytes); // 120  86  52  18 <==> 0x78 0x56 0x34 0x12
   ...

Функция ArrayPrint не умеет печатать числа в шестнадцатеричном формате, поэтому мы видим их десятичное представление, однако их нетрудно перевести в основание 16 и убедиться, что они соответствуют исходным байтам. Визуально они идут в обратном порядке: то есть под 0-м индексом в массиве находится 0x78, и далее 0x56, 0x34 и 0x12. Очевидно, что это порядок "от младшего к старшему" (мы действительно на Windows).

Теперь познакомимся с функцией MathSwap, которую MQL5 предоставляется для изменения порядка байтов.

integer MathSwap(integer value)

Функция возвращает целое число, в котором порядок байтов из переданного аргумента изменен на обратный. Функция принимает параметры типа ushort/uint/ulong (т.е. размером 2, 4, 8 байтов).

Попробуем функцию в действии:

   const uint ui = 0x12345678;
   PrintFormat("%I32X -> %I32X"uiMathSwap(ui));
   const ulong ul = 0x0123456789ABCDEF;
   PrintFormat("%I64X -> %I64X"ulMathSwap(ul));

Вот каков результат:

   12345678 -> 78563412
   123456789ABCDEF -> EFCDAB8967452301

Попробуем вывести в журнал массив байтов после преобразования величины 0x12345678 с помощью MathSwap:

   bo = MathSwap(ui);    // записали в ByteOverlay результат MathSwap
   ArrayPrint(bo.bytes); //  18  52  86 120 <==> 0x12 0x34 0x56 0x78

В байте с индексом 0, где раньше было 0x78, теперь находится 0x12, и в элементах с остальными номерами значения также обменялись.