Mac*_*icz 6 ruby floating-point ruby-on-rails
First, take a specific float 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)
Run Code Online (Sandbox Code Playgroud)
So multiplying by BigDecimal casts f to BigDecimal. Therefore 1.to_d * f (or f * 1.to_d) can be seen as a (poor, but still) form of converting f to BigDecimal. And yet for these specific values we have:
f.to_d == 1.to_d * f # => false (?!)
Run Code Online (Sandbox Code Playgroud)
Isn't this a bug? I'd assume that while multiplying by 1.to_d Ruby should invoke f.to_d internally. But the results differ, i.e. for f = 64.4:
f.to_d # => #<BigDecimal:7f8202038280,'0.644E2',18(36)>
1.to_d * f # => #<BigDecimal:7f82019c1208,'0.6440000000 000001E2',27(45)>
Run Code Online (Sandbox Code Playgroud)
I cannot see why floating-point representation error should be an excuse here, yet it's obviously a cause, somehow. So why is this happening?
附言。我写了一段代码来解决这个问题:
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
Run Code Online (Sandbox Code Playgroud)
除非指定,否则它使用隐式精度,Float::DIG其15适用于当前实现:
Float::DIG
#=> 15
Run Code Online (Sandbox Code Playgroud)
所以64.4.to_d相当于:
BigDecimal(64.4, Float::DIG)
#=> #<BigDecimal:7fd7cc0aa838,'0.644E2',18(36)>
Run Code Online (Sandbox Code Playgroud)
BigDecimal#*另一方面通过以下方式转换给定的浮点参数:
if (RB_TYPE_P(r, T_FLOAT)) {
b = GetVpValueWithPrec(r, DBL_DIG+1, 1);
}
Run Code Online (Sandbox Code Playgroud)
DBL_DIG是 的 C 等价形式Float::DIG,所以它基本上是:
BigDecimal(64.4, Float::DIG + 1)
#=> #<BigDecimal:7fd7cc098408,'0.6440000000 000001E2',27(36)>
Run Code Online (Sandbox Code Playgroud)
也就是说,如果您显式提供精度,则可以获得预期结果:
f.to_d(16) == 1.to_d * f
#=> true
Run Code Online (Sandbox Code Playgroud)
或者:
f.to_d == 1.to_d.mult(f, 15)
#=> true
Run Code Online (Sandbox Code Playgroud)
当然,通过显式f转换to_d:
f.to_d == 1.to_d * f.to_d
#=> true
Run Code Online (Sandbox Code Playgroud)
这不是一个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)>
Run Code Online (Sandbox Code Playgroud)
IMO,64.4.to_d应该返回那个。
| 归档时间: |
|
| 查看次数: |
4088 次 |
| 最近记录: |