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(×tamp);
188
189 cout << ctime(×tamp);
190
191 int ts = sizeof(timestamp);
192 cout << "timestamp decimal and size: " << timestamp << " : " << ts << endl;
193 vector<uint8_t> timestampbytes(reinterpret_cast<uint8_t*>(×tamp), reinterpret_cast<uint8_t*>(×tamp) + 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.