I've stumbled upon an "anomaly" in my code (Python).
Which one is bigger 2^3 or 3^2. Of course 3^2 beacause it's 9 and the other one is 8. What about 2^4 and 4^2? They're equal. Basically this was my idea: How many times is x^y different or equal to y^x, when x and y ranges from 1 to n (x and y are natural numbers). I wrote a code to it and for the first 60 there was nothing unusual. Then I was only interested in ratios ((times x^y = y^x) / (n^2 (all combinations))). I ran the code for bigger numbers and plotted it. I found a weird curve.
For the first 60 numbers the "diff ratio" was increasing, then it started to decrease with a "pattern" (I couldn't figure out what could it be exactly). I also noticed that the ratios not just decreasing and increasing, they are converging, and not to 0.5-0.5, but 0.75 and 0.25.
I can't figure out why does this happen, I tried to run the code with bigger numbers, but even after n=35000 it still hasn't crossed the previously mentioned limit. (I'm guessing it has something to do with the powers of 2)
What do you think, did I make a mistake somewhere in my code or I just didn't noticed a pattern? Does it have a mathematical background? Did I stumble upon a known mathematical problem?
First code with the scatter plot:
import numpy as np
import matplotlib.pyplot as plt
eqratios = []
diffratios = []
settings = np.arange(1, 1001, 10)
for setting in settings:
diff = 0
eq = 0
for x in np.arange(1, setting, 1):
for y in np.arange(1, setting, 1):
if (x**y != y**x):
diff += 1
else:
eq += 1
eqratio = eq / ((setting-1)**2)
diffratio = diff / ((setting -1) ** 2)
eqratios.append(eqratio)
diffratios.append(diffratio)
plt.figure(figsize=(10, 6))
plt.scatter(settings, diffratios)
plt.scatter(settings, eqratios)
plt.xlabel("n")
plt.ylabel("Blue: diff, Orange: eq")
plt.title("Scatter plot")
plt.grid(True)
plt.show()
And here is an improved, but complicated code to calculate with bigger numbers:
import numpy as np
settings = 35000
# 35k
# Diff Ratio: 0.7502916438079795
# Eq Ratio: 0.24970835619202048
# 21 k
# Diff Ratio: 0.7504859584121709
# Eq Ratio: 0.24951404158782908
# 19k
# Google Collab: - Diff Ratio: 0.7511174618072887 Eq Ratio: 0.24888253819271133
# PyCharm: Diff Ratio: 0.7505370849271196 Eq Ratio: 0.24946291507288043
n_values = settings - 1
total_combinations = n_values ** 2
x_range = np.arange(1, settings)
y_range = np.arange(1, settings)
x_matrix, y_matrix = np.meshgrid(x_range, y_range)
comparison_result = np.where(x_matrix**y_matrix != y_matrix**x_matrix, 1, 0)
diff_ratio = np.count_nonzero(comparison_result) / total_combinations
eq_ratio = 1 - diff_ratio # Since eq_ratio + diff_ratio = 1
print("Diff Ratio:", diff_ratio)
print("Eq Ratio:", eq_ratio)
(If you have suggestions about how can I improve my code to run with bigger numbers, I'd be thankful)
(PS.: Sorry for my bad english, I hope you can understand it)
It's a programming error.
The only cases where $x^y = y^x$ for integers $x$ and $y$ are when $x = y$ or when $x$ and $y$ are $2$ and $4$ (in either order). So if you try all pairs $(x,y)$ where $x, y \in \{1, 2, \ldots, n\}$, you should have exactly $n + 2$ cases of "equal" out of $n^2$ total cases, so the "eq ratio" should be $\dfrac1n + \dfrac2{n^2}$. As $n$ (your
settingvariable) gets larger, the "eq ratio" should rapidly converge to zero.But because you passed integer arguments to
numpy.arange, the variablesxandyhad integer values. Not the type of arbitrary-size integer value that you get if you enter583to an interactive python prompt, but signed integers stored in a finite number of bits. (When I tried it, I foundnumpy.int32as the type, but I suppose it is possible that your environment usednumpy.int64.)When you raise an even integer to an integer power, you get some multiple of a power of $2$. If the power is large enough, you will get a multiple of $2^{32}$; double the power, and you will get a multiple of $2^{64}$. When you do your exponentiation on signed integers of $32$ or $64$ bits, you actually get back a result equal to the correct value modulo $2^{32}$ or $2^{64}$, respectively, within the range of numbers representable by that integer type. Any multiple of $2^{32}$ (using $32$-bit integers) or $2^{64}$ (using $64$-bit integers) is congruent to $0$ modulo $2^{32}$ or $2^{64}$, respectively, so $0$ is the answer you get. Any power of an odd number will still be odd, however, so you will never get $0$.
As a result, when you try values of $x$ and $y$ over a large range,
x**yis zero for almost all values ofyfor almost all even values ofx, andy**xis zero for almost all values ofxfor almost all even values ofy. In other words, most of the time whenxandyare both positive, you are comparing zero to zero and your program says they're equal. (There are also a few unusual cases, for example,x**yandy**xare equal to the same non-zero value modulo $2^{32}$ when $x=14$ and $y=28$.) Andxandyare both positive in $25\%$ of all cases.If you pass floating-point numbers to
numpy.arange, for example,np.arange(1.0, 1001.0, 10.0), thenxandywill be floating-point numbers and you won't have the phenomenon wherex**ycomes out to zero. Instead, you will find that when the powers get large enough, the result isinf, and whenever bothx**yandy**xevaluate toinfthey are considered equal. In that case the "eq ratio" decreases toward zero at first, but oncesettingsgets to about $150$ you start gettinginfresults for some of the numbers, and these eventually overwhelm the correct results.Try plain old
rangeinstead ofnumpy.arange. Then you will get integers without limitation on the number of bits (or at least, the limit is so large you probably will not reach it) and you will get results that are in line with the mathematical results.But essentially any "eq ratio" different from $\dfrac1n + \dfrac2{n^2}$ will be the result of a programming error or some limitation of the computer and programming language.