Swap Bytes¶
Level: 201 · for anyone with a hex editor open
One line: Swap Bytes reverses the bytes of each value, which converts little-endian to big-endian and back — so UTF-16LE text becomes UTF-16BE in place, byte order mark included — and it is the one operation the Endian toggle cannot change while the other settings still can, because a reversal reads the same from either end.
What the dialog does¶
The manual's whole description is "Swap the bytes of X[i]" (Hex Operations ↗). It takes no operand, and it is the only operation in the list whose subject is byte order itself: each value read in one order is written in the other. Treat Data As still matters, because it says how many bytes one value is, and so which bytes trade places.
In Python¶
Verified output of swap_bytes_py.py — regenerated by tools/run_examples.py, never hand-typed.
1. UTF-16LE BECOMES UTF-16BE IN PLACE
------------------------------------------------------------------------
é€ in UTF-16LE with its byte order mark, then Swap Bytes as
Unsigned Short:
before ff fe e9 00 ac 20 .decode('utf-16') -> 'é€'
after fe ff 00 e9 20 ac .decode('utf-16') -> 'é€'
after, without its first two bytes, as UTF-16BE 'é€'
The mark was swapped along with the text, FF FE to FE FF, so a
reader that trusts the mark still gets é€. That is what the mark is
for.
2. THE ENDIAN TOGGLE CANNOT CHANGE IT
------------------------------------------------------------------------
Little and Big wrote the same bytes:
all 65,536 Unsigned Shorts True
65,536 Unsigned Ints, every 65,537th value True
Reading a value little-endian and writing it back big-endian is a
reversal, and so is the other way round. Either way the bytes come
out reversed, so the setting has nothing to decide.
3. THE WIDTH CAN
------------------------------------------------------------------------
Swap Bytes on 12 34 56 78:
Unsigned Short 34 12 78 56
Unsigned Int 78 56 34 12
Unsigned Int64 01 02 03 04 05 06 07 08 -> 08 07 06 05 04 03 02 01
4. A RANGE THAT STARTS ONE BYTE LATE
------------------------------------------------------------------------
The same six bytes, with the selection starting at offset 1 and
four bytes long. The pairs are the wrong pairs:
ff e9 fe ac 00 20 .decode('utf-16-le') -> '\ue9ff곾\u2000'
Three code units, and none of them is the mark, é or €. Swap Bytes
trusts the range to start on a value, and nothing in the bytes says
where one starts.
5. PYTHON SPELLS IT array.byteswap()
------------------------------------------------------------------------
array('H', before).byteswap() fe ff 00 e9 20 ac
== Swap Bytes as Unsigned Short True
UTF-16LE to UTF-16BE, mark and all¶
Section 1 swaps é€ in UTF-16LE with its byte order mark, FF FE E9 00 AC 20, as Unsigned Short. Every code unit reverses, the mark along with the rest, so the file becomes FE FF 00 E9 20 AC: UTF-16BE with a big-endian mark, and a reader that trusts the mark decodes é€ from either version. The text changed order and took its own announcement with it, which is the job the mark exists to do.
The toggle has nothing to decide¶
Section 2 swaps every Unsigned Short under both Endian settings, and a sample of Unsigned Ints, and the two settings never disagree. Reading two bytes little-endian and writing them big-endian reverses them; reading big-endian and writing little-endian reverses them too. The chapter's table finds one other operation the toggle cannot change, Binary Invert, and there no setting matters at all. Here the width still does: section 3 swaps 12 34 56 78 as two Shorts, giving 34 12 78 56, and as one Int, giving 78 56 34 12.
A range that starts in the wrong place¶
Section 4 is the failure to watch for. Start the same swap one byte into the file and every pair is the wrong pair: three code units come out, and none is the mark, é or €. Nothing in UTF-16 bytes says where a code unit starts, so Swap Bytes trusts the range, and the range was one byte out. Before swapping a file with a header, check that the selection starts on a multiple of the value width from the start of the data.
What the manual does not say¶
- Whether Swap Bytes is offered for Unsigned and Signed Byte, where there is nothing to swap.
- Whether a Float or Double is swapped as raw bytes, as the integer types are.
- What becomes of a last value too short for the type.
If you are coming from Python or ABAP¶
Python. array.array('H', data) followed by .byteswap() is section 5, the standard library's own Swap Bytes, and data[i:i + 2][::-1] is the same thing by hand. For text, decoding and re-encoding — data.decode('utf-16-le').encode('utf-16-be') — has one advantage over swapping: given an odd number of bytes it raises UnicodeDecodeError, where a swap carries on.
ABAP. (Not machine-checked — CI cannot run ABAP.) Assigning an x field to an i always reads it big-endian ↗, so a little-endian number arriving in an xstring has to be reversed before the assignment — Swap Bytes by hand. The application server's own order is cl_abap_char_utilities=>endian ↗, and a legacy binary file can be opened with BIG ENDIAN or LITTLE ENDIAN ↗, which converts its numeric fields between that order and the server's on the way in and out.
Try it¶
- Make a small UTF-16LE file with a mark —
printf '\xff\xfeA\x00B\x00' > u16.txtin bash — Swap Bytes as Unsigned Short, and open it in an editor that reads the mark. - Swap two copies of the same file, one under each Endian setting, and compare them with Tools > Compare. They should be identical.
- Select from one byte into the file and swap again. Read the result with View > Character Set on Unicode.
- In a copy of a WAV file, select the sample rate — four little-endian bytes at offset 24 — and Swap Bytes as Unsigned Int. Read the Inspector with View > Endian on Little, then on Big.
See also¶
- Byte order and the BOM — what
FF FEandFE FFtell a reader - The bytes do not say which end — why a value wider than a byte needs an order at all
- Rotate Left — Rotate Left 8 on a Short, which is this operation
- UTF-16 and surrogates — the code units section 4 misaligned