import ctypes
def main():
key = 0xDEADBEEF
print(f"({type(key)}) {key = }")
key = key * 5 + 2
print(f"({type(key)}) {key = }")
key = key * 5 + 2
print(f"({type(key)}) {key = }")
key = key * 5 + 2
print(f"({type(key)}) {key = }")
key = ctypes.c_uint(0xDEADBEEF)
print(f"({type(key)}) {key.value = }")
key = ctypes.c_uint(key.value * 5 + 2)
print(f"({type(key)}) {key.value = }")
key = ctypes.c_uint(key.value * 5 + 2)
print(f"({type(key)}) {key.value = }")
key = ctypes.c_uint(key.value * 5 + 2)
print(f"({type(key)}) {key.value = }")
if __name__ == '__main__':
main()
If you run this it outputs:
(<class 'int'>) key = 3735928559
(<class 'int'>) key = 18679642797
(<class 'int'>) key = 93398213987
(<class 'int'>) key = 466991069937
(<class 'ctypes.c_ulong'>) key.value = 3735928559
(<class 'ctypes.c_ulong'>) key.value = 1499773613
(<class 'ctypes.c_ulong'>) key.value = 3203900771
(<class 'ctypes.c_ulong'>) key.value = 3134601969
Why does it keep growing on python?
Is there any way to replicate what C does without using ctypes or is it just how python works?
Okay, thanks to @n.1.8e9-where's-my-sharem I've understood that in C uint
get's capped at 32 bits and to get this same effect in python we can use number modulo 2^32
def main():
key_a = 0xDEADBEEF
key_b = uint32(0xDEADBEEF)
printBinary(key_a)
printBinary(key_b)
print('--')
key_a = key_a * 5 + 2
key_b = uint32(key_b * 5 + 2)
printBinary(key_a)
printBinary(key_b)
print('--')
key_a = key_a * 5 + 2
key_b = uint32(key_b * 5 + 2)
printBinary(key_a)
printBinary(key_b)
print('--')
key_a = key_a * 5 + 2
key_b = uint32(key_b * 5 + 2)
printBinary(key_a)
printBinary(key_b)
print('--')
key_a = key_a * 5 + 2
key_b = uint32(key_b * 5 + 2)
printBinary(key_a)
printBinary(key_b)
def printBinary(argument) -> None:
binary_repr = f"{argument:<15} in binary {argument:>43b}"
print(binary_repr)
def uint32( n, num_bits=32):
return n % (2 ** num_bits)
if __name__ == '__main__':
main()
So if we run the previous code we get this:
3735928559 in binary 11011110101011011011111011101111
3735928559 in binary 11011110101011011011111011101111
--
18679642797 in binary 10001011001011001001011101010101101
1499773613 in binary 1011001011001001011101010101101
--
93398213987 in binary 1010110111110111101111010010101100011
3203900771 in binary 10111110111101111010010101100011
--
466991069937 in binary 110110010111010110101100011101011110001
3134601969 in binary 10111010110101100011101011110001
--
2334955349687 in binary 100001111110100110001011110010011010110111
2788107959 in binary 10100110001011110010011010110111
Where we can see that with the function uint32
we can keep the first 32 bits of the key.
Python integers can be of any magnitude (they are bignums). In many other programming languages, integers are of fixed size and overflow/wrap around modulo 2number_of_bits.
If you want to replicate the behaviour of these languages in Python, the easiest way is to add an explicit reduction modulo 2number_of_bits. There are several equivalent ways to express this reduction:
n = n % (2**nbits)
n = n % (1<<nbits)
n = n & ((1<<nbits) - 1)