Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Decoding Multi-Byte Integers in Little- and Big-Endian Formats

Hello! Last time, you used masks to work with selected bits inside an integer. This lesson asks a different question: when an integer occupies several bytes, which byte goes at the lowest memory address?

You’ll learn to decode the same bytes under little-endian and big-endian conventions, first by hand and then with Python. This matters when reading binary files or network fields: the bytes can be perfectly intact while the interpreted number is wrong.


One value, several addresses

Memory is commonly byte-addressable: each address identifies one byte, or eight bits. A 32-bit integer therefore occupies four consecutive byte addresses. Hexadecimal makes its parts easy to see because two hex digits make one byte. For example, the value 0x0A0B0C0D consists of the bytes 0A, 0B, 0C, and 0D.

The byte 0A is the most significant byte: it contributes to the highest-value positions in this integer. 0D is the least significant byte. Those names describe the bytes’ roles in the number, not where they happen to be stored.

Watch the selected parts of Endianness Explained With an Egg by Computerphile. Focus on the numbered memory addresses rather than the direction in which the number is written on screen.

Endianness Explained With an Egg - Computerphile

Computerphile uses a 32-bit value to show why storing a multi-byte integer requires a byte-order convention.

Watch the storage puzzle for the two possible layouts, then byte significance for why the first and last bytes have different numerical weights. Finish with shared conventions to see why agreement matters when systems exchange bytes. You can skip the intervening history and architecture examples for now.

Here are the two layouts for the same value, 0x0A0B0C0D. Address is the lowest address occupied by the integer.

The 32-bit value `0x0A0B0C0D` occupies addresses \(a\) through \(a+3\). Little-endian places its least significant byte, `0D`, at \(a\); big-endian places its most significant byte, `0A`, there.
Address Little-endian byte Big-endian byte
0D 0A
0C 0B
0B 0C
0A 0D

Little-endian puts the least significant byte at the lowest address; big-endian puts the most significant byte there. Neither convention changes the bits within a byte. Endianness concerns the order of whole bytes in a multi-byte value.


Decode bytes by assigning weights

Suppose you read these two bytes in increasing address order:

Address:       a      a+1
Stored byte:  01      02

In big-endian order, 01 is the high-value byte and 02 is the low-value byte. The integer is 0x0102, or . In little-endian order, 01 is the low-value byte and 02 is the high-value byte. The integer is 0x0201, or .

The bytes did not move. Their assigned numerical weights changed. One byte spans eight bits, so each position farther from the least significant byte has times the weight of the preceding position.

For any number of bytes, the reliable hand-decoding method is:

  1. Write the bytes in increasing address order, retaining zeros.
  2. Identify which end contains the least significant byte under the stated convention.
  3. Starting at that byte, assign weights , then add the contributions.

Hex provides a shortcut. With bytes 12 34 56 78 in increasing address order, big-endian decoding gives 0x12345678; little-endian decoding gives 0x78563412. You reverse the order of byte pairs, not the order of individual hex digits or bits.

Be careful about the direction of the task. Earlier, the image began with a known number and asked how to store it. Here, you begin with known stored bytes and ask what number they decode to. Keeping those directions distinct prevents a common mental mix-up.

A single byte has no byte-order difference. Multiple identical bytes can also hide a mistaken assumption: 00 00 decodes to zero either way. Nonmatching bytes, such as 01 02, make the convention visible.


Let the program state the convention

A binary field needs a specified byte order. Python makes that choice explicit with int.from_bytes for decoding and int.to_bytes for encoding.

Read the two integer-method entries in Python’s Built-in Types documentation. Pay particular attention to the byteorder argument and to the fact that encoding also requires a chosen length.

Built-in Types

Python’s documentation gives precise definitions and examples for converting between integers and byte sequences. Read these entries to connect the hand-decoding rule with an API you can use to check it.

In the integer methods, find int.to_bytes(). Read its definition and byte-order explanation, stopping after the paragraph that contrasts "big" and "little". Then find int.from_bytes() and read its examples and byte-order explanation, stopping after that entry’s corresponding paragraph. Notice that the same input bytes appear in examples with different decoded results.

This small program checks both directions. The spaces inside bytes.fromhex separate bytes for readability; they are not stored as bytes.

raw = bytes.fromhex("01 02")

big = int.from_bytes(raw, byteorder="big")
little = int.from_bytes(raw, byteorder="little")

print(big)       # 258
print(little)    # 513

value = 0x0A0B0C0D
print(value.to_bytes(4, byteorder="big").hex(" "))     # 0a 0b 0c 0d
print(value.to_bytes(4, byteorder="little").hex(" "))  # 0d 0c 0b 0a

The 4 specifies four bytes, matching the width of the value in the image. Writing an explicit length matters: leading zero bytes may be part of a format even though they do not change an integer’s numerical value.

You can also connect this lesson to two’s complement. First, byte order assembles the bit pattern; then signed interpretation determines whether that pattern represents a negative number. For example, the bytes FE FF decode as unsigned 0xFFFE in little-endian order. Interpreted as a signed 16-bit two’s-complement value, that is :

raw = bytes.fromhex("fe ff")
print(int.from_bytes(raw, "little", signed=True))  # -2
print(int.from_bytes(raw, "big", signed=True))     # -257

The signed=True option does not select an endian convention. The two arguments answer separate questions: which byte has which weight? and is the resulting fixed-width bit pattern signed?


Why the bytes alone may not be enough

Imagine a backend service receives a two-byte length field containing 01 00. If its format specifies big-endian, the length is . If the service incorrectly decodes those same bytes as little-endian, it gets . The receiver cannot generally infer the intended answer from those two bytes alone; it needs the file-format or protocol specification.

This is why byte order belongs to the data contract, not merely to the computer running your code. Some network protocol fields use big-endian network byte order, while a binary file format can specify its own order. When parsing such data, supply the specified order explicitly instead of assuming your machine’s native order. Ordinary byte sequences are not automatically reversed merely because they travel over a network.


Key takeaways

To decode a multi-byte integer, keep the bytes in address order and determine which one is least significant. Little-endian places that byte first; big-endian places it last. Assign byte weights accordingly, or make the convention explicit with int.from_bytes. Signedness is a separate interpretation applied after the bytes form a fixed-width value.

Next, we’ll look at bytes that represent text rather than integers, using ASCII and UTF-8.

Can't find a good explanation? Sign up and we'll make it for you

Sign up