完全に一致しなくても、だいたい合ってりゃいいとき
浮動小数点の数は直接比較してはいけない。
0.3 == 0.1 + 0.2 # => false 0.1 + 0.2 # => 0.30000000000000004
ほぼ同じなんだから、なんとかならんのか。
そんなの誤差でしょ。
≒みたいなのが欲しい。
MiniTest::Assertions#assert_in_delta では、ふたつの数値の差が閾値以下なら良しとしてくれる。
require 'test/unit' class TestFloat < Test::Unit::TestCase def test_in_delta assert_in_delta(0.3, 0.2+0.1) end end # >> Loaded suite - # >> Started # >> . # >> Finished in 0.000531 seconds. # >> # >> 1 tests, 1 assertions, 0 failures, 0 errors, 0 skips # >> # >> Test run options: --seed 20395
テストが通った。
閾値の既定値は 0.001
これって微妙では?
require 'test/unit' class TestFloat < Test::Unit::TestCase def test_3km # 3km に対して 1cm なんて誤差だよね。 assert_in_delta(3_000.00, 3_000.01) end def test_3nm # 3nm と 30nm は違うよ。全然違うよ。 assert_in_delta(3e-9, 3e-8) end end ARGV << '-v' # >> Loaded suite - # >> Started # >> TestFloat#test_3km: 0.00 s: F # >> TestFloat#test_3nm: 0.00 s: . # >> # >> Finished in 0.000732 seconds.
3km の方は失敗して 3nm の方はパスした。嬉しくない。
こんなのが欲しい。
# -*- coding: utf-8; -*- class Float def ≒(o, precision = 1e-15) case when self == o # infinity がからむと面倒なので true when self == 0 # 0 を発見したやつエライ o.abs < precision when o == 0 abs < precision else (self-o).abs/self < precision end end end 0.3.≒(0.1+0.2) # => true 0.3.≒(0.1+0.2, 1e-15) # => true 0.3.≒(0.1+0.2, 1e-16) # => false 3_000.00.≒(3_000.01) # => false 3_000.00.≒(3_000.01, 1e-5) # => true 3_000.00.≒(3_000.01, 1e-6) # => false 3e-9.≒(3e-8) # => false
何桁の精度で一致するか。みたいな。*1
Ruby はメソッド名に = が使えるんで、Float#nearly= なんてどうだろう?
class Float alias_method :nearly=, :≒ end 0.3.nearly = 0.1+0.2 # => 0.30000000000000004 3e-9.nearly = 3e-8 # => 3.0e-08
はい、またハマりました。
= 付きメソッドは結果じゃなくて右辺値を返すんだよね。*2
じゃあ Float#nearly_equal? 長っ!
それとも Float#ne? これじゃ not equal だな JK
Float#approximate? それも長っ!