How to keep precision on float point arithmetic?

1
2
3
4
5
190000 * ( 783 . 0 / 10000 )
# => 14876.999999999998
( 190000 * 783 . 0 ) / 10000
# => 14877.0

How to make a 2 point truncation instead of rounding?

1
2
3
4
5
195555 * 0 . 07 83
# => 15311.956499999998
( 195555 * 0 . 07 83 ) . round ( 2 )
# => 15311.96

Plain Solution
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# Public: A calculator aims handling Float operation precision and
# saving the result with truncated 2 point Float.
#
# Examples
#
# 190000 * 0.0783
# # => 14876.999999999998
# 190000 * 783 / 10000
# # => 14877
#
# cal = RateCalculator.new(190000, 0.0783)
# cal.run
# # => 14877.0
#
#
# 195555 * 0.0783
# # => 15311.956499999998
#
# cal = RateCalculator.new(195555, 0.0783)
# # => 15311.95
#
# Returns a Float
class RateCalculator
attr_reader :base , :rate
# Internal: Handles 6 point rate.
MAGNIFIER = 1000000
# Public: Initialization
#
# base - Integer
# rate - Numeric
def initialize ( base , rate )
raise "#initialize: <base> needs to be Integer" unless base . is_a? Integer
@base = base
@rate = rate
end
def run
truncate_2_point MAGNIFIER * rate * base / MAGNIFIER
end
private
def truncate_2_point ( float )
( float * 100 ) . to_i / 100 . 0
end
end

It works, but with so many worries about the unknown conditions.

BigDecimal
First, what the hell happens on the precision of float point arithmetic?

1
2
0 . 1 + 0 . 2
# => 0.30000000000000004

According to What Every Programmer Should Know About Floating-Point Arithmetic , the answer is the binary fraction issue.

Specifically, binary can only represent those numbers as a finite fraction where the denominator is a power of 2. Unfortunately, this does not include most of the numbers that can be represented as finite fraction in base 10, like 0.1.

To get through the precision problem, Ruby provides the Arbitrary-Precision Decimal shipped by `BigDecimal`

. And so sweet, `BigDecimal`

supports several rounding modes, including `:truncate`

.

Here is the final solution.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
require 'bigdecimal'
# Public: A calculator aims handling arithmatic precision and
# saving the result with 2 points truncated decimal.
#
# Examples
#
# 190000 * 0.0783
# # => 14876.999999999998
# 190000 * 783 / 10000
# # => 14877
#
# cal = RateCalculator.new(190000, 0.0783).run
# # => 14877.0
#
#
# 195555 * 0.0783
# # => 15311.956499999998
#
# cal = RateCalculator.new(195555, 0.0783).run
# # => 15311.95
#
# Returns a BigDecimal
class RateCalculator
attr_reader :base , :rate
def initialize ( base , rate )
@base = BigDecimal ( base . to_s )
@rate = BigDecimal ( rate . to_s )
end
def run
BigDecimal . save_rounding_mode do
BigDecimal . mode ( BigDecimal :: ROUND_MODE , :truncate )
( base * rate ) . round ( 2 )
end
end
end

Reference