はじめに
Minitestのアサーションを調べていたところ、以下の二つのメソッドを見つけました。
assert_in_delta(exp, act, delta = 0.001, msg = nil)
assert_in_epsilon(a, b, epsilon = 0.001, msg = nil)
この記事ではこの二つのメソッドの違いについて説明していきます。
絶対誤差で比較する assert_in_delta
わかりやすいのは絶対誤差で比較する assert_in_delta
だと思います。
たとえばRubyで 0.1 + 0.2 を計算すると 0.3 ピッタリにはなりません。
irb(main):001:0> 0.1 + 0.2
=> 0.30000000000000004
浮動小数点の問題で小数同士の計算は微妙な誤差が出やすいです。
このような場合に assert_in_delta
を使うと「ある程度の誤差は許容するテスト」を書くことができます。
assert_in_delta 0.3, (0.1 + 0.2), 0.001
上のテストは「0.1 + 0.2 の結果が 0.3 になればOK、ただし前後 0.001 の誤差は許容する」の意味になります。
相対誤差で比較する assert_in_epsilon
一方、 assert_in_epsilon
は相対誤差で比較します・・・と言ってすぐに「ああ、なるほど」と思う人はどれくらいいるでしょうか?
恥ずかしながら僕は「相対誤差」という用語を初めて知りました。
相対誤差とは簡単に言うと測定値に占める誤差の割合です。
たとえば以下のようなケースを考えてみます。
- 期待値 10 に対して実際の値が 15 だった場合
- 期待値 1000 に対して実際の値が 1005 だった場合
絶対誤差で言うとどちらも |10 - 15| = 5 と |1000 - 1005| = 5 でどちらも同じです。
しかし、値の精度を考えた場合、数字が大きい後者の方が相対的に精度が高いことはなんとなくわかるでしょう。
相対誤差を使うとこの精度を定量的に把握することができます。
相対誤差は以下のような公式で求めます。
- 絶対誤差 / 真値 = 相対誤差
ただしMinitestでは 二つの値の小さい方を真値 として扱います。
それでは上で挙げた二つの例の相対誤差を計算してみましょう。
- |10 - 15| / 10 = 5 / 10 = 0.5
- |1000 - 1005| / 1000 = 5 / 1000 = 0.005
というわけでこの内容をMinitestでテストすると次のようになります。
assert_in_epsilon 10, 15, 0.5
assert_in_epsilon 1000, 1005, 0.005
誤差がこれよりも大きくなると指定された相対誤差を超えてしまうのでテストが失敗します。
assert_in_epsilon 10, 16, 0.5
# => Minitest::Assertion: Expected |10 - 16| (6) to be <= 5.0.
assert_in_epsilon 1000, 1006, 0.005
# => Minitest::Assertion: Expected |1000 - 1006| (6) to be <= 5.0.
まとめ
というわけでこの記事では 絶対誤差で比較する assert_in_delta
と相対誤差で比較する assert_in_epsilon
の説明をしてみました。
・・・とはいうものの、 assert_in_epsilon
の方はどういうときに使うのかな~というのがいまいちよくわかりません。
assert_in_delta
は考え方がシンプルなのでまだわかりやすいんですけどね。
あと、この記事の説明は仕入れたばかりの知識をまとめたものなので、もしかすると間違った内容を書いている部分もあるかもしれません。
もし間違いを見つけたらコメント等で優しく指摘してやってください。
よろしくお願いします。
謝辞
assert_in_epsilon
が相対誤差を扱うメソッドであることは @zakuro9715 さんがスタックオーバーフローで教えてくれました。
@zakuro9715 さん、どうもありがとうございました。
Minitestのassert_in_epsilonはどういうユースケースで使われるアサーションでしょうか?
2015.7.21 追記:絶対誤差と相対誤差の違いを具体的な数値で確認してみる
@scivola さんのコメントを参考にして絶対誤差と相対誤差の違いを確認するサンプルプログラムを作ってみました。
def thrice(x)
x * 3
end
def delta(a, b)
(a - b).abs
end
def epsilon(a, b)
(a - b).abs.to_f / [a, b].min
end
numbers =
[3, 0.3, 0.03, 0.003, 0.0003, 0.00003,
2, 0.2, 0.02, 0.002, 0.0002, 0.00002]
puts "Calc delta =============="
numbers.each do |n|
expected = (Rational(n.to_s) * 3).to_f
puts "#{n}, #{expected}, #{delta(expected, thrice(n))}"
end
puts "Calc epsilon ============="
numbers.each do |n|
expected = (Rational(n.to_s) * 3).to_f
puts "#{n}, #{expected}, #{epsilon(expected, thrice(n))}"
end
実行結果はこちらです。
Calc delta ==============
3, 9.0, 0.0
0.3, 0.9, 1.1102230246251565e-16
0.03, 0.09, 0.0
0.003, 0.009, 1.734723475976807e-18
0.0003, 0.0009, 0.0
3.0e-05, 9.0e-05, 0.0
2, 6.0, 0.0
0.2, 0.6, 1.1102230246251565e-16
0.02, 0.06, 0.0
0.002, 0.006, 0.0
0.0002, 0.0006, 1.0842021724855044e-19
2.0e-05, 6.0e-05, 6.776263578034403e-21
Calc epsilon =============
3, 9.0, 0.0
0.3, 0.9, 1.2335811384723962e-16
0.03, 0.09, 0.0
0.003, 0.009, 1.927470528863119e-16
0.0003, 0.0009, 0.0
3.0e-05, 9.0e-05, 0.0
2, 6.0, 0.0
0.2, 0.6, 1.8503717077085943e-16
0.02, 0.06, 0.0
0.002, 0.006, 0.0
0.0002, 0.0006, 1.8070036208091741e-16
2.0e-05, 6.0e-05, 1.1293772630057339e-16
ちょっとわかりにくいかもしれませんが、deltaの方は誤差が e-21 から e-16 の間でバラ付いています。
一方、epsilonの方はどれも e-16 または誤差なしで安定しています。
よって、 assert_in_delta
の場合は数値によって
assert_in_delta 0.9, thrice(0.3), 1e-15
assert_in_delta 0.009, thrice(0.003), 1e-17
と精度を変えなければいけませんが(もしくは一番荒い精度で統一する)、 assert_in_epsilon
を使うと
assert_in_epsilon 0.9, thrice(0.3), 1e-15
assert_in_epsilon 0.009, thrice(0.003), 1e-15
と、どれも同じ精度を指定することができます。