Help us understand the problem. What is going on with this article?

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

More than 5 years have passed since last update.

はじめに

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

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

MinitestのAPIドキュメント

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

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

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

  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でテストすると次のようになります。

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

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

jnchito
SIer、社内SEを経て、ソニックガーデンに合流したプログラマ。 「プロを目指す人のためのRuby入門」の著者。 http://gihyo.jp/book/2017/978-4-7741-9397-7 および「Everyday Rails - RSpecによるRailsテスト入門」の翻訳者。 https://leanpub.com/everydayrailsrspec-jp
https://blog.jnito.com/
sonicgarden
「お客様に無駄遣いをさせない受託開発」と「習慣を変えるソフトウェアのサービス」に取り組んでいるソフトウェア企業
http://www.sonicgarden.jp
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした