在Ruby

时间:2016-11-07 19:20:08

标签: ruby-on-rails ruby floating-point

首先,采取特定的浮动f

f = [64.4, 73.60, 77.90, 87.40, 95.40].sample # take any one of these special Floats
f.to_d.class == (1.to_d * f).class # => true (BigDecimal)

所以乘以BigDecimalf转换为BigDecimal。因此,1.to_d * f(或f * 1.to_d)可被视为将f转换为BigDecimal的(不良但仍然)形式。然而,对于这些特定值,我们有:

f.to_d == 1.to_d * f # => false (?!)

这不是一个错误吗?我假设在乘以1.to_d时,Ruby应该在内部调用f.to_d。但结果不同,即f = 64.4

f.to_d # => #<BigDecimal:7f8202038280,'0.644E2',18(36)>
1.to_d * f # => #<BigDecimal:7f82019c1208,'0.6440000000 000001E2',27(45)>

我不明白为什么浮点表示错误应该成为一个借口,但它显然是一个原因,不知何故。那为什么会这样呢?

PS。我写了一段代码来解决这个问题:

https://github.com/Swarzkopf314/ruby_wtf/blob/master/multiplication_by_unit.rb

2 个答案:

答案 0 :(得分:2)

这不是错误。 f == f.to_d会返回false,因此如果f == 1.to_d * f为真,则f.to_d == 1.to_d * f必须为false,因为f != f.to_d==的{​​{1}}方法旨在将BigDecimal而非BigDecimalBigDecimal进行比较。有时平等会起作用,但对于某些float来说,f表示是准确的,而BigDecimal则不是。

修改:有关详细说明,请参阅Is Floating Point Math Broken

答案 1 :(得分:1)

  

为什么会发生这种情况?

TL; DR使用不同的精度。

答案很长:

64.4.to_d来电bigdecimal/util&#39; s Float#to_d

def to_d(precision=nil)
  BigDecimal(self, precision || Float::DIG)
end

除非另有说明,否则它使用隐式精度Float::DIG15用于当前实现:

Float::DIG
#=> 15

所以64.4.to_d相当于:

BigDecimal(64.4, Float::DIG)
#=> #<BigDecimal:7fd7cc0aa838,'0.644E2',18(36)>
另一方面,

BigDecimal#*转换给定的浮点参数via

if (RB_TYPE_P(r, T_FLOAT)) {
    b = GetVpValueWithPrec(r, DBL_DIG+1, 1);
}

DBL_DIGFloat::DIG的C等价物,所以它基本上是:

BigDecimal(64.4, Float::DIG + 1)
#=> #<BigDecimal:7fd7cc098408,'0.6440000000 000001E2',27(36)>

也就是说,如果你明确提供精确度,你可以得到预期的结果:

f.to_d(16) == 1.to_d * f
#=> true

或:

f.to_d == 1.to_d.mult(f, 15)
#=> true

当然是通过f明确转换to_d

f.to_d == 1.to_d * f.to_d
#=> true
  

这不是一个错误吗?

看起来像是一个,你应该提交错误报告。

请注意,0.644E20.6440000000000001E2都不是给定浮点数的精确表示。由于Eli Sadoff已经noted64.4的确切值为64.400000000000005684341886080801486968994140625,因此最准确的BigDecimal表示形式为:

BigDecimal('64.400000000000005684341886080801486968994140625')
#=> #<BigDecimal:7fd7cc04a0c8,'0.6440000000 0000005684 3418860808 0148696899 4140625E2',54(63)>

IMO,64.4.to_d应该只返回。