minofoto and miscellaneous notes

ごく気まぐれに,書きたいことを適当に書いています。本当の話かもしれませんし,フィクションかもしれません。

円周率の計算(Machin の式)

Ruby で遊んでいたら、高校生時代に友達と円周率を計算して遊んでいたことを思い出した。当時はまだコンピュータを持っておらず、関数電卓で円に内接する多角形の面積を計算したり、解析概論から Machin の式を見つけ出して計算したりしていた。友達が PC-9801 を持っていたが、どうやって倍精度計算をするか、その限界を越えるにはどうしたらいいか、といった話をグダグタとしていた。

Machin の式はこんな感じ。

 \pi = 16(\frac{1}{5} - \frac{1}{3 \times 5^3} + \frac{1}{5 \times 5^5} - ...) - 4(\frac{1}{239} - \frac{1}{3 \times 239^3} + \frac{1}{5 \times 239^5} - ...)

この式が良いのは、冪乗があるものの、加減乗除だけで構成されていて単純なことだ。
ruby で書くと以下のようになる。
ruby は整数の計算は桁数の制限がないことと、この式はひたすら項を加減していくだけなので、全部の項に factor をかけておくだけで、小数の計算精度を考えずに計算できる。

def Machin()
  prec = 100
  matched_index = 0
  loop = 30

  factor = 1
  for i in 1..prec do
    factor *= 10
  end
  factor1 = 16 * factor
  factor2 =  4 * factor
  sum = factor1 / 5 - factor2 / 239

  div = 3
  for i in 1..loop
    sum += -factor1 / (div * 5**div)   + factor1 / ((div+2) * 5**(div+2))
    sum -= -factor2 / (div * 239**div) + factor2 / ((div+2) * 239**(div+2))
    div += 4
    puts "#{sprintf("%8d",i)} #{sum}"
  end
  return sum
end

$start = Time.now
puts "Machin の式による円周率"
Machin()
puts Time.now-$start

古い MacBook Pro (Core i7-4578U 3GHz dual core) で実行してみると意外に速い。1 msec 程度で小数点以下 30桁近く計算できているようにみえる。

$ ruby machin.rb
Machin の式による円周率
       1 31416210293250344250468325171164080697062446182134305548606396697574415082581156863085774715303620079
       2 31415926824043995172402598360735758604897579967201355063017547035914903487748109950086128003694245533
       3 31415926536235547619955045938203118459271322712991732260010405689815976684675012175940708988985388463
       4 31415926535898358474857006722516843955090730354281179593931838398495000890439868309465181373830789786
       5 31415926535897932947473748577153435433787472210278399039415887536036600624560331366617000933453000146
       6 31415926535897932385393245926718652825091820036365355561499505447671429038153835703541996763189559804
       7 31415926535897932384627502076754923578603952858843005625356465856445914191569978625053179643878350850
       8 31415926535897932384626435346265610108418224608598626348719124702975728463343742259229373035461723957
       9 31415926535897932384626433834967774246425946616320634070726846710697736185351464266957897915043585053
      10 31415926535897932384626433832798181320873204152280609055092074978365028242887424241942263143313044890
      11 31415926535897932384626433832795033456017380588539006987908612446065286640820240779409963401710977707
      12 31415926535897932384626433832795028848774340169568717799892980661436541525752937262258421934060087564
      13 31415926535897932384626433832795028841981785615050020762231123465728069457680425238581026447563602177
      14 31415926535897932384626433832795028841971709044323951350064488696988037559753790469840994549636967408
      15 31415926535897932384626433832795028841971694016301271684489234984290065900959680994986868403485258600
      16 31415926535897932384626433832795028841971693993784982166044833812760297891191671226977100393717248832
      17 31415926535897932384626433832795028841971693993751109427006988812698398829976010400673834116408142191
      18 31415926535897932384626433832795028841971693993751058287322259793318396065343944029846128154609955813
      19 31415926535897932384626433832795028841971693993751058209867272216277219504082451389153487461969263172
      20 31415926535897932384626433832795028841971693993751058209749625351706126852156677062114887680753448201
      21 31415926535897932384626433832795028841971693993751058209749446196953453517888686699842356993297743027
      22 31415926535897932384626433832795028841971693993751058209749445923497087355714539445875926316609121046
      23 31415926535897932384626433832795028841971693993751058209749445923078806095979536420509089060197389981
      24 31415926535897932384626433832795028841971693993751058209749445923078165048578878411934919267800531363
      25 31415926535897932384626433832795028841971693993751058209749445923078164064377944349349611767129831896
      26 31415926535897932384626433832795028841971693993751058209749445923078164062864424622439667791268307192
      27 31415926535897932384626433832795028841971693993751058209749445923078164062862093586998418682337244371
      28 31415926535897932384626433832795028841971693993751058209749445923078164062862089991840979712133067678
      29 31415926535897932384626433832795028841971693993751058209749445923078164062862089986288946017364411264
      30 31415926535897932384626433832795028841971693993751058209749445923078164062862089986280361562208912209
0.001099


思ったより早かったので、調子に乗って、桁数を増やしてみた。また、項を加えても変わらない桁までを計算できたとみなして、その桁だけを表示するように変えてみた。

## digit 桁(前の計算でマッチしたところ)から文字列の長さまでの s1, s2 を比較する。
## 計算が一致しているところまでを正しく計算できているとみなして、その桁数を返す
def check_match(s1,s2,digit)   
  matched_index = -1
  for i in digit..s1.length do
    if s1[i] == s2[i]
      matched_index = i
    else
      return matched_index
    end
  end
  return -1
end

def Machin()
  prec = 100000
  matched_index = 0
  loop = 10000

  str = "314"
  
  factor = 1
  for i in 1..prec do
    factor *= 10
  end
  factor1 = 16 * factor
  factor2 =  4 * factor
  sum = factor1 / 5 - factor2 / 239

  div = 3
  for i in 1..loop
    sum += -factor1 / (div * 5**div)   + factor1 / ((div+2) * 5**(div+2))
    sum -= -factor2 / (div * 239**div) + factor2 / ((div+2) * 239**(div+2))
    div += 4
    #puts "#{sprintf("%8d",i)} #{sum}"

    str_prev = str
    str = sum.to_s
    matched_index = check_match(str_prev, str, matched_index)
    puts "#{i} 回目の計算 (#{matched_index}桁 / #{(Time.now - $start)} sec)"
    puts "3.#{str[1..matched_index]}"  #計算結果の見た目を整える
    if matched_index < 0
      break
    end
  end
  return sum
end

$start = Time.now
puts "Machin の式による円周率"
Machin()
puts Time.now-$start

実行してみると、見た目で桁数が増えていくので楽しい。

$ ruby pi-machin.rb
Machin の式による円周率
1 回目の計算 (2桁 / 1.757707 sec)
3.14
2 回目の計算 (3桁 / 1.844319 sec)
3.141
3 回目の計算 (7桁 / 1.924601 sec)
3.1415926
4 回目の計算 (9桁 / 1.999731 sec)
3.141592653
5 回目の計算 (12桁 / 2.077495 sec)
3.141592653589
6 回目の計算 (16桁 / 2.154938 sec)
3.1415926535897932
7 回目の計算 (18桁 / 2.233316 sec)
3.141592653589793238
8 回目の計算 (21桁 / 2.31235 sec)
3.141592653589793238462
9 回目の計算 (24桁 / 2.404382 sec)
3.141592653589793238462643
10 回目の計算 (27桁 / 2.483269 sec)
3.141592653589793238462643383

......

273 回目の計算 (764桁 / 24.741014 sec)
3.14159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036001133053054882046652138414695194151160943305727036575959195309218611738193261179310511854807446237996274956735188575272489122793818301194912983367336244065664308602139494639522473719070217986094370277053921717629317675238467481846766940513200056812714526356082778577134275778960917363717872146844090122495343014654958537105079227968925892354201995611212902196086403441815981362977477130996051870721135000

25秒ほどで 760桁ほど計算できた。ただ、結果が合っているのかは確認していません...