完全に一致しなくても、だいたい合ってりゃいいとき

浮動小数点の数は直接比較してはいけない。

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? それも長っ!

*1:MiniTest::Assertions に、そういうのが無いところと見ると、なんかマズいんだろうな ......

*2:右辺値を返さないと foo.x = foo.y = 0 みたいなのができない。