How to make 2+2=5 in Python

Quinten Lisowe
4 min readNov 29, 2020
Photo by Chris Ried on Unsplash

It’s a well known fact that you can redefine keywords, characters, numbers, and other aspects of some programming languages. For example, one of the things you can do in C++ is swap out various keywords for emojis.

Seeing as my language of choice is Python, I wanted to see if I could implement something just as fun. I’ll be doing everything here in Python 3.9 x64 as a reference since taking advantage of low level quirks like this tends to change over time.

Unfortunately the way Python handles Unicode characters restricts it from being able to do a lot of things. We’re not able to use emoji’s as variable names (😢), let alone keywords, and we certainly can’t redefine simple logic such as 2+2=4…right?

The reason C, C++, and other languages are able to pull off these kinds of shenanigans is because they have a way of telling their pre-processor what to do. Python doesn’t have a pre-processor, it has an interpreter. It’s a part of what makes the language so easy to use — it abstracts a lot of those pesky details away to shift the focus away from execution speed of the program to development speed of the software developer. For normal use cases this functions as a universal and easy to use platform to develop software on. Things get difficult when you want to modify the standard functionality, but there is a way to hack it.

Let’s try to do something fun, let’s try to get the python interpreter to make 2+2equal 5. I don’t mean something cheesy like print("2+2=5"), I mean take the integer 2 and and combine it with another integer 2 and have the output equal 5.

To do this we need to alter a fundamental aspect of Python in how it interprets numbers. Python is built with C, which means that if we can access Python at a low enough level, we might be able to emulate the #define behavior of C. Luckily for us, there is a built in library called ctypes that lets us access python at this level. So basically, every number in python has a specific id associated with it. We can access this id by doing id(x):

>>> id(4)
2077461146000

What we’re going to do is abuse the fact that we can access these id’s at the C level so that whenever the python interpreter tries to access the number 4, it’s immediately redirected to 5. Truth be told, it’s actually quite simple. All we need to do is set the memory location of 4 equal to 5 and voila!

import ctypes
offset = 24
ctypes.c_int8.from_address(id(4) + offset).value = 5

Now if you type 2 + 2 into python it will return 5!…but there’s something weird happening. First of all, what’s up with that offset number? The id’s for 4 and 5 are 32 numbers apart and yet we only added 24:

>>> id(4)
2077461146000
>>> id(5)
2077461146032

Elaborating off of that, it would stand to reason that we should set the value of 4 directly equal to the id of 5 instead of adding any offset at all, but unfortunately doing so has no effect on what 2+2 evaluates to. Varying degrees of offsets produce various effects, some causing integer overflows and others outright crashing the program with no Exceptions being thrown. So for now though let’s run with the idea that adding offset=24 causes 2+2 to equal 5.

While initially things seem to have worked, when running with this assumption that 4 has seamlessly been redefined as 5, upon closer inspection we find that there are some cracks in this theory.

print(4)
print(2 + 2)
print(6 - 2)
print(2 * 2)

All of these evaluate the correct answer of 4 as 5, but when we turn it into a float:

print(8 / 2)

It correctly evaluates to 4.0. This happens because id(4) is not in the same memory location as id(float(4)). Where things really get interesting is when we do

pow(2, 4)

Usually this would evaluate as 2⁴=16, but with our New Math it evaluates 2⁴=50, which would in theory put our new 4 number equal to 5.6438561898. Oddly enough, when using 4 as both the base and the exponent it seems to evaluate as expected:

>>> pow(4, 4)
3125

Let’s take this to the next level

Instead of just redefining a single number what about redefining a range of numbers?

import struct
import ctypes
import random

offset = 24
# amount of numbers to redefine
num = 100
nums = list(range(num))
addresses = [id(x) + offset for x in nums]
random.shuffle(nums)

for a, n in zip(addresses, nums):
ctypes.c_ssize_t.from_address(a).value = n
# Remember, whatever num is equal to changes each time, so the loop length will change
for redefined_number in range(num):
print(redefined_number)
print('2 + 2 =', 2+2)
print('9 - 4 =', 9-4)
print('5 * 6 =', 5*6)
# print('1 / 0 =\n', 1/0)

This script takes the first hundred integers and randomly assigns them a new value. Functionally this should allow you to divide by “zero”, but there seems to be an additional check on Python’s side that automatically throws a “division by zero” Exception. Another thing to note here is that the redefined numbers aren’t necessarily unique, meaning that several numbers can be redefined as a single number, leaving some to not be defined at all. 9 and 12 can both be mapped to 33, but 33 can only be mapped to 9 or 12, but not both.

The practical applications of this knowledge are left as an exercise to the reader.

If you like this kind of content I encourage you to follow me for more

--

--