一些`BigDecimal`值与`Float`不匹配

时间:2016-03-08 22:49:55

标签: ruby rspec floating-point

某些BigDecimal值可以与Rspec3中Float的{​​{1}}进行比较,但某些值不能。

eq

为避免错误,我使用describe "compare BigDecimal with Float" do it { expect("83.79".to_d).to eq(83.79) } # => fail it { expect("83.75".to_d).to eq(83.75) } # => succeed end 等表达式。

为什么第一次测试失败而第二次测试成功?

2 个答案:

答案 0 :(得分:4)

您永远不应该尝试使用浮点值进行任何类型的严格相等测试。您始终必须使用Float处理不准确的内部代表问题,因此==!=并不十分有用。

考虑一下:

'83.79'.to_d - 83.79
# => #<BigDecimal:7ff33fcea560,'-0.1E-13',9(36)> 
'83.75'.to_d - 83.75
# => #<BigDecimal:7ff33fcee688,'0.0',9(27)> 

请注意83.79的差异并非完全为零。

如果需要比较浮点值,则总是需要在比较中使用delta;你总想说:

  

这些值是否相互之间有少量差异?

而不是

  

这些值是否相等?

在Rspec术语中:

expect('83.75'.to_d).to be_within(1e-12).of(83.75)
expect('83.79'.to_d).to be_within(1e-12).of(83.79)

并选择delta(在这种情况下为1e-12)以符合您的要求。

答案 1 :(得分:2)

&#34; 83.79&#34; .to_d正好代表内部表示中的分数8379/100,因为它使用基数10(或其幂),而#34; 83.79&#34;。 to_f不是因为内部表示使用基数2,所以它们不相等。

对于83.75来说,这是不一样的,因为is在两个基数2和10中都是完全相同的(这是83 + 1/2 + 1/4)。

如果在同一个表达式中混合使用大小数和浮点数,则浮点值将转换为最接近的大小数点...因此,您实际上是执行此操作:83.79.to_d或以不同方式"83.79".to_f.to_d <登记/> 由于"83.79".to_f不准确,并且因为大小数比浮点数更准确,所以没有理由它与"83.79".to_d匹配。

但是,如果你以另一种方式强制转换,我希望平等成立:

expect("83.79".to_d.to_f).to eq(83.79)

这是因为我们可以合理地期望(最不惊讶)转换to_f将回答最接近精确分数的浮点,无论是精确的大小数还是字符串表示。