Project

General

Profile

Bug #13331 ยป multiplication_by_unit.rb

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

 
1
# checked for ruby-version 2.4.0
2

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

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

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

    
19
require 'bigdecimal'
20
require 'bigdecimal/util' # add method to_d
21

    
22
GLOBAL = binding # scoping trick
23

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

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

    
32
  puts code 
33
  puts "=> #{evaled} #{comment}"
34
  puts
35
end
36

    
37
puts
38

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

    
41
f = strange_values.sample
42

    
43
# visualization
44

    
45
ep "f", f
46
ep "f.class", Float
47
ep "(1.to_d * f).class", BigDecimal
48
ep "f.to_d.class == (1.to_d * f).class", true
49

    
50
ep "f == 1.to_d * f", true
51
ep "f.to_d == 1.to_d * f.to_d", true
52

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

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

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

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