0

以下のクラスをユニットテストしようとすると、次の丸め誤差が発生します。

class TypeTotal
    attr_reader  :cr_amount,  :dr_amount,
                 :cr_count,  :dr_count

    def initialize()
        @cr_amount=Float(0);    @dr_amount=Float(0)
        @cr_count=0;            @dr_count= 0
    end

    def increment(is_a_credit, amount, count=1)
        case is_a_credit        
         when true
            @cr_amount = Float(amount)+ Float(@cr_amount)
            @cr_count += count
         when false
            @dr_amount = Float(amount)+ Float(@dr_amount)
            @dr_count += count
        end
    end
end

単体テスト:

require_relative 'total_type'
require 'test/unit'

class TestTotalType < Test::Unit::TestCase 
  #rounding error
  def test_increment_count()
    t = TypeTotal.new()
       t.increment(false, 22.22, 2)       
       t.increment(false, 7.31, 3) 
     assert_equal(t.dr_amount, 29.53)    
  end
end

出力:

  1) Failure:
test_increment_count(TestTotalType) [total_type_test.rb:10]:
<29.529999999999998> expected but was
<29.53>.

1 tests, 1 assertions, 1 failures, 0 errors, 0 skips

丸め誤差では効果がないはずなので、ドルの値についてはPick Axeの本で推奨されているため、Floatsを使用しています。

私はruby 1.9.2p290 (2011-07-09) [i386-mingw32]Windows764ビットHomeとWindowsXP32ビットProで実行しています。

私はもう試した

  • 変数をfloatにキャストする
  • +=を削除して増分をスペルアウト

動作はランダムに表示されます。

  • 12.22+7.31は動作します
  • 11.11+7.31が機能しない
  • 11.111+7.31は動作します

何がうまくいかないのですか?

4

3 に答える 3

5

それがアドバイスでしたか?浮動小数点はバイナリ浮動小数点演算を使用しているため、丸め誤差が発生しやすいため、浮動小数点を使用しないことをお勧めします。Floatのドキュメントから:

浮動小数点オブジェクトは、ネイティブアーキテクチャの倍精度浮動小数点表現を使用して、不正確な実数を表します。

あなたが言及している正確なアドバイスを引用することができれば、それは助けになるでしょう。

BigDecimal代わりに使用するか、「セント」または「数百セント」などの暗黙の単位を持つ整数を使用することをお勧めします。

于 2011-10-13T11:20:58.600 に答える
2

フロートの問題についてはすでに説明しました。

フロートでテストする場合は、を使用しないでassert_equalくださいassert_in_delta

例:

require 'test/unit'

class TestTotalType < Test::Unit::TestCase 
  TOLERANCE = 1E-10 #or another (small) value

  #rounding error
  def test_increment_count()
    t = TypeTotal.new()
       t.increment(false, 22.22, 2)       
       t.increment(false, 7.31, 3) 
     #~ assert_equal(t.dr_amount, 29.53)  #may detect float problems
     assert_in_delta(t.dr_amount, 29.53, TOLERANCE)    
  end
end        
于 2011-10-13T18:40:15.177 に答える
2

解決策は、BigDecimalを使用することでした。

require 'bigdecimal'

class TypeTotal
    attr_reader  :cr_amount,  :dr_amount,
                 :cr_count,  :dr_count

    def initialize()
        @cr_amount=BigDecimal.new("0");  @cr_count=0,
        @dr_amount=BigDecimal.new("0");  @dr_count=0

    end

    def increment(is_a_credit, amount, count=1)
        bd_amount = BigDecimal.new(amount)
        case is_a_credit        
         when true
            @cr_amount= bd_amount.add(@cr_amount, 14)
            @cr_count += count
         when false
            @dr_amount= bd_amount.add(@dr_amount, 14)
            @dr_count = count
        end
    end

Pick Axe(p53)の本では、例として通貨にフロートを使用しましたが、値を表示するときに0.5セントを追加するか、BigDecimalを使用する必要があることを説明する脚注がありました。

よろしくお願いします!

于 2011-10-13T19:20:09.390 に答える