Project

General

Profile

Bug #13331 ยป multiplication_by_unit.rb

msatkiewicz (Maciej Satkiewicz), 03/19/2017 06:51 PM

 
# checked for ruby-version 2.4.0

####
# This script shows that ruby conversion of Float into Decimal is somehow inconsistent;
# One would reasonably expect the equality 1.to_d * f == f.t_d to hold for every Float value f,
# i.e. multiplying by DigDecimal unit can be viewed as alternative way of casting Float into Decimal;
# or simply one could expect that BigDecimal#* should always invoke BigDecimal#to_d on it's argument.
# Yet for some Floats (e.g. 64.4) the results differ, as showed in this script.
# This can cause a bug in some application contexts.

# My original question on Stackoverflow:
# http://stackoverflow.com/questions/40472933/inconsistent-conversion-of-float-into-decimal-in-ruby/40473007

# As Stefan points out in his exhaustive answer, it is due to different precisions being used:
# 64.4.to_d is equivalent to BigDecimal(64.4, Float::DIG)
# while
# 1.to_d * 64.4 translates to BigDecimal(64.4, Float::DIG + 1)

require 'bigdecimal'
require 'bigdecimal/util' # add method to_d

GLOBAL = binding # scoping trick

# eval the code and print the code and it's result
def ep(code, check = nil, comment = nil)
evaled = GLOBAL.eval(code)

unless check.nil?
raise "oops for #{code}" if evaled != check
end

puts code
puts "=> #{evaled} #{comment}"
puts
end

puts

strange_values = [64.4, 73.60, 77.90, 87.40, 95.40]

f = strange_values.sample

# visualization

ep "f", f
ep "f.class", Float
ep "(1.to_d * f).class", BigDecimal
ep "f.to_d.class == (1.to_d * f).class", true

ep "f == 1.to_d * f", true
ep "f.to_d == 1.to_d * f.to_d", true

ep "f.to_d == 1.to_d * f", false, "<- WTF??"
ep "f == f.to_d", false, "<- WTF??"

# sanity check
ep "f * 1.to_d == 1.to_d * f", true

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

# precision check:
ep "f.to_d(16) == 1.to_d * f", true
ep "f.to_d == 1.to_d.mult(f, 15)", true

    (1-1/1)