Understanding Big and Little Endian Byte Order

Problems with byte order are frustrating, and I want to spare you the grief I experienced. Here's the key:

  • Problem: Computers speak different languages, like people.
    • Some write data "left-to-right" and others "right-to-left".
    • A machine can read its own data just fine - problems happen when one computer stores data and a different type tries to read it.
  • Solutions
    • Agree to a common format (i.e., all network traffic follows a single format), or
    • Always include a header that describes the format of the data. If the header appears backwards, it means data was stored in the other format and needs to be converted.

Numbers vs. Data

The most important concept is to recognize the difference between a number and the data that represents it.

A number is an abstract concept, such as a count of something. You have ten fingers. The idea of "ten" doesn't change, no matter what representation you use: ten, 10, diez (Spanish), ju (Japanese), 1010 (binary), X (Roman numeral)... these representations all point to the same concept of "ten".

Contrast this with data. Data is a physical concept, a raw sequence of bits and bytes stored on a computer. Data has no inherent meaning and must be interpreted by whoever is reading it.

Data is like human writing, which is simply marks on paper. There is no inherent meaning in these marks. If we see a line and a circle (like this: |O) we may interpret it to mean "ten".

But we assumed the marks referred to a number. They could have been the letters "IO", a moon of Jupiter. Or perhaps the Greek goddess. Or maybe an abbreviation for Input/Output. Or someone's initials. Or 10 in base 2, aka 2 in binary. The list of possibilities goes on.

The point is that a single piece of data (|O) can be interpreted in many ways, and the meaning is unclear until someone clarifies the intent of the author.

Computers face the same problem. They store data, not abstract concepts, and do so using a sequence of 1's and 0's. Later, they read back the 1's and 0's and try to recreate the abstract concept from the raw data. Depending on the assumptions made, the 1's and 0's can mean very different things.

Why does this problem happen? Well, there's no rule that all computers must use the same language, just like there's no rule all humans need to. Each type of computer is internally consistent (it can read back its own data), but there are no guarantees about how another type of computer will interpret the data it created.

Basic concepts

  • Data (bits and bytes, or marks on paper) is meaningless; it must be interpreted to create an abstract concept, like a number.
  • Like humans, computers have different ways to store the same abstract concept. (i.e., we have many ways to say "ten": ten, 10, diez, etc.)

Storing Numbers as Data

Thankfully, most computers agree on a few basic data formats (this was not always the case). This gives us a common starting point which makes our lives a bit easier:

  • A bit has two values (on or off, 1 or 0)
  • A byte is a sequence of 8 bits
    • The "leftmost" bit in a byte is the biggest. So, the binary sequence 00001001 is the decimal number 9. 00001001 = (23 + 20 = 8 + 1 = 9).
    • Bits are numbered from right-to-left. Bit 0 is the rightmost and the smallest; bit 7 is leftmost and largest.

We can use these basic agreements as a building block to exchange data. If we store and read data one byte at a time, it will work on any computer. The concept of a byte is the same on all machines, and the idea of "Byte 0" is the same on all machines. Computers also agree on the order you sent them bytes -- they agree on which byte was sent first, second, third, etc. so "Byte 35" is the same on all machines.

So what's the problem -- computers agree on single bytes, right?

Well, this is fine for single-byte data, like ASCII text. However, a lot of data needs to be stored using multiple bytes, like integers or floating-point numbers. And there is no agreement on how these sequences should be stored.

Byte Example

Consider a sequence of 4 bytes, named W X Y and Z - I avoided naming them A B C D because they are hex digits, which would be confusing. So, each byte has a value and is made up of 8 bits.

 Byte Name:    W       X       Y       Z
 Location:     0       1       2       3
 Value (hex):  0x12    0x34    0x56    0x78 


For example, W is an entire byte, 0x12 in hex or 00010010 in binary. If W were to be interpreted as a number, it would be "18" in decimal (by the way, there's nothing saying we have to interpret it as a number - it could be an ASCII character or something else entirely :) ).

With me so far? We have 4 bytes, W X Y and Z, each with a different value.

Understanding Pointers

Pointers are a key part of programming, especially the C programming language. A pointer is a number that references a memory location. It is up to us (the programmer) to interpret the data at that location.

In C, when you cast (convert) a pointer to certain type (such as a char * or int *), it tells the computer how to interpret the data at that location. For example, let's declare

void *p = 0; // p is a pointer to an unknown data type
             // p is a NULL pointer -- do not dereference
char *c;     // c is a pointer to a single byte

Note that we can't get the data from p because we don't know it's type. p could be pointing at a single number, a letter, the start of a string, your horoscope, an image -- we just don't know.

Now, suppose we write

c = (char *)p;


Ah -- now this statement tells the computer to point to the same place as p, and interpret the data as a single character (1 byte). In this case, c would point to memory location 0, or byte W. If we printed c, we'd get the value in W, which is hex 0x12 (remember that W is a whole byte).

This example does not depend on the type of computer we have -- again, all computers agree on what a single byte is (in the past this was not the case).

The example is helpful, even though it is the same on all computers -- if we have a pointer to a single byte (char *, a single byte), we can walk through memory, reading off a byte at a time. We can examine any memory location and the endian-ness of a computer won't matter -- every computer will give back the same information.

So, what's the problem?

Problems happen when computers try to read multiple bytes. Some data types contain multiple bytes, like long integers or floating-point numbers. A single byte has only 256 values, so can store 0 - 255.

Now problems start - when you read multi-byte data, where does the biggest byte appear?

  • Big endian machine: Stores data big-end first. When looking at multiple bytes, the first byte (lowest address) is the biggest.
  • Little endian machine: Stores data little-end first. When looking at multiple bytes, the first byte is smallest.

The naming makes sense, eh? Big-endian thinks the big-end is first. (By the way, the big-endian / little-endian naming comes from Gulliver's Travels, where the Lilliputans argue over whether to break eggs on the little-end or big-end. Sometimes computer debates are just as meaningful :-) )

Again, endian-ness does not matter if you have a single byte. If you have one byte, it's the only data you read so there's only one way to interpret it (again, because computers agree on what a byte is).

Now suppose we have our 4 bytes (W X Y Z) stored the same way on a big-and little-endian machine. That is, memory location 0 is W on both machines, memory location 1 is X, etc.

We can create this arrangement by remembering that bytes are machine-independent. We can walk memory, one byte at a time, and set the values we need. This will work on any machine:

c = 0;     // point to location 0 (won't work on a real machine!)
*c = 0x12; // Set W's value
c = 1;     // point to location 1
*c = 0x34; // Set X's value
...        // repeat for Y and Z; details left to reader

This code will work on any machine, and we have both set up with bytes W, X, Y and Z in locations 0, 1, 2 and 3.

Interpreting Data

Now let's do an example with multi-byte data (finally!). Quick review: a "short int" is a 2-byte (16-bit) number, which can range from 0 - 65535 (if unsigned). Let's use it in an example:

short *s; // pointer to a short int (2 bytes)
s = 0;    // point to location 0; *s is the value

So, s is a pointer to a short, and is now looking at byte location 0 (which has W). What happens when we read the value at s?

  • Big endian machine: I think a short is two bytes, so I'll read them off: location s is address 0 (W, or 0x12) and location s + 1 is address 1 (X, or 0x34). Since the first byte is biggest (I'm big-endian!), the number must be 256 * byte 0 + byte 1, or 256*W + X, or 0x1234. I multiplied the first byte by 256 (2^8) because I needed to shift it over 8 bits.
  • Little endian machine: I don't know what Mr. Big Endian is smoking. Yeah, I agree a short is 2 bytes, and I'll read them off just like him: location s is 0x12, and location s + 1 is 0x34. But in my world, the first byte is the littlest! The value of the short is byte 0 + 256 * byte 1, or 256*X + W, or 0x3412.

Keep in mind that both machines start from location s and read memory going upwards. There is no confusion about what location 0 and location 1 mean. There is no confusion that a short is 2 bytes.

But do you see the problem? The big-endian machine thinks s = 0x1234 and the little-endian machine thinks s = 0x3412. The same exact data gives two different numbers. Probably not a good thing.

Yet another example

Let's do another example with 4-byte integer for "fun":

int *i; // pointer to an int (4 bytes on 32-bit machine)
i = 0;  // points to location zero, so *i is the value there 

Again we ask: what is the value at i?

  • Big endian machine: An int is 4 bytes, and the first is the largest. I read 4 bytes (W X Y Z) and W is the largest. The number is 0x12345678.
  • Little endian machine: Sure, an int is 4 bytes, but the first is smallest. I also read W X Y Z, but W belongs way in the back -- it's the littlest. The number is 0x78563412.

Same data, different results - not a good thing. Here's an interactive example using the numbers above, feel free to plug in your own:


The NUXI problem

Issues with byte order are sometimes called the NUXI problem: UNIX stored on a big-endian machine can show up as NUXI on a little-endian one.

Suppose we want to store 4 bytes (U, N, I and X) as two shorts: UN and IX. Each letter is a entire byte, like our WXYZ example above. To store the two shorts we would write:

short *s; // pointer to set shorts
s = 0;    // point to location 0
*s = UN;  // store first short: U * 256 + N (fictional code)
s = 2;    // point to next location
*s = IX;  // store second short: I * 256 + X

This code is not specific to a machine. If we store "UN" on a machine and ask to read it back, it had better be "UN"! I don't care about endian issues, if we store a value on one machine, we need to get the same value back.

However, if we look at memory one byte at a time (using our char * trick), the order could vary. On a big endian machine we see

Byte:     U N I X
Location: 0 1 2 3

Which make sense. U is the biggest byte in "UN" and is stored first. The same goes for IX: I is the biggest, and stored first.

On a little-endian machine we would see:

Byte:     N U X I
Location: 0 1 2 3

And this makes sense also. "N" is the littlest byte in "UN" and is stored first. Again, even though the bytes are stored "backwards" in memory, the little-endian machine knows it is little endian, and interprets them correctly when reading the values back. Also, note that we can specify hex numbers such as x = 0x1234 on any machine. Even a little-endian machine knows what you mean when you write 0x1234, and won't force you to swap the values yourself (you specify the hex number to write, and it figures out the details and swaps the bytes in memory, under the covers. Tricky.).

This scenario is called the "NUXI" problem because byte sequence UNIX is interpreted as NUXI on the other type of machine. Again, this is only a problem if you exchange data -- each machine is internally consistent.

Exchanging Data Between Endian Machines

Computers are connected - gone are the days when a machine only had to worry about reading its own data. Big and little-endian machines need to talk and get along. How do they do this?

Solution 1: Use a common format

The easiest approach is to agree to a common format for sending data over the network. The standard network order is actually big-endian, but some people get uppity that little-endian didn't win... we'll just call it "network order".

To convert data to network order, machines call a function hton (host-to-network). On a big-endian machine this won't actually do anything, but we won't talk about that here (the little-endians might get mad).

But it is important to use hton before sending data, even if you are big-endian. Your program may be so popular it is compiled on different machines, and you want your code to be portable (don't you?).

Similarly, there is a function ntoh (network to host) used to read data off the network. You need this to make sure you are correctly interpreting the network data into the host's format. You need to know the type of data you are receiving to decode it properly, and the conversion functions are:

 htons()--"Host to Network Short" 
 htonl()--"Host to Network Long" 
 ntohs()--"Network to Host Short" 
 ntohl()--"Network to Host Long" 

Remember that a single byte is a single byte, and order does not matter.

These functions are critical when doing low-level networking, such as verifying the checksums in IP packets. If you don't understand endian issues correctly your life will be painful - take my word on this one. Use the translation functions, and know why they are needed.

Solution 2: Use a Byte Order Mark (BOM)

The other approach is to include a magic number, such as 0xFEFF, before every piece of data. If you read the magic number and it is 0xFEFF, it means the data is in the same format as your machine, and all is well.

If you read the magic number and it is 0xFFFE (it is backwards), it means the data was written in a format different from your own. You'll have to translate it.

A few points to note. First, the number isn't really magic, but programmers often use the term to describe the choice of an arbitrary number (the BOM could have been any sequence of different bytes). It's called a byte-order mark because it indicates the byte order the data was stored in.

Second, the BOM adds overhead to all data that is transmitted. Even if you are only sending 2 bytes of data, you need to include a 2-byte BOM. Ouch!

Unicode uses a BOM when storing multi-byte data (some Unicode character encodings can have 2, 3 or even 4-bytes per character). XML avoids this mess by storing data in UTF-8 by default, which stores Unicode information one byte at a time. And why is this cool?

(Repeated for the 56th time) "Because endian issues don't matter for single bytes".

Right you are.

Again, other problems can arise with BOM. What if you forget to include the BOM? Do you assume the data was sent in the same format as your own? Do you read the data and see if it looks "backwards" (whatever that means) and try to translate it? What if regular data includes the BOM by coincidence? These situations are not fun.

Why are there endian issues at all? Can't we just get along?

Ah, what a philosophical question.

Each byte-order system has its advantages. Little-endian machines let you read the lowest-byte first, without reading the others. You can check whether a number is odd or even (last bit is 0) very easily, which is cool if you're into that kind of thing. Big-endian systems store data in memory the same way we humans think about data (left-to-right), which makes low-level debugging easier.

But why didn't everyone just agree to one system? Why do certain computers have to try and be different?

Let me answer a question with a question: Why doesn't everyone speak the same language? Why are some languages written left-to-right, and others right-to-left?

Sometimes communication systems develop independently, and later need to interact.

Epilogue: Parting Thoughts

Endian issues are an example of the general encoding problem - data needs to represent an abstract concept, and later the concept needs to be created from the data. This topic deserves its own article (or series), but you should have a better understanding of endian issues. More information:

Interactive example:





Tools of the trade:


41 Comments »

Trackbacks & Pingbacks

  1. Pingback by Unicode and You | BetterExplained — February 21, 2007 @ 6:04 am

  2. Pingback by A little diddy about binary file formats | BetterExplained — February 21, 2007 @ 6:10 am

  3. Pingback by network « life ideas — November 13, 2007 @ 4:07 pm


Comments

  1. Great post! Thank you!

    Michael P — January 12, 2007 @ 8:41 am

  2. how do you write 999 in little endian

    angela — January 25, 2007 @ 10:21 am

  3. Hi Angela, it depends on whether you are using a 2-byte or 4-byte integer. I’ll assume you are using a 4-byte integer because they are more common.

    In hex, 999 is 0×03e7. But we need to pad it out to 4 bytes, so it becomes

    0×00 00 03 e7

    (Broken into 1-byte groups for easier reading). On a little-endian machine, the little end (e7) comes first, so it would be stored as

    e7 03 00 00

    To double-check, if you plug these numbers into the endian calculator it should give 999 for little-endian.

    Hope this helps!

    Kalid — January 26, 2007 @ 8:12 pm

  4. This is really really helpful! Big Thanks!

    Vivi — April 16, 2007 @ 10:39 pm

  5. In Byte Example you say that 00100010 is 0×12 or dec. 18. Is that true?

    jakob — May 15, 2007 @ 1:05 am

  6. Whoops, that was a typo — thanks! It should be:

    0×12 = binary 0001 0010 = decimal 18

    Putting the space helps make it more clear, I’ll make the change now. Appreciate the comment.

    Kalid — May 15, 2007 @ 1:38 am

  7. thanks a million..a superb explanation about byte ordering…
    mr. jakob: there is no typo… its right in doc. as 00010010(0×12) and 18(dec)..

    shekhar — May 24, 2007 @ 11:42 pm

  8. Hi Shekhar, I’m glad you found it useful. Jakob actually caught the typo, I fixed up the original article :)

    Kalid — May 25, 2007 @ 1:02 am

  9. Thank you boss… just when i thought that Endianess is a complete waste in the world of Computer arithmatics!!!.. you proved it works…

    Sumeash — July 19, 2007 @ 5:15 am

  10. Hi Sumeash, always happy to help!

    Kalid — July 31, 2007 @ 1:05 am

  11. i want to know about the coding … how can we change the number present in big endian to little endian and vice-versa

    Navin — September 12, 2007 @ 7:21 am

  12. How will following structure look in big-endian and little-endian systems?
    struct STRUCT {
    unsigned int a:12;
    unsigned int b:10;
    unsigned int c:10;
    };
    struct STRUCT myStruct= {0xFED, 0×345, 0×3AB};

    emile — October 4, 2007 @ 1:01 am

  13. Was so very educative. Now I think i can go easy with my programming.

    Giridhar — November 29, 2007 @ 2:09 pm

  14. @emile: I’ll have to take a closer look at that one and remember how padded items are laid out :) .

    @Giridhar: Thanks, glad you liked it.

    Kalid — November 29, 2007 @ 3:28 pm

  15. Awesome post!
    thank you

    yogesh — December 5, 2007 @ 10:03 pm

  16. Thanks Yogesh!

    Kalid — December 5, 2007 @ 10:11 pm

  17. Question from Doran: “How can 2 chars allocate to an unsigned short? It just doesn’t make sense to me.
    I’ve heard about NUXI few times before, and still I can’t get it. Please can you explain it for me. (even in C)

    My reply:

    On a 32-bit computer, a short is composed of 16 bits (2 bytes). In order to set the value, you an specify the short using two hex characters (in C):

    short a = 0×12;
    short b = 0×34;

    So short a has 0×12 (18 decimal) and b is similar. Now, instead of using the characters “0-A”, let’s just use U N I and X. For example, U could be 3, N could be 4, I could be 5, and X could be 6.

    short a = 0xUN;
    short b = 0xIX;

    On any machine, these shorts would be stored consecutively in memory. Address 0 and 1 would be “a”, and address 2 and 3 would be “b”. [again, each short takes up 2 bytes].

    On a big-endian machine, the data would look like this:

    Addr 0: U
    Addr 1: N
    Addr 2: I
    Addr 3: X

    On a little-endian machine, we store the *smallest* part of the number first. That is, in a = 0xUN, we store “N” first, which are the low-order bits. So in memory it would look like this:

    Addr 0: N
    Addr 1: U
    Addr 2: X
    Addr 3: I

    Hence the “NUXI” problem. On a big-endian machine the data looks like UNIX, on a little-endian machine the data looks like NUXI. This isn’t a problem if you stay on the same machine (each machine knows how to convert appropriately), but can be a problem if you are exchanging binary data between machines.

    Hope this helps,

    -Kalid

    Kalid — December 10, 2007 @ 6:58 pm

  18. Well information is good!!!
    I have one query if there is not much advantage of Big-Endian over Little Endian then why Network Byte order is Big-Endian????

    SHAAN — December 17, 2007 @ 12:17 am

  19. While there are advantages to each, I don’t think one is clearly better than the other. I think they just had choose one or the other — Big Endian may have been a more popular format at the time :) .

    Kalid — December 17, 2007 @ 12:19 am

  20. Thank you so much. I’ve been lucky thus far doing high end coding, but having rolled my sleeves up to start mucking about with bit and bytes this has been very helpful indeed.

    kudos!

    Steve — December 20, 2007 @ 2:53 pm

  21. Thanks Steve, glad you found it useful! It’s fun to dip into bits & bytes every once in a while :) .

    Kalid — December 20, 2007 @ 4:22 pm

  22. That was damn good explanation. Thanks a lot for the post

    Ramkumar — December 28, 2007 @ 12:20 am

  23. Hi Ramkumar, you’re welcome. Glad you liked it.

    Kalid — December 28, 2007 @ 1:12 pm

  24. this is a great read

    socal — December 29, 2007 @ 6:12 pm

  25. Thanks Socal!

    Kalid — December 30, 2007 @ 1:52 am

  26. How can I change byte order from Big Endian to Little Endian and vice versa without breaking structure. When we are sending any structure.

    avvy — January 8, 2008 @ 3:21 am

  27. Hi avvy, you can use the “host to network” and “network to host” functions to convert data (more info here: http://linux.die.net/man/3/htons). You’d have to convert each field in the structure separately.

    Kalid — January 8, 2008 @ 2:38 pm

  28. Hi Kalid,

    I cannot get a better picture of this topic wherever i search. I was searching about endianness in a hurry as was looking for some stuff which can atleast give the details in short and i feel lucky to find your post. Your post came like an angel as i had some urgency to find about endianness faster.
    Very briefly you told whole story about it. Details perfect…Flow of explanation perfect! Kudos!
    I’ll appreciate if you drop a small mail whenver you post stuff like this. :)

    Thanks a tonn!

    Shweta — January 17, 2008 @ 7:41 am

  29. Hi Shweta, glad you liked the article! If you’d like to receive emails when new posts appear, just enter your email address in the “subscribe” form on the upper-right of the page. Thanks for the comment.

    Kalid — January 17, 2008 @ 10:23 am

  30. Hey thanks for info..done it :)
    Hoping to see something informative soon.

    Shweta — January 18, 2008 @ 4:06 am

  31. You have a ‘locaiton’ in there, if you care to fix it.
    I second everyone else and say that this is awesome. I didn’t even know this issue existed and now I understand it well (I think).

    I think you should mention explicitly that if you store ‘UNIX’ in little-endian it will end up as NUXI in big-endian. Not strictly necessary, but I think it would Explain it Better. (Even Better.) Or perhaps lainExp it terBet. (enEv terBet.)

    Whenever I see the word endian I think first of Ender Wiggin. The enemy is down and so forth.

    Alrenous — March 14, 2008 @ 7:54 pm

  32. Thanks Alrenous, glad you’re enjoying it. Appreciate the tip — went through and cleaned up a bunch of typos (it’s a bit embarrassing how many were in there).

    Good suggestion on the explanation, I’m always looking for ways to make things clearer (that’s why this isn’t best explained :) ).

    I hadn’t made the Ender Wiggin association, but I love Ender’s game… maybe there’s a way to fit him and Bean into this article somewhere.

    Kalid — March 14, 2008 @ 8:10 pm

  33. Gr8 post. really helpful.

    Breeson — April 6, 2008 @ 10:48 pm

  34. Thanks Breeson, glad it was useful.

    Kalid — April 28, 2008 @ 10:24 pm

  35. Thanks alot Kalid ur explanation about endianness is awesome. I hve one question that need to be answered Is there is a way In a mixed binary file with 4 byte Integers and single byte characters to identify whether the byte we read from the file is a character or is a part of 4 byte integer data.

    It would be very helpful if you can answer me

    Kumar — April 30, 2008 @ 12:08 pm

  36. Hi Kumar, glad you enjoyed it. Offhand, I don’t think there’s a way, looking at the raw data, to tell whether it’s supposed to be a character or integer.

    I think you’d need the file format spec to figure out the structure of the data — for example, the TCP header defines the byte ranges for each field.

    Kalid — April 30, 2008 @ 12:20 pm

  37. hi,
    actually i know nothing about the c-language, but i have a question.
    i have a riddle. it is: 71+, OÄE
    i have to get numbers of this riddle, that is, i have to “translate” this riddle somehow into numbers.
    and i know, that it has something to do with the c or c++ language and also something with intel byte order…
    can you please help me? i would be most thankful.

    joonas — May 4, 2008 @ 2:14 am

  38. Hi Joonas, I’m not exactly sure of the question — you have raw data and need to extract a value?

    If you have 4 bytes (for example), I would examine it as a 4-byte integer, 4-byte unsigned integer, 2 shorts, and 4 characters (all in the big and little endian varieties). I’d then try to look for some pattern in the results.

    Kalid — May 4, 2008 @ 11:00 am

RSS feed for comments on this post. TrackBack URI

Leave a comment

Have a question? Know an explanation that caused your own a-ha moment? Write about it here.




Like it? Try All articles, RSS Feed or Email Subscription | Idea or suggestion? Contact me
copyright © 2007 Kalid Azad