Minitestで絶対誤差と相対誤差を利用したテストを実行する


## はじめに

Minitestのアサーションを調べていたところ、以下の二つのメソッドを見つけました。

- `assert_in_delta(exp, act, delta = 0.001, msg = nil)`
- `assert_in_epsilon(a, b, epsilon = 0.001, msg = nil)`

[MinitestのAPIドキュメント](http://docs.seattlerb.org/minitest/Minitest/Assertions.html)

この記事ではこの二つのメソッドの違いについて説明していきます。

## 絶対誤差で比較する assert_in_delta

わかりやすいのは絶対誤差で比較する `assert_in_delta` だと思います。
たとえばRubyで 0.1 + 0.2 を計算すると 0.3 ピッタリにはなりません。

````ruby
irb(main):001:0> 0.1 + 0.2 
=> 0.30000000000000004
````

浮動小数点の問題で小数同士の計算は微妙な誤差が出やすいです。
このような場合に `assert_in_delta` を使うと「ある程度の誤差は許容するテスト」を書くことができます。

```ruby
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` は相対誤差で比較します・・・と言ってすぐに「ああ、なるほど」と思う人はどれくらいいるでしょうか？
恥ずかしながら僕は「相対誤差」という用語を初めて知りました。

相対誤差とは簡単に言うと測定値に占める誤差の割合です。
たとえば以下のようなケースを考えてみます。

1. 期待値 10 に対して実際の値が 15 だった場合
2. 期待値 1000 に対して実際の値が 1005 だった場合

絶対誤差で言うとどちらも |10 - 15| = 5 と |1000 - 1005| = 5 でどちらも同じです。
しかし、値の精度を考えた場合、数字が大きい後者の方が相対的に精度が高いことはなんとなくわかるでしょう。
相対誤差を使うとこの精度を定量的に把握することができます。

相対誤差は以下のような公式で求めます。

- **絶対誤差 / 真値 = 相対誤差**

ただしMinitestでは **二つの値の小さい方を真値** として扱います。
それでは上で挙げた二つの例の相対誤差を計算してみましょう。

1. |10 - 15| / 10 = 5 / 10 = 0.5
2. |1000 - 1005| / 1000 = 5 / 1000 = 0.005

というわけでこの内容をMinitestでテストすると次のようになります。

```ruby
assert_in_epsilon 10, 15, 0.5
assert_in_epsilon 1000, 1005, 0.005
```

誤差がこれよりも大きくなると指定された相対誤差を超えてしまうのでテストが失敗します。

```ruby
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はどういうユースケースで使われるアサーションでしょうか？](http://ja.stackoverflow.com/questions/12475/minitest%e3%81%aeassert-in-epsilon%e3%81%af%e3%81%a9%e3%81%86%e3%81%84%e3%81%86%e3%83%a6%e3%83%bc%e3%82%b9%e3%82%b1%e3%83%bc%e3%82%b9%e3%81%a7%e4%bd%bf%e3%82%8f%e3%82%8c%e3%82%8b%e3%82%a2%e3%82%b5%e3%83%bc%e3%82%b7%e3%83%a7%e3%83%b3%e3%81%a7%e3%81%97%e3%82%87%e3%81%86%e3%81%8b)

## 2015.7.21 追記：絶対誤差と相対誤差の違いを具体的な数値で確認してみる
@scivola さんのコメントを参考にして絶対誤差と相対誤差の違いを確認するサンプルプログラムを作ってみました。

```rb
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
```

実行結果はこちらです。

```text
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` の場合は数値によって

```rb
assert_in_delta 0.9, thrice(0.3), 1e-15
assert_in_delta 0.009, thrice(0.003), 1e-17
```

と精度を変えなければいけませんが（もしくは一番荒い精度で統一する）、 `assert_in_epsilon` を使うと

```rb 
assert_in_epsilon 0.9, thrice(0.3), 1e-15
assert_in_epsilon 0.009, thrice(0.003), 1e-15
```

と、どれも同じ精度を指定することができます。