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

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

A Tour of Goはプログラミング言語Goの入門サイトです。 このシリーズではA Tour of Goの練習問題を解説します。

今回は以下の問題を扱います。

問題
Exercise: Loops and Functions
解答
https://go.dev/play/p/1j9H5rPSXDM


始めに、

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

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

ソース
https://go.dev/play/p/ndKC5UQmMQ6

実行結果

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://go.dev/play/p/fBXXOLb01Eb

実行結果

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

次に、

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

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

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

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

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

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

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

ソース
https://go.dev/play/p/U36tvWlM0IB

実行結果

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

次に、

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

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

ソース
https://go.dev/play/p/k93Up0pmFu3

実行結果

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

次に、

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

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

ソース(z=xの場合)
https://go.dev/play/p/BAJELYrKhRz

実行結果

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

ソース(z=x/2の場合)
https://go.dev/play/p/1j9H5rPSXDM

実行結果

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

最後に、

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

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