首先,取一个特定的浮点数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 (?!)
这不是一个bug吗?我认为当乘以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)>
我不明白为什么浮点表示错误应该是一个借口,但它显然是一个原因,在某种程度上。那么为什么会发生这种情况呢?
p。我写了一段代码来解决这个问题:
https://github.com/Swarzkopf314/ruby_wtf/blob/master/multiplication_by_unit.rb为什么会这样呢?
TL;DR使用不同的精度。
长答:
64.4.to_d
呼叫bigdecimal/util
的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#*
通过:
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
,当然,通过to_d
显式转换f
:
f.to_d == 1.to_d * f.to_d
#=> true
这不是一个bug吗?
看起来像,你应该提交一个bug报告。
注意0.644E2
和0.6440000000000001E2
都不是给定浮点数的精确表示。正如Eli Sadoff已经指出的那样,64.4
的确切值是64.400000000000005684341886080801486968994140625
,因此最精确的BigDecimal
表示将是:
BigDecimal('64.400000000000005684341886080801486968994140625')
#=> #<BigDecimal:7fd7cc04a0c8,'0.6440000000 0000005684 3418860808 0148696899 4140625E2',54(63)>
IMO, 64.4.to_d
应该就这样返回
这不是一个bug。f == f.to_d
返回false
,所以如果f == 1.to_d * f
为真,那么f.to_d == 1.to_d * f
一定是false
,因为f != f.to_d
。BigDecimal
的==
方法是用来比较BigDecimal
而不是BigDecimal
和float
。有时等式会起作用,但对于某些f
, BigDecimal
的表示是精确的,而float
则不是。
编辑:更多的解释,请参阅浮点数学损坏