Yun Sheng's Site
A Little Bit of This, A Little Bit of That

MSB and LSB, MSb and LSb

tldr

  • Distinguish between Most/Least Significant Byte vs Most/Least Significant bit
  • Most Significant means if you add 1 how big of a deal you change the number
  • Big Endian and Little Endian affects how the bytes are ordered in the binary, but in your program it’s always assembled correctly

Overview

This week I was working on an inner source project and learned something new. I never knew that there exists the concept of Most Significant bit before.

Hence as usual, vibe with Gemini/Claude, learn again and write it down.

In this post I’ll use MSB and LSB for Most Significant Byte and Least Significant Byte. Use MSb and LSb for Most Significant bit and Least Significant bit

Why is it called Most/Least Significant

I learned this when I was interviewing with Redline Trading Solutions in 2016. Seems like they got merged/acquired by Pico?

Anyways, the dude told me the reason a byte or bit is called Most Significant is because if you add one to it, how much does it change the number.

So if the allowance I give my kid is 20 bucks per paycheck, then 2 in the 20 would be the Most Significant digit (because he then gets 30 bucks), where as 0 would be the Least Significant digit (because he would only get 21 compared to 30).

Note that I used digit instead of Byte or bit. This is because I’m only explaining what is Most Significant or Least Significant.

Bytes

So let’s talk about Most Significant Byte vs Least Significant Byte first.

This is to describe when there are two bytes, which byte is the Most Significant Byte.

The sentence above is a bit vague, but let me explain.

So say you have a uint32_t (unsigned int 32bits), and your code is like

1
2
3
4
5
6
7
8
9
#include <stdint.h>
#include <stdio.h>

uint32_t num = 0x12345678;

int main() {
    printf("num = 0x%X\n", num);
    // this would print "0x12345678"
}

Because this is a C program and num is a global initialized variable, it belongs to the data segment, hence it’s going to be in the output binary.

Then the question is, how are 0x12, 0x34, 0x56, 0x78 ordered in the binary?

Most people would assume they are in order, which means on disk you would see them from left to right 0x12, 0x34, 0x56, 0x78

But the reality is there exist two orders.

Big Endian (MSB First)

If 0x12, 0x34, 0x56, 0x78 is in the binary where the Most Significant Byte is first, then this is called Big Endian.

Little Endian (LSB First)

If 0x78, 0x56, 0x34, 0x12 is in the binary where the Least Significant Byte is first, then this is called Little Endian.

What does it mean if it’s Big Endian or Little Endian?

Big Endian and Little Endian describes how the bytes of a multibyte object are arranged in address order or in a defined serialization.

So Intel Chips and Apple M Chips are Little Endian, that means for the chip to get 0x12345678, the hex has to be 0x78, 0x56, 0x34, 0x12.

You can verify the program above with the following (since nowadays most chips are little endian)

gcc a.c && xxd -g 1 a.out | grep "78 56 34 12"
00008000: 78 56 34 12 00 00 00 00 00 00 00 00 00 00 00 00  xV4.............

Note that regardless of Big Endian or Little Endian, your code sees 0x12345678. The endianness decides what is in the binary in the case above.

One important one is when we try to send 0x12345678 over the network.

Networking is Big Endian?

I remember in my networking class my professor said networking is Big Endian, but I misinterpreted it, what he meant was the protocol information is always Big Endian when you send it over the wire. Say you are sending some payload to some address’s port 4660 which is hex 0x1234, it would be over the wire in the order of 0x12, 0x34.

The actual payload however, is usually just raw bytes, so on a Little Endian machine, trying to send a uint32_t 0x12345678 over the wire would have it sent as 0x78, 0x56, 0x34, 0x12.

1
2
3
4
5
uint32_t payload = 0x12345678;
send(socket, &payload, sizeof payload, 0);

// On a Little Endian machine the payload over the wire would be
// 0x78, 0x56, 0x34, 0x12

There is a function called htonl to convert from host byte order to network byte order.

1
2
uint32_t wire_payload = htonl(payload);
send(socket, &wire_payload, sizeof wire_payload, 0);

One thing to note is that when my professor said “Networking is Big Endian”, he meant the TCP, UDP, IP header information themselves are sent as Big Endian. NOT the payload as we saw above.

The other thing is that there exist other protocols that requires headers to be sent over the wire and they are not universally Big Endian, it’s per protocol. Some of the ones that Gemini brought up are

  • USB (Universal Serial Bus) is strictly Little Endian
  • SMB/CIFS (Windows File Sharing) is Little Endian
  • Bluetooth (HCI layer) is Little Endian

And again when we say Little Endian for all three above we are talking about the protocol headers, NOT the payload.

Bits

Ok we’ve talked a lot about bytes and let’s talk about bits. This is the part that got me confused because I didn’t know there was a difference in bit numbering conventions.

In my previous knowledge, bit 0 is always on the right side (meaning it’s the least significant one because if you add one to it, the number doesn’t change drastically). This is called the LSb 0 convention.

See example below for 0x12, its binary is 0b0001_0010

bit 76543210
    00010010

Apparently there exists another convention called MSb 0, where the Most Significant bit is assigned the index 0. So the above would be

bit 01234567
    00010010

TCP

MSb seems to be prevalent in networking protocols, for instance TCP

  TCP Header Format

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |          Source Port          |       Destination Port        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                        Sequence Number                        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Acknowledgment Number                      |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |  Data |           |U|A|P|R|S|F|                               |
   | Offset| Reserved  |R|C|S|S|Y|I|            Window             |
   |       |           |G|K|H|T|N|N|                               |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |           Checksum            |         Urgent Pointer        |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Options                    |    Padding    |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                             data                              |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

                            TCP Header Format

The top row is the tens digit, the bottom row is the ones digit, so you read them together as one column. This ruler restarts at 0 for every 32-bit row of the header. For example, in the row containing the control bits (the 4th row), the column with 1 on top and 1 on the bottom is bit 11 — that’s the ACK flag, set on every packet after the initial handshake to acknowledge received data. It runs from bit 0 through bit 31 this way, left to right, for each row.

Nevertheless, the Most Significant bit is bit 0.

BlazingMQ

BlazingMQ uses MSb as well. See MessagePropertiesHeader

    //   +---------------+---------------+---------------+---------------+
    //   |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
    //   +---------------+---------------+---------------+---------------+
    //   | R |PHS2X| HS2X|   MPAW Upper  |          MPAW Lower           |
    //   +---------------+---------------+---------------+---------------+
    //   |   Reserved    |    NumProps   |
    //   +---------------+---------------+

Note that bit 0 is the MSb. One difference between BlazingMQ and TCP in terms of bit numbering is that BlazingMQ marks bit 0 to bit 7 for each byte, whereas TCP treats all 32 bits consecutively.

Raspberry Pi Datasheet

If you go to RP2040 Page 434 you would find the datasheet of UARTCR Register.

This shows how to set individual bits in the 32-bit register. Note that it uses LSb, so bit 0 means the least significant bit (almost every modern chip uses this convention)

So if I want to enable SIRLP: SIR low-power IrDA mode., because it’s bit 2 on the datasheet, and I know the convention is LSb. So in my code I’d actually set bit 2 with something like

1
2
3
4
5
6
uint32_t UARTCR_REGISTER = 0;

void SetSIRLP(int value) {
    // assert value is 0 or 1 only
    UARTCR_REGISTER = (UARTCR_REGISTER & ~(1 << 2)) | (value << 2);
}

So when would I need to know about MSBs or LSBs? It’s when something is wrong and you need to inspect the live memory.

Say the code you compiled and flashed to your raspberry isn’t working as you expected, and you want to check if you actually set the thing correctly on the hardware.

You would use a hardware debugger to read the memory at the address of your UARTCR_REGISTER while the chip is running, and look for 4 bytes. And because you know RP2040 is Little Endian, the first byte in memory would be the one that contains your SIRLP bit, read that out and look for bit 2 to confirm.

bit 0 to bit 7 or bit 0 to bit 31

You might have noticed in the examples above when describing a uint32_t, there are two numbering schemes that apply regardless of whether we use MSb or LSb.

Assume that we are talking about a uint32_t with LSb. It could be marked from bit 0 to bit 31 like the following

1
2
3
4
5
6
0x12345678

3322 2222 2222 1111 1111 1100 0000 0000
1098 7654 3210 9876 5432 1098 7654 3210
---- ---- ---- ---- ---- ---- ---- ----
0001 0010 0011 0100 0101 0110 0111 1000

Or it could be marked for each byte from bit 0 to bit 7

1
2
3
4
5
0x12       0x34       0x56       0x78

7654 3210  7654 3210  7654 3210  7654 3210
---- ----  ---- ----  ---- ----  ---- ----
0001 0010  0011 0100  0101 0110  0111 1000

Note that Big Endian vs Little Endian wouldn’t matter in the diagram above because we are viewing the hex in our program, so the value is always 0x12345678 in our code.

This numbering choice is independent of MSb/LSb, and we’ve actually already seen both options in the examples above: TCP numbers the whole 32 bits at once (bit 0 to bit 31) using MSb 0, while BlazingMQ numbers each byte separately (bit 0 to bit 7, repeating per byte) also using MSb 0. This section just shows that the same two choices, whole-word vs per-byte apply under LSb 0 too.

Both ways of marking the bits are valid, but I think using bit 0 - bit 31 feels easier to handle in code.

Update

While talking to JG about this post a few things came up and I think it’s worth it to note it down.

  • When sending a byte over the wire there could be LSb first or MSb first, meaning are we sending the right most bit first over the wire or the left most bit first over wire (since bits on wires are high/low voltages)
Update: 2026-07-12
Tags:

See Also