Endianness

Wednesday, 16 September 2026

First posted on eLearningWorld.

Introduction

Endianness is the order of the bytes in a computer system when representing a number that requires more than one byte to store it. In essence with positive numbers, greater than 255 and with storing either positive or negative, greater than -128 or 127. But why does that matter to me?

Disclaimers

Names / logos can be trademarks of their respective owners. Please review their website for details. I am independent from the organisations mentioned and am in no way writing for or endorsed by them. The information presented in this article is written according to my own understanding, there could be inaccuracies, so please do undertake your own research. The featured image is my copyright, please don’t use without my permission.

Representation

In reality to most people ’endianness’ doesn’t matter. Software just works, or doesn’t. You don’t need to know the why on both accounts, you just want the former and not the latter. But here is a thing, what if we take that aside and just understand because we want to discover something new?

All data in a computer is stored as bytes. Bytes are made up of bits, switches that can be on or off, binary. Eight bit make up a byte, four bits make a nibble. Two nibbles in every byte. The bits represent the placement value of a written number. Decimal has 1’s, 10’s, 100’s columns and so forth. Binary has 1’s. 2’s, 4’s columns and so on. The powers of two rather than the powers of ten. See my post ‘What is a Byte anyway?’.

A computer fundamentally only understands bytes and their values as a number and can compare the numbers. It does not understand that they can have a meaning, a representation. It is us that programs them to represent our world. The character we have used represented by a value in a single byte is one example of this. Of course there is ‘ UTF-8 ’ that uses one to four bytes to allow representation of more characters beyond what ‘ ASCII ’ can provide. This is great for when we wish to convey our languages, but what if we’re conveying just numbers for example? Then the number ‘100’ typed as digits, characters, becomes three bytes when we know that a single byte can represent it as a value.

But what about the number 404? Ignoring the ‘ Sign bit ’ and dealing with only positive numbers, then we need two bytes, sixteen bits, where the sixteenth bit represents the 65536’s column. Therefore 404 is ‘0000 0001 1001 0100’. Notice that the most significant byte (the one with a bit set for the 256’s column) is first, followed by the least significant byte. This arrangement is called ‘Big endian’ and follows how left to right languages like English are read. If the bytes were swapped then this is a called ‘Little endian’ with the least significant byte first. How the computer, the CPU decides which one it uses or both is down to the architecture. There is lots more on ‘ en.wikipedia.org/wiki/Endianness ’. Within a computer the endianness only really matters if the data is destined to be at some point read by another computer with a different endianness. This can be true of stored data and data that is sent over a network. The former could be ‘ UTF-16 ’ where it has a means of saying the byte order, the endianness with a ‘Byte Order Mark’, a ‘ BOM ’. Here I’m going to demonstrate conceptually about over a ‘ network ’, specifically the ‘ Internet protocol suite ’, where the endianness is ‘big’.

A demonstration

Time is something we all tend to be concerned about. The when of things, be it past, present or future. Storing time like anything else needs to be done as a number on a computer no matter how it is in the real world. One way of doing this is ‘ Unix Time ’ which now stores the number of seconds since the 00:00:00 on the 1st January 1970 as a 64 bit value, or 8 bytes. It was a 32 bit value, 4 bytes but that has the ‘ Year 2038 problem ’, another form of the ‘ Year 2000 problem ’ or ‘ Y2K ’.

Now we want one device to tell another the time an event happened. Therefore to adhere to the ‘Internet protocol suite’, we need to convert the time value from little to big endian if we are a little endian device. In this example I’m using a ‘ Raspberry Pi B+ ’ which is using little endian. There are ‘ functions ’ that already do this for us, such as ‘htonl’ and ‘htonll’, and back ‘ntohl’ and ‘ntohll’ – where ‘h’ is ‘host’, ‘n’ is network and ‘l’ stands for ’long’ – typically a 32 bit integer. But that would defeat understanding what is going on internally, as would use of the ‘ byteswap ’ function. Therefore I have adapted some functions from ‘ byteswap-c ’ that uses the bitwise shift ‘<<’ and ‘>>’ ‘ operators ’ to shift the bits of the bytes to in effect move the bytes within the given data type (uint32_t / uint64_t) with the bitwise ‘And’ - ‘&’ and ‘Or’ - ‘|’ ‘ operators ’ to produce the result.

The code is, ‘time.cpp’:

  1/**
  2 * @file time.cpp
  3 * @copyright (c) 2026 G J Barnard.
  4 *
  5 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
  6 * conditions are met:
  7 *  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  8 *  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
  9 *     disclaimer in the documentation and/or other materials provided with the distribution.
 10 *  3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
 11 *     derived from this software without specific prior written permission.
 12 *
 13 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 14 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 15 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 16 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 17 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 18 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
 19 * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 20 *
 21 * @par License
 22 * SPDX-License-Identifier: BSD-3-Clause - https://spdx.org/licenses/BSD-3-Clause.html.
 23 *
 24 * @version V1.0.0
 25 */
 26
 27#include <cstdint>
 28#include <ctime>
 29#include <iomanip>
 30#include <iostream>
 31#include <vector>
 32
 33using namespace std;
 34
 35// Byte swapping functions, adapted from: https://github.com/krxecs/byteswap-c.
 36// License: 0-clause BSD License (SPDX license code: `0BSD`): https://spdx.org/licenses/0BSD.html.
 37/**
 38 * @brief Swap the byte order of a 32-bit unsigned integer.
 39 * @param num The 32-bit unsigned integer to swap.
 40 * @return The byte-swapped 32-bit unsigned integer.
 41 */
 42uint32_t bswap32(uint32_t num) {
 43    int nums = sizeof(num);
 44    vector<uint8_t> numbytesp(reinterpret_cast<uint8_t*>(&num), reinterpret_cast<uint8_t*>(&num) + nums);
 45    cout << "bswap32 parameter dec value: " << dec << num << endl;
 46    cout << "bswap32 parameter hex value: " << hex << setw(8) << setfill('0') << num << endl;
 47    cout << "bswap32 parameter in memory: ";
 48    for (uint8_t i = 0; i < nums; i++) {
 49        cout << hex << setw(2) << setfill('0') << (int) numbytesp.at(i);
 50    }
 51    cout << endl;
 52
 53    // Note: Changed flipping of middle pair to be same as bswap64.
 54    uint32_t bshifted[4];
 55    bshifted[0] = (num >> 24);
 56    bshifted[1] = (num >> 8);
 57    bshifted[2] = (num << 8);
 58    bshifted[3] = (num << 24);
 59
 60    for (uint8_t i = 0; i < 4; i++) {
 61        cout << "byte " << dec << (int) i << " shifted: " << hex << setw(8) << setfill('0') << bshifted[i] << endl;
 62    }
 63
 64    uint32_t banded[4];
 65    banded[0] = bshifted[0] & 0xff;
 66    banded[1] = bshifted[1] & 0xff00;
 67    banded[2] = bshifted[2] & 0xff0000;
 68    banded[3] = bshifted[3] & 0xff000000;
 69
 70    for (uint8_t i = 0; i < 4; i++) {
 71        cout << "byte " << dec << (int) i << " bitwise anded: " << hex << setw(8) << setfill('0') << banded[i] << endl;
 72    }
 73
 74    uint32_t result = banded[0] | banded[1] | banded[2] | banded[3];
 75
 76    /*
 77    return ((num >> 24) & 0xff) |    // move byte 3 to byte 0
 78         ((num << 8) & 0xff0000) |   // move byte 1 to byte 2
 79         ((num >> 8) & 0xff00) |     // move byte 2 to byte 1
 80         ((num << 24) & 0xff000000); // byte 0 to byte 3
 81    */
 82
 83    vector<uint8_t> numbytesr(reinterpret_cast<uint8_t*>(&result), reinterpret_cast<uint8_t*>(&result) + nums);
 84    cout << "bswap32 all bytes swapped result dec value: " << dec << result << endl;
 85    cout << "bswap32 all bytes swapped result hex value: " << hex << setw(8) << setfill('0') << result << endl;
 86    cout << "bswap32 all bytes swapped result in memory: ";
 87    for (uint8_t i = 0; i < nums; i++) {
 88        cout << hex << setw(2) << setfill('0') << (int) numbytesr.at(i);
 89    }
 90    cout << endl;
 91
 92    return result;
 93}
 94
 95/**
 96 * @brief Swap the byte order of a 64-bit unsigned integer.
 97 * @param x The 64-bit unsigned integer to swap.
 98 * @return The byte-swapped 64-bit unsigned integer.
 99 */
100uint64_t bswap64(uint64_t num) {
101    int nums = sizeof(num);
102    vector<uint8_t> numbytesp(reinterpret_cast<uint8_t*>(&num), reinterpret_cast<uint8_t*>(&num) + nums);
103    cout << "bswap64 parameter dec value: " << dec << num << endl;
104    cout << "bswap64 parameter hex value: " << hex << setw(16) << setfill('0') << num << endl;
105    cout << "bswap64 parameter in memory: ";
106    for (uint8_t i = 0; i < nums; i++) {
107        cout << hex << setw(2) << setfill('0') << (int) numbytesp.at(i);
108    }
109    cout << endl;
110
111    uint64_t bshifted[8];
112    bshifted[0] = num >> 56;
113    bshifted[1] = num >> 40;
114    bshifted[2] = num >> 24;
115    bshifted[3] = num >> 8;
116    bshifted[4] = num << 8;
117    bshifted[5] = num << 24;
118    bshifted[6] = num << 40;
119    bshifted[7] = num << 56;
120
121    for (uint8_t i = 0; i < 8; i++) {
122        cout << "byte " << dec << (int) i << " shifted: " << hex << setw(16) << setfill('0') << bshifted[i] << endl;
123    }
124
125    uint64_t banded[8];
126    banded[0] = bshifted[0] & 0xff;
127    banded[1] = bshifted[1] & 0xff00;
128    banded[2] = bshifted[2] & 0xff0000;
129    banded[3] = bshifted[3] & 0xff000000;
130    banded[4] = bshifted[4] & 0xff00000000;
131    banded[5] = bshifted[5] & 0xff0000000000;
132    banded[6] = bshifted[6] & 0xff000000000000;
133    banded[7] = bshifted[7] & 0xff00000000000000;
134
135    for (uint8_t i = 0; i < 8; i++) {
136        cout << "byte " << dec << (int) i << " bitwise anded: " << hex << setw(16) << setfill('0') << banded[i] << endl;
137    }
138
139    uint64_t result =
140        banded[0] | banded[1] | banded[2] | banded[3] |
141        banded[4] | banded[5] | banded[6] | banded[7];
142
143    /*
144    return x >> 56                        // move byte 7 to byte 0
145         | ((x >> 40) & 0xff00)           // move byte 6 to byte 1
146         | ((x >> 24) & 0xff0000)         // move byte 5 to byte 2
147         | ((x >> 8) & 0xff000000)        // move byte 4 to byte 3
148         | ((x << 8) & 0xff00000000)      // move byte 3 to byte 4
149         | ((x << 24) & 0xff0000000000)   // move byte 2 to byte 5
150         | ((x << 40) & 0xff000000000000) // move byte 1 to byte 6
151         | x << 56;                       // move byte 0 to byte 7
152    */
153
154    vector<uint8_t> numbytesr(reinterpret_cast<uint8_t*>(&result), reinterpret_cast<uint8_t*>(&result) + nums);
155    cout << "bswap64 all bytes swapped result dec value: " << dec << result << endl;
156    cout << "bswap64 all bytes swapped result hex value: " << hex << setw(16) << setfill('0') << result << endl;
157    cout << "bswap64 all bytes swapped result in memory: ";
158    for (uint8_t i = 0; i < nums; i++) {
159        cout << hex << setw(2) << setfill('0') << (int) numbytesr.at(i);
160    }
161    cout << endl;
162
163    return result;
164}
165
166/**
167 * Sending and receiving time over a conceptual communications channel.
168 */
169int main() {
170    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
171        cout << "Little endian on: ";
172    #else
173        cout << "Big endian on: ";
174    #endif
175
176    #ifdef __linux__
177        cout << "Linux." << endl;
178    #elif _WIN32
179        cout << "Windows." << endl;
180    #else
181        cout << "Not Linux or Windows." << endl;
182    #endif
183
184    cout << "Current time is: ";
185
186    time_t timestamp;
187    time(&timestamp);
188
189    cout << ctime(&timestamp);
190
191    int ts = sizeof(timestamp);
192    cout << "timestamp decimal and size: " << timestamp << " : " << ts << endl;
193    vector<uint8_t> timestampbytes(reinterpret_cast<uint8_t*>(&timestamp), reinterpret_cast<uint8_t*>(&timestamp) + ts);
194    cout << "timestamp hex value: " << hex << timestamp << endl;
195    cout << "timestamp in memory: ";
196    for (uint8_t i = 0; i < ts; i++) {
197        cout << hex << setw(2) << setfill('0') << (int) timestampbytes.at(i);
198    }
199    cout << endl;
200
201    // Convert to network byte order for our endianness.
202
203    uint32_t tosend32 = timestamp;
204    uint64_t tosend64 = timestamp;
205    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
206        tosend32 = bswap32(tosend32);
207        tosend64 = bswap64(tosend64);
208    #endif
209
210    int tss32 = sizeof(tosend32);
211    vector<uint8_t> tosend32bytes(reinterpret_cast<uint8_t*>(&tosend32), reinterpret_cast<uint8_t*>(&tosend32) + tss32);
212
213    cout << "Sending: " << endl << "32: ";
214    for (uint8_t i = 0; i < tss32; i++) {
215        cout << hex << setw(2) << setfill('0') << (int) tosend32bytes.at(i);
216    }
217    cout << endl << "64: ";
218
219    int tss64 = sizeof(tosend64);
220    vector<uint8_t> tosend64bytes(reinterpret_cast<uint8_t*>(&tosend64), reinterpret_cast<uint8_t*>(&tosend64) + tss64);
221
222    for (uint8_t i = 0; i < tss64; i++) {
223        cout << hex << setw(2) << setfill('0') << (int) tosend64bytes.at(i);
224    }
225    cout << endl;
226
227    // Convert back from network byte order to our endianness.
228    uint8_t *fromsend32n8ar = tosend32bytes.data();
229    uint8_t *fromsend64n8ar = tosend64bytes.data();
230
231    cout << "Received: " << endl << "32: ";
232    for (uint8_t i = 0; i < tss32; i++) {
233        cout << hex << setw(2) << setfill('0') << (int) fromsend32n8ar[i];
234    }
235    cout << endl << "64: ";
236    for (uint8_t i = 0; i < tss64; i++) {
237        cout << hex << setw(2) << setfill('0') << (int) fromsend64n8ar[i];
238    }
239    cout << endl;
240
241    uint32_t *fromsend32nptr = reinterpret_cast<uint32_t *>(fromsend32n8ar);
242    uint32_t fromsend32n = *fromsend32nptr;
243    uint64_t *fromsend64nptr = reinterpret_cast<uint64_t *>(fromsend64n8ar);
244    uint64_t fromsend64n = *fromsend64nptr;
245    #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
246        uint32_t fromsend32h = bswap32(fromsend32n);
247        uint64_t fromsend64h = bswap64(fromsend64n);
248    #endif
249    cout << "Received 32 time value: " << dec << fromsend32h << " : " << hex << fromsend32h << endl;
250    cout << "Received 64 time value: " << dec << fromsend64h << " : " << hex << fromsend64h << endl;
251
252    cout << "Received 32 time is: ";
253    uint64_t rect32t = fromsend32h;
254    time_t *r32time = reinterpret_cast<time_t*>(&rect32t);
255    cout << ctime(r32time);
256
257    cout << "Received 64 time is: ";
258    uint64_t rect64t = fromsend64h;
259    time_t *r64time = reinterpret_cast<time_t*>(&rect64t);
260
261    cout << ctime(r64time);
262
263    return 0;
264}

You can also get the code from my ‘ GitHub ’.

Which when compiled, using ‘g++ time.cpp -o time’ on the Pi, and run gives us a result of:

gareth@matilda:~/code/C++ $ g++ time.cpp -o time
gareth@matilda:~/code/C++ $ ./time
Little endian on: Linux.
Current time is: Mon Sep  7 11:10:48 2026
timestamp decimal and size: 1788775848 : 8
timestamp hex value: 6a9e8da8
timestamp in memory: a88d9e6a00000000
bswap32 parameter dec value: 1788775848
bswap32 parameter hex value: 6a9e8da8
bswap32 parameter in memory: a88d9e6a
byte 0 shifted: 0000006a
byte 1 shifted: 006a9e8d
byte 2 shifted: 9e8da800
byte 3 shifted: a8000000
byte 0 bitwise anded: 0000006a
byte 1 bitwise anded: 00009e00
byte 2 bitwise anded: 008d0000
byte 3 bitwise anded: a8000000
bswap32 all bytes swapped result dec value: 2827853418
bswap32 all bytes swapped result hex value: a88d9e6a
bswap32 all bytes swapped result in memory: 6a9e8da8
bswap64 parameter dec value: 1788775848
bswap64 parameter hex value: 000000006a9e8da8
bswap64 parameter in memory: a88d9e6a00000000
byte 0 shifted: 0000000000000000
byte 1 shifted: 0000000000000000
byte 2 shifted: 000000000000006a
byte 3 shifted: 00000000006a9e8d
byte 4 shifted: 0000006a9e8da800
byte 5 shifted: 006a9e8da8000000
byte 6 shifted: 9e8da80000000000
byte 7 shifted: a800000000000000
byte 0 bitwise anded: 0000000000000000
byte 1 bitwise anded: 0000000000000000
byte 2 bitwise anded: 0000000000000000
byte 3 bitwise anded: 0000000000000000
byte 4 bitwise anded: 0000006a00000000
byte 5 bitwise anded: 00009e0000000000
byte 6 bitwise anded: 008d000000000000
byte 7 bitwise anded: a800000000000000
bswap64 all bytes swapped result dec value: 12145537948191817728
bswap64 all bytes swapped result hex value: a88d9e6a00000000
bswap64 all bytes swapped result in memory: 000000006a9e8da8
Sending:
32: 6a9e8da8
64: 000000006a9e8da8
Received:
32: 6a9e8da8
64: 000000006a9e8da8
bswap32 parameter dec value: 2827853418
bswap32 parameter hex value: a88d9e6a
bswap32 parameter in memory: 6a9e8da8
byte 0 shifted: 000000a8
byte 1 shifted: 00a88d9e
byte 2 shifted: 8d9e6a00
byte 3 shifted: 6a000000
byte 0 bitwise anded: 000000a8
byte 1 bitwise anded: 00008d00
byte 2 bitwise anded: 009e0000
byte 3 bitwise anded: 6a000000
bswap32 all bytes swapped result dec value: 1788775848
bswap32 all bytes swapped result hex value: 6a9e8da8
bswap32 all bytes swapped result in memory: a88d9e6a
bswap64 parameter dec value: 12145537948191817728
bswap64 parameter hex value: a88d9e6a00000000
bswap64 parameter in memory: 000000006a9e8da8
byte 0 shifted: 00000000000000a8
byte 1 shifted: 0000000000a88d9e
byte 2 shifted: 000000a88d9e6a00
byte 3 shifted: 00a88d9e6a000000
byte 4 shifted: 8d9e6a0000000000
byte 5 shifted: 6a00000000000000
byte 6 shifted: 0000000000000000
byte 7 shifted: 0000000000000000
byte 0 bitwise anded: 00000000000000a8
byte 1 bitwise anded: 0000000000008d00
byte 2 bitwise anded: 00000000009e0000
byte 3 bitwise anded: 000000006a000000
byte 4 bitwise anded: 0000000000000000
byte 5 bitwise anded: 0000000000000000
byte 6 bitwise anded: 0000000000000000
byte 7 bitwise anded: 0000000000000000
bswap64 all bytes swapped result dec value: 1788775848
bswap64 all bytes swapped result hex value: 000000006a9e8da8
bswap64 all bytes swapped result in memory: a88d9e6a00000000
Received 32 time value: 1788775848 : 6a9e8da8
Received 64 time value: 1788775848 : 6a9e8da8
Received 32 time is: Mon Sep  7 11:10:48 2026
Received 64 time is: Mon Sep  7 11:10:48 2026
gareth@matilda:~/code/C++ $

Where we can see the time being fetched and conceptually sent from one device to another. What can be confusing is the distinction between the bytes in memory and what is presented. The output hexadecimal value is in big endian (as readers of English understand it) whereas in memory it is stored as little endian. I’ve tried to make this clear in the output.

On a big endian device, no swapping will take place, so this does need to be run on a little endian device for the full effect. What the functions ‘bswap32’ and ‘bswap64’ show is how the bytes are reordered to match the desired endianness, switching from one to the other.

Conclusion

I hope this sheds some light on how computers represent real and manipulate real world concepts purely as numbers.

What has struck me about writing this article, is that I had intended it to be shorter but kept finding that one concept lead to another that then needed to be explained. That is perhaps the fear of computing, that there is so much to know even for something that on the surface appears simple. That I could have gone further and combined different concepts together into a collection that is then sent and received as a byte stream. But that would have been too far.