在Ruby中浮点数到十进制的转换不一致



首先,取一个特定的浮点数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 (?!)

这不是一个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/utilFloat#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#*通过:

转换给定的float参数:
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

,当然,通过to_d显式转换f:

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

这不是一个bug吗?

看起来像,你应该提交一个bug报告。

注意0.644E20.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_dBigDecimal==方法是用来比较BigDecimal而不是BigDecimalfloat。有时等式会起作用,但对于某些f, BigDecimal的表示是精确的,而float则不是。

编辑:更多的解释,请参阅浮点数学损坏

最新更新