A Tour of Goの練習問題を解説するシリーズ(1/11) – Exercise: Loops and Functions

みなさん、こんにちは。人類をGopherにしたいと考えているまるりんです。

A Tour of Goはプログラミング言語Goの入門サイトです。 A Tour of Goには練習問題があるのですが、なかなか難しいものが多いと思います。 そこでこのシリーズではA Tour of Goの練習問題を解説していきたいと思います。

今回は以下の問題を解説します。

Exercise: Loops and Functions

解答
https://play.golang.org/p/NzNp91yJKw_x


はじめに、

まず計算を 10 回繰り返してそれぞれの z を表示します。

とあるので10回繰り返してそれぞれのzを表示します。 それぞれのzがルート2に近づいているのが分かります。

ソース
https://play.golang.org/p/9vLBP_uxsxy

実行結果

1.5
1.4166666666666667
1.4142156862745099
1.4142135623746899
1.4142135623730951
1.414213562373095
1.4142135623730951
1.414213562373095
1.4142135623730951
1.414213562373095
1.414213562373095

次に、

x (1, 2, 3, ...) のさまざまな値に対する答えがどれほど近似し、 推測が速くなるかを確認してください。

とあるので10までの値でそれぞれの平方根を計算します。比較のためライブラリで計算した平方根を表示しています。 推測が速くなるかの確認はどうやるのか不明なため、スキップしました。 ルート2と3以外はライブラリの値と一致することを確認しました。

ソース
https://play.golang.org/p/tWEx8vFZonu

実行結果

x= 1, z=                  1, math.Sqrt()=                  1
x= 2, z=  1.414213562373095, math.Sqrt()= 1.4142135623730951
x= 3, z= 1.7320508075688774, math.Sqrt()= 1.7320508075688772
x= 4, z=                  2, math.Sqrt()=                  2
x= 5, z=   2.23606797749979, math.Sqrt()=   2.23606797749979
x= 6, z=  2.449489742783178, math.Sqrt()=  2.449489742783178
x= 7, z= 2.6457513110645907, math.Sqrt()= 2.6457513110645907
x= 8, z= 2.8284271247461903, math.Sqrt()= 2.8284271247461903
x= 9, z=                  3, math.Sqrt()=                  3
x=10, z= 3.1622776601683795, math.Sqrt()= 3.1622776601683795

次に、

次に値が変化しなくなった (もしくはごくわずかな変化しかしなくなった) 場合にループを停止させます。

とあるので値が変化しなくなった(もしくはごくわずかな変化しかしなくなった)をどう判定するのか考えます。 現在計算した平方根と直前に計算した平方根の差を取ります。 それらの数の大小関係が不明なため、差の絶対値を取ります。その値がごくわずかな値以下であればループを停止します。

d := |現在計算した平方根 - 直前に計算した平方根|
if d <= ごくわずかな値  // この条件が真の時にループを停止

ではごくわずかな値はどのように決めればよいのでしょうか。適当に0.01のように決めてもよいのでしょうか。 ここで計算機イプシロンという概念を導入します。 計算機イプシロンとはコンピュータで表すことのできる小数において「1より大きい最小の数」と1との差です。 計算機イプシロンは以下の式で定義できます。

var eps float64 = math.Nextafter(1, 2) - 1

Nextafter(x, y)はxからyに向かって次に表現可能な小数を返します。その値から1を引くと計算機イプシロンです。 この値を用いてループを停止してみます。

ちなみに絶対値を取った値が計算機イプシロンよりも小さな場合があると思うのですが、そこには足を踏み入れないでおきます。

ソース
https://play.golang.org/p/UkVez5bVr67

実行結果

x= 1, z=                  1, math.Sqrt()=                  1
x= 2, z=  1.414213562373095, math.Sqrt()= 1.4142135623730951
x= 3, z= 1.7320508075688774, math.Sqrt()= 1.7320508075688772
x= 4, z=                  2, math.Sqrt()=                  2
x= 5, z=   2.23606797749979, math.Sqrt()=   2.23606797749979
x= 6, z=  2.449489742783178, math.Sqrt()=  2.449489742783178
x= 7, z= 2.6457513110645907, math.Sqrt()= 2.6457513110645907
x= 8, z= 2.8284271247461903, math.Sqrt()= 2.8284271247461903
x= 9, z=                  3, math.Sqrt()=                  3
x=10, z= 3.1622776601683795, math.Sqrt()= 3.1622776601683795

次に、

それが 10 回よりも多いか少ないかを確認してください。

とあるのでループの実行回数を左端に出力してみました。10回よりも少なくなっているものが多いです。 ごくわずかな変化しかしなくなった時にループを抜けることにより無駄な計算をせずに済んでいます。

ソース
https://play.golang.org/p/JlrPN6YQ7BU

実行結果

count= 1, x= 1, z=                  1, math.Sqrt()=                  1
count= 6, x= 2, z=  1.414213562373095, math.Sqrt()= 1.4142135623730951
count= 6, x= 3, z= 1.7320508075688774, math.Sqrt()= 1.7320508075688772
count= 7, x= 4, z=                  2, math.Sqrt()=                  2
count= 7, x= 5, z=   2.23606797749979, math.Sqrt()=   2.23606797749979
count= 7, x= 6, z=  2.449489742783178, math.Sqrt()=  2.449489742783178
count= 7, x= 7, z= 2.6457513110645907, math.Sqrt()= 2.6457513110645907
count=10, x= 8, z= 2.8284271247461903, math.Sqrt()= 2.8284271247461903
count= 7, x= 9, z=                  3, math.Sqrt()=                  3
count=10, x=10, z= 3.1622776601683795, math.Sqrt()= 3.1622776601683795

次に、

x や x/2 のように他の初期推測の値を z に与えてみてください。

とあるので他の初期推測値で試してみます。 z=xの場合はループ回数に変化はなしですが、z=x/2の場合にループ回数が減っています。 初期推測値の選び方によって計算回数を減らせることがわかりました。

ソース(z=xの場合)
https://play.golang.org/p/hJq3SFbzO0j

実行結果

count= 1, x= 1, z=                  1, math.Sqrt()=                  1
count= 6, x= 2, z=  1.414213562373095, math.Sqrt()= 1.4142135623730951
count= 6, x= 3, z= 1.7320508075688774, math.Sqrt()= 1.7320508075688772
count= 7, x= 4, z=                  2, math.Sqrt()=                  2
count= 7, x= 5, z=   2.23606797749979, math.Sqrt()=   2.23606797749979
count= 7, x= 6, z=  2.449489742783178, math.Sqrt()=  2.449489742783178
count= 7, x= 7, z= 2.6457513110645907, math.Sqrt()= 2.6457513110645907
count=10, x= 8, z= 2.8284271247461903, math.Sqrt()= 2.8284271247461903
count= 7, x= 9, z=                  3, math.Sqrt()=                  3
count=10, x=10, z= 3.1622776601683795, math.Sqrt()= 3.1622776601683795

ソース(z=x/2の場合)
https://play.golang.org/p/NzNp91yJKw_x

実行結果

count= 7, x= 1, z=                  1, math.Sqrt()=                  1
count= 6, x= 2, z=  1.414213562373095, math.Sqrt()= 1.4142135623730951
count= 5, x= 3, z= 1.7320508075688774, math.Sqrt()= 1.7320508075688772
count= 1, x= 4, z=                  2, math.Sqrt()=                  2
count= 5, x= 5, z=   2.23606797749979, math.Sqrt()=   2.23606797749979
count= 6, x= 6, z=  2.449489742783178, math.Sqrt()=  2.449489742783178
count= 6, x= 7, z= 2.6457513110645907, math.Sqrt()= 2.6457513110645907
count=10, x= 8, z=   2.82842712474619, math.Sqrt()= 2.8284271247461903
count= 6, x= 9, z=                  3, math.Sqrt()=                  3
count=10, x=10, z=  3.162277660168379, math.Sqrt()= 3.1622776601683795

最後に、

あなたの関数の結果は標準ライブラリの math.Sqrt にどれくらい近づきましたか?

とあります。 z=x/2の出力で見比べてみるとすべての値がライブラリの出力と一致しているか、小数点以下がほとんど同じということが分かりました。