python

python練習問題 | オイラー予想と計算回数

当サイトでは広告が表示される記事があります

フェルマーの最終定理に関係して「オイラー予想」をPythonでやってみましょう。

簡単な計算をして、解の探索探索回数を減らす工夫をしてみようと思います。

「オイラーの予想」についてです。15:50~16:48ぐらいをご覧ください。

Youtub「るーいのゆっくり科学」チャンネル より

この記事は、「Pythonでフェルマーの最終定理」の続きです。

せんわんこ
せんわんこ

よろしくー

長年授業でやってきたことをまとめて、 ITパスポートの無料Note(1度落ちちゃった方向け) 基本情報技術者対策のNoteを作ってます。興味あったらどーぞ(▼・ω・▼)

オイラーの予想

オイラーの予想とは

オイラーさんって1700年代の数学者がいます。

以下のような式を満たす、自然数x,y,z,wの組み合わせはない、って予想をしました。

なんですけど、これは予想が外れたようです。

x=2682440 y=15365639 z=18796760 w=20615673

これをpythonで確認してみましょう。

探索だと時間がかかるので、

「左辺と右辺の値をそれぞれ計算して表示してね」

x= 2682440
y=15365639
z=18796760
w=20615673

x = x**4 + y**4 + z**4
w = w**4
print(x)
print(w)
print(x-w)
180630077292169281088848499041
180630077292169281088848499041
0

ということで、限られた範囲を探索しても、外側に答えがあることもあるんですね。

1986年にコンピュータによる計算で発見されました。

200年も見つからなかった答えがある場合もあるので、

あらゆる数を計算することなく、あらゆる数について証明をする数学をする必要がある。

数学ってすごいですね。

5乗や6乗もあるので探索してみよう

5乗が成立する数は、x=27, y=84, z=110, w=133, v=144です。

150以下の探索で足りるので、やってみましょう。

from tqdm import tqdm
for x in tqdm(range(1,150)):
  for y in range(x,150):
    for z in range(y,150):
      for w in range(z,150):
        for v in range(1,150):
          if x**5 + y**5 + z**5 + w**5 == v**5:
            print(f"{x} {y} {z} {w} {v}") 

うそです。めちゃ時間かかって途中で止めました。

これからは、計算回数を減らす工夫を考えます。

計算回数を減らす改良をしてみよう

オイラー予想の5乗版は、探索時間がすごくかかります。

計算回数を減らすような改良を考えていきましょう。

重複する探索をしないようにする

フェルマー最終定理でも思ったのですが

出る答えが、x=3, y=4, z=5 とか x=4, y=3, z=5 みたいに入れ替わったのもでてくるので、

無駄な計算をしていますね。

なので、

x≦y≦z≦v≦wの範囲を探すように改良したい

と思いました。

from tqdm import tqdm
for x in tqdm(range(1,150)):
  for y in range(x,150):
    for z in range(y,150):
      for w in range(z,150):
        for v in range(w,150):
          if x**5 + y**5 + z**5 + w**5 == v**5:
            print(f"\n{x} {y} {z} {w} {v}") 

でもまだすごい時間かかりそうです。

計算回数を減らしてみる

vを振っている時に、x,y,z,wの値は変わりません。

いままでのプログラムだと、vが変わった時にも毎回計算をしています。

左辺が変わらないのにね。

じゃぁ、左辺が変わる、つまり、

wが変わった時にだけ、x,y,z,wの5乗の和を計算するようにしてみましょう。

from tqdm import tqdm
for x in tqdm(range(1,150)):
  for y in range(x,150):
    for z in range(y,150):
      for w in range(z,150):
        v1 = x**5 + y**5 + z**5 + w**5  # wが変わった時に左辺を計算する。
        for v in range(w,150):
          if v1 == v**5:
            print(f"\n{x} {y} {z} {w} {v}") 

少し速くなりました。

探索範囲を限定する

右辺はw=1~150を毎回5乗しています。

右辺の計算回数を減らしてみたいです。

左辺の5乗和がでたら、その値になりそうなwだけ計算ことを考えます。

左辺の5乗和を計算して、とりあえず変数v1に格納します。

このv1の1/5乗が自然数になっていれば良いんですよね。

しかし、変数v1を1/5乗すると計算誤差が発生する場合があります。

下のコードの計算結果は、ぴったり5にはなりません。コンピュータの計算には誤差があります。

# 計算誤差
x=5
x=x**5
x=x**(1/5)
print(x)
5.000000000000001

よって、

その値の周辺を探索することにします。

5.000000000000001の場合は、4~6を探索します。

4.999999999999999の場合は、3~5を探索します。

これでvの探索は3回だけになりました。

from tqdm import tqdm
for x in tqdm(range(27,29)):
  for y in range(84,87):
    for z in range(110,112):
      for w in range(133,135):
        v1 = x**5 + y**5 + z**5 + w**5
        st = v1**(1/5)
        st = int(st)
        for v in range(st-1,st+2):
          if v1==v**5:
            print(f"\n{x} {y} {z} {w} {v} {v1} {v**5}") 

実行すると、まぁ待てるかなぁと。

必要な時だけ計算する

計算誤差は小さいのと、探したい値は自然数(小数点以下が0)なのを考えて、

整数からのずれが小さいときだけ、「解なんじゃね?」ってことで計算することにします。

つまり、左辺の1/5乗が5.5や5.7の時に、右辺が5や6とかの整数になるわけはないので無視します。

左辺の1/5乗が5.000000000000001とか4.999999999999999だったら、w=4~6を探そうかなってしたい。

from tqdm import tqdm
for x in tqdm(range(1,150)):
  for y in range(x,150):
    for z in range(y,150):
      for w in range(z,150):
        v1 = x**5 + y**5 + z**5 + w**5
        st = v1**(1/5)
        if st-int(st)<0.00001:       #計算誤差が小さい時だけ探索する。
          st = int(st)
          for v in range(st-1,st+2): #計算誤差があるから、周囲を探してみる。
            if v1==v**5:
              print(f"\n{x} {y} {z} {w} {v} {v1} {v**5}") 

必要な計算だけをする

5.000000000000001の時も4.999999999999999の時も、5だけを候補として計算すれば良いですよね。

今までは、vの探索回数は3回(v=4, 5, 6)でしたが、1回(v=5)計算するだけにします。

from tqdm import tqdm
for x in tqdm(range(1,150)):
  for y in range(x,150):
    for z in range(y,150):
      for w in range(z,150):
        v1 = x**5 + y**5 + z**5 + w**5
        st = v1**(1/5)
        if st-int(st)<0.00001:
          v = round(st,0)    # 四捨五入
          if v1==v**5:
            print(f"\n{x} {y} {z} {w} {v} {v1} {v**5}") 

round()について

実は、round()は完全な四捨五入ではないです。

でもこの計算には影響ないです。

2.5や3.5のような「5」が絡むときに偶数丸めをするようです。

2.5は2に、3.5は4になります。

これは、5を必ず切り上げると、1~4の4個が切り捨て、5~9の5個が切り上げになって不平等になるからって統計的な理由です。

ですが、今回の計算では関係ありません。

for x in [0.5, 1.5, 2.5, 3.5, 4.5, 5.5]:
  print(f"{x} ->{round(x)}")
タイトルとURLをコピーしました