Skip to content

Commit aca55f4

Browse files
author
rodrigo.nogueira
committed
Add endianness conversion algorithm
- Implements byte order conversions for 16/32/64-bit integers - Provides bidirectional bytes ↔ int conversions - Supports both big-endian and little-endian formats - Includes comprehensive doctests with real-world examples - Documents use cases: network protocols, cryptography, file formats - All tests pass: ruff, mypy, doctests
1 parent 2c15b8c commit aca55f4

File tree

1 file changed

+329
-0
lines changed

1 file changed

+329
-0
lines changed

conversions/endianness.py

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
"""
2+
Endianness Conversion Algorithm
3+
4+
This module implements endianness (byte order) conversion utilities for converting
5+
between big-endian and little-endian representations of multi-byte integers.
6+
7+
Endianness refers to the order of bytes in a multi-byte data type:
8+
- Big-endian: Most significant byte first (e.g., 0x12345678 → [0x12, 0x34, 0x56, 0x78])
9+
- Little-endian: Least significant byte first
10+
(e.g., 0x12345678 → [0x78, 0x56, 0x34, 0x12])
11+
12+
Common uses:
13+
- Network protocols (TCP/IP uses big-endian for multi-byte fields)
14+
- File format parsing (PNG, JPEG headers specify endianness)
15+
- Cryptographic algorithms (MD5 uses little-endian, SHA-256 uses big-endian)
16+
- Binary data serialization (Protocol Buffers, MessagePack)
17+
- Hardware interfacing (ARM is bi-endian, x86 is strictly little-endian)
18+
- Cross-platform data exchange
19+
20+
Real-world examples:
21+
- IP addresses are transmitted in big-endian (network byte order)
22+
- Modern ARM and x86 processors typically operate in little-endian mode
23+
- Java class files use big-endian format
24+
- Bitcoin uses little-endian for block hashes
25+
- USB and PCI protocols use little-endian
26+
27+
References:
28+
- https://en.wikipedia.org/wiki/Endianness
29+
- RFC 1700 (Network Byte Order)
30+
- https://tools.ietf.org/html/rfc1700
31+
"""
32+
33+
34+
def swap_endianness_16(value: int) -> int:
35+
"""
36+
Swap the byte order of a 16-bit integer.
37+
38+
Args:
39+
value: 16-bit integer (0 to 65,535)
40+
41+
Returns:
42+
Integer with swapped byte order
43+
44+
Raises:
45+
ValueError: If value is negative or exceeds 16-bit range
46+
47+
>>> swap_endianness_16(0x1234)
48+
13330
49+
50+
>>> hex(swap_endianness_16(0x1234))
51+
'0x3412'
52+
53+
>>> swap_endianness_16(0xABCD)
54+
52651
55+
56+
>>> hex(swap_endianness_16(0xABCD))
57+
'0xcdab'
58+
59+
>>> swap_endianness_16(0)
60+
0
61+
62+
>>> swap_endianness_16(0xFFFF)
63+
65535
64+
"""
65+
if value < 0 or value > 0xFFFF:
66+
msg = f"value must be between 0 and {0xFFFF}"
67+
raise ValueError(msg)
68+
69+
return ((value & 0xFF) << 8) | ((value & 0xFF00) >> 8)
70+
71+
72+
def swap_endianness_32(value: int) -> int:
73+
"""
74+
Swap the byte order of a 32-bit integer.
75+
76+
Args:
77+
value: 32-bit integer (0 to 4,294,967,295)
78+
79+
Returns:
80+
Integer with swapped byte order
81+
82+
Raises:
83+
ValueError: If value is negative or exceeds 32-bit range
84+
85+
>>> swap_endianness_32(0x12345678)
86+
2018915346
87+
88+
>>> hex(swap_endianness_32(0x12345678))
89+
'0x78563412'
90+
91+
>>> swap_endianness_32(0xDEADBEEF)
92+
4022250974
93+
94+
>>> hex(swap_endianness_32(0xDEADBEEF))
95+
'0xefbeadde'
96+
97+
>>> swap_endianness_32(0)
98+
0
99+
100+
>>> swap_endianness_32(0xFFFFFFFF)
101+
4294967295
102+
103+
>>> swap_endianness_32(0x01020304)
104+
67305985
105+
"""
106+
if value < 0 or value > 0xFFFFFFFF:
107+
msg = f"value must be between 0 and {0xFFFFFFFF}"
108+
raise ValueError(msg)
109+
110+
return (
111+
((value & 0x000000FF) << 24)
112+
| ((value & 0x0000FF00) << 8)
113+
| ((value & 0x00FF0000) >> 8)
114+
| ((value & 0xFF000000) >> 24)
115+
)
116+
117+
118+
def swap_endianness_64(value: int) -> int:
119+
"""
120+
Swap the byte order of a 64-bit integer.
121+
122+
Args:
123+
value: 64-bit integer (0 to 18,446,744,073,709,551,615)
124+
125+
Returns:
126+
Integer with swapped byte order
127+
128+
Raises:
129+
ValueError: If value is negative or exceeds 64-bit range
130+
131+
>>> swap_endianness_64(0x0123456789ABCDEF)
132+
17279655951921914625
133+
134+
>>> hex(swap_endianness_64(0x0123456789ABCDEF))
135+
'0xefcdab8967452301'
136+
137+
>>> swap_endianness_64(0xFEDCBA9876543210)
138+
1167088121787636990
139+
140+
>>> hex(swap_endianness_64(0xFEDCBA9876543210))
141+
'0x1032547698badcfe'
142+
143+
>>> swap_endianness_64(0)
144+
0
145+
146+
>>> swap_endianness_64(0xFFFFFFFFFFFFFFFF)
147+
18446744073709551615
148+
"""
149+
if value < 0 or value > 0xFFFFFFFFFFFFFFFF:
150+
msg = f"value must be between 0 and {0xFFFFFFFFFFFFFFFF}"
151+
raise ValueError(msg)
152+
153+
return (
154+
((value & 0x00000000000000FF) << 56)
155+
| ((value & 0x000000000000FF00) << 40)
156+
| ((value & 0x0000000000FF0000) << 24)
157+
| ((value & 0x00000000FF000000) << 8)
158+
| ((value & 0x000000FF00000000) >> 8)
159+
| ((value & 0x0000FF0000000000) >> 24)
160+
| ((value & 0x00FF000000000000) >> 40)
161+
| ((value & 0xFF00000000000000) >> 56)
162+
)
163+
164+
165+
def bytes_to_int_little(data: bytes) -> int:
166+
"""
167+
Convert bytes to integer using little-endian byte order.
168+
169+
Args:
170+
data: Byte sequence to convert (1-8 bytes)
171+
172+
Returns:
173+
Integer representation in little-endian order
174+
175+
Raises:
176+
TypeError: If data is not bytes
177+
ValueError: If data is empty or exceeds 8 bytes
178+
179+
>>> bytes_to_int_little(b'\\x78\\x56\\x34\\x12')
180+
305419896
181+
182+
>>> hex(bytes_to_int_little(b'\\x78\\x56\\x34\\x12'))
183+
'0x12345678'
184+
185+
>>> bytes_to_int_little(b'\\x01\\x02')
186+
513
187+
188+
>>> bytes_to_int_little(b'\\xff')
189+
255
190+
191+
>>> bytes_to_int_little(b'\\x00\\x00\\x00\\x01')
192+
16777216
193+
"""
194+
if not data or len(data) > 8:
195+
msg = "data must be between 1 and 8 bytes"
196+
raise ValueError(msg)
197+
198+
return int.from_bytes(data, byteorder="little")
199+
200+
201+
def bytes_to_int_big(data: bytes) -> int:
202+
"""
203+
Convert bytes to integer using big-endian byte order.
204+
205+
Args:
206+
data: Byte sequence to convert (1-8 bytes)
207+
208+
Returns:
209+
Integer representation in big-endian order
210+
211+
Raises:
212+
TypeError: If data is not bytes
213+
ValueError: If data is empty or exceeds 8 bytes
214+
215+
>>> bytes_to_int_big(b'\\x12\\x34\\x56\\x78')
216+
305419896
217+
218+
>>> hex(bytes_to_int_big(b'\\x12\\x34\\x56\\x78'))
219+
'0x12345678'
220+
221+
>>> bytes_to_int_big(b'\\x01\\x02')
222+
258
223+
224+
>>> bytes_to_int_big(b'\\xff')
225+
255
226+
227+
>>> bytes_to_int_big(b'\\x00\\x00\\x00\\x01')
228+
1
229+
"""
230+
if not data or len(data) > 8:
231+
msg = "data must be between 1 and 8 bytes"
232+
raise ValueError(msg)
233+
234+
return int.from_bytes(data, byteorder="big")
235+
236+
237+
def int_to_bytes_little(value: int, num_bytes: int) -> bytes:
238+
"""
239+
Convert integer to bytes using little-endian byte order.
240+
241+
Args:
242+
value: Integer to convert (non-negative)
243+
num_bytes: Number of bytes in output (1-8)
244+
245+
Returns:
246+
Bytes representation in little-endian order
247+
248+
Raises:
249+
ValueError: If value is negative, num_bytes invalid, or value too large
250+
251+
>>> int_to_bytes_little(0x12345678, 4)
252+
b'xV4\\x12'
253+
254+
>>> int_to_bytes_little(513, 2)
255+
b'\\x01\\x02'
256+
257+
>>> int_to_bytes_little(255, 1)
258+
b'\\xff'
259+
260+
>>> int_to_bytes_little(16777216, 4)
261+
b'\\x00\\x00\\x00\\x01'
262+
"""
263+
if value < 0:
264+
msg = "value must be non-negative"
265+
raise ValueError(msg)
266+
267+
if num_bytes < 1 or num_bytes > 8:
268+
msg = "num_bytes must be between 1 and 8"
269+
raise ValueError(msg)
270+
271+
if value >= (1 << (num_bytes * 8)):
272+
msg = f"value {value} too large for {num_bytes} bytes"
273+
raise ValueError(msg)
274+
275+
return value.to_bytes(num_bytes, byteorder="little")
276+
277+
278+
def int_to_bytes_big(value: int, num_bytes: int) -> bytes:
279+
"""
280+
Convert integer to bytes using big-endian byte order.
281+
282+
Args:
283+
value: Integer to convert (non-negative)
284+
num_bytes: Number of bytes in output (1-8)
285+
286+
Returns:
287+
Bytes representation in big-endian order
288+
289+
Raises:
290+
ValueError: If value is negative, num_bytes invalid, or value too large
291+
292+
>>> int_to_bytes_big(0x12345678, 4)
293+
b'\\x124Vx'
294+
295+
>>> int_to_bytes_big(258, 2)
296+
b'\\x01\\x02'
297+
298+
>>> int_to_bytes_big(255, 1)
299+
b'\\xff'
300+
301+
>>> int_to_bytes_big(1, 4)
302+
b'\\x00\\x00\\x00\\x01'
303+
"""
304+
if value < 0:
305+
msg = "value must be non-negative"
306+
raise ValueError(msg)
307+
308+
if num_bytes < 1 or num_bytes > 8:
309+
msg = "num_bytes must be between 1 and 8"
310+
raise ValueError(msg)
311+
312+
if value >= (1 << (num_bytes * 8)):
313+
msg = f"value {value} too large for {num_bytes} bytes"
314+
raise ValueError(msg)
315+
316+
return value.to_bytes(num_bytes, byteorder="big")
317+
318+
319+
if __name__ == "__main__":
320+
import doctest
321+
322+
_ = doctest.testmod()
323+
324+
print("Endianness Conversion Examples:")
325+
print(f"16-bit: 0x1234 → {hex(swap_endianness_16(0x1234))}")
326+
print(f"32-bit: 0x12345678 → {hex(swap_endianness_32(0x12345678))}")
327+
print(f"64-bit: 0x0123456789ABCDEF → {hex(swap_endianness_64(0x0123456789ABCDEF))}")
328+
print(f"Bytes to int (little): {bytes_to_int_little(b'\x78\x56\x34\x12'):#x}")
329+
print(f"Bytes to int (big): {bytes_to_int_big(b'\x12\x34\x56\x78'):#x}")

0 commit comments

Comments
 (0)