首先,采取特定的浮动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)
所以乘以BigDecimal
将f
转换为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
答案 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
而非BigDecimal
与BigDecimal
进行比较。有时平等会起作用,但对于某些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::DIG
,15
用于当前实现:
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_DIG
是Float::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.644E2
和0.6440000000000001E2
都不是给定浮点数的精确表示。由于Eli Sadoff已经noted,64.4
的确切值为64.400000000000005684341886080801486968994140625
,因此最准确的BigDecimal
表示形式为:
BigDecimal('64.400000000000005684341886080801486968994140625')
#=> #<BigDecimal:7fd7cc04a0c8,'0.6440000000 0000005684 3418860808 0148696899 4140625E2',54(63)>
IMO,64.4.to_d
应该只返回。