# Saving Money with Ruby

- Accurate decimal numbers by definition, not by chance -

Wolfgang Teuber, Leipzig on Rails, updated Nov 14th, 2015

# Saving Money with Ruby

When dealing with money, the question is...

``````savings     = 50000  # amount invested
interest    = 0.02   # annual interest rate: 2%
withholding = 0.25   # flat rate withholding tax: 25%
soli        = 0.055  # solidarity supplement: 5.5%
church      = 0.04   # church tax: 4%
exempt      = 801    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)``````

Would you ship this code?

# Saving Money with Ruby

...probably not

``````savings     = 50000  # amount invested
interest    = 0.02   # annual interest rate: 2%
withholding = 0.25   # flat rate withholding tax: 25%
soli        = 0.055  # solidarity supplement: 5.5%
church      = 0.04   # church tax: 4%
exempt      = 801    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)``````

`Integer`? Wait a minute...

# Saving Money with Ruby

Dividing an `Integer` by an `Integer` results in an `Integer`.

``````a = 10
a = a / 3
a = a * 3
a == 10    # false, a == 9
``````

## Solution:

Don't use `Integer`*,
try duck typing (.0) or typecasting (.to_f) instead.

*for financial calculations

# Saving Money with Ruby

...here we go

``````savings     = 50000.0  # amount invested
interest    = 0.02     # annual interest rate: 2%
withholding = 0.25     # flat rate withholding tax: 25%
soli        = 0.055    # solidarity supplement: 5.5%
church      = 0.04     # church tax: 4%
exempt      = 801.0    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)``````

Would you ship this code?

# Saving Money with Ruby

...probably not

``````savings     = 50000.0  # amount invested
interest    = 0.02     # annual interest rate: 2%
withholding = 0.25     # flat rate withholding tax: 25%
soli        = 0.055    # solidarity supplement: 5.5%
church      = 0.04     # church tax: 4%
exempt      = 801.0    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)``````

`Float`? Looks good at first sight...

# Saving Money with Ruby

...right, that's the issue. `Float` is inaccurate by definition.

``````Float::DIG           # number of significant digits, default: 15
0.25.to_r            # (1/4)
123456789.987654321  # 123456789.98765433
0.02.to_r            # (5764607523034235/288230376151711744)
4.55 % 0.05          # 0.04999999999999957``````

## Solution:

Don't use `Float`*,
try `BigDecimal` instead.

*for financial calculations

# Saving Money with Ruby

...here we go again

``````require 'bigdecimal'
require 'bigdecimal/util'

savings     = 50000.0.to_d  # amount invested
interest    = 0.02.to_d     # annual interest rate: 2%
withholding = 0.25.to_d     # flat rate withholding tax: 25%
soli        = 0.055.to_d    # solidarity supplement: 5.5%
church      = 0.04.to_d     # church tax: 4%
exempt      = 801.0.to_d    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)
``````

Would you ship this code?

# Saving Money with Ruby

...maybe, maybe not

``````require 'bigdecimal'
require 'bigdecimal/util'

savings     = 50000.0.to_d  # amount invested
interest    = 0.02.to_d     # annual interest rate: 2%
withholding = 0.25.to_d     # flat rate withholding tax: 25%
soli        = 0.055.to_d    # solidarity supplement: 5.5%
church      = 0.04.to_d     # church tax: 4%
exempt      = 801.0.to_d    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)
``````

Still `Float`? Hm, will it work this time?

# Saving Money with Ruby

...let's see

``````# util.rb
...
class Float < Numeric
# Convert +flt+ to a BigDecimal and return it.
#
#     require 'bigdecimal'
#     require 'bigdecimal/util'
#
#     0.5.to_d
#     # => #<BigDecimal:1dc69e0,'0.5E0',9(18)>
#
def to_d(precision=nil)
BigDecimal(self, precision || Float::DIG)
end
end
...``````
ext/bigdecimal/lib/bigdecimal/util.rb#L26-L41

`.to_d` even uses maximum precision by default...

# Saving Money with Ruby

...I don't want to be picky, but check this out.

``````interest = 0.07
interest.to_r          # (1261007895663739/18014398509481984)
interest.to_d.to_r     # (7000000000000001/100000000000000000)
``````

What now? Use `Rational`? Since it's sooo accurate?
Isn't there a `Money` gem? Maybe MRI just can't do it...

# Saving Money with Ruby

...well, there are tools

• are great for new or small projects
• require massive refactoring in existing projects
• need to work with SQL-DB, noSQL-DB, APIs, JS, gems ?

``````interest = 7.to_r / 100
interest.to_s               # "7/100"
'%.2f' % interest           # "0.07"

require 'money'
Money.new(7, 'EUR').to_s    # "0,07"
Money.new(100, 'USD').to_s  # "1.00"``````

...integrating either of them in legacy code will be a huge effort.
Is there a better option?

# Saving Money with Ruby

...yes there is `String.to_d`

``````require 'bigdecimal'
require 'bigdecimal/util'

savings     = '50000'.to_d  # amount invested
interest    = '0.02'.to_d   # annual interest rate: 2%
withholding = '0.25'.to_d   # flat rate withholding tax: 25%
soli        = '0.055'.to_d  # solidarity supplement: 5.5%
church      = '0.04'.to_d   # church tax: 4%
exempt      = '801'.to_d    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)
``````

`String`? How disappointing...

# Saving Money with Ruby

``````# util.rb
class String
# Convert +string+ to a BigDecimal and return it.
#
#     require 'bigdecimal'
#     require 'bigdecimal/util'
#
#     "0.5".to_d
#     # => #<BigDecimal:1dc69e0,'0.5E0',9(18)>
#
def to_d
BigDecimal(self)
end
end``````

ext/bigdecimal/lib/bigdecimal/util.rb#L47-L62

# Saving Money with Ruby

...it's not needed, precision is arbitrary.

``````/* bigdecimal.c */
BigDecimal_new(int argc, VALUE *argv)
{
...
case T_STRING:
/* fall through */
default:
break;
}
StringValueCStr(iniValue);
return VpAlloc(mf, RSTRING_PTR(iniValue));
}
``````

ext/bigdecimal/bigdecimal.c#L2509-L2555

CRuby uses `char*` for internal representation.

# Saving Money with Ruby

...it's accurate and it inherits from `Numeric`.

``````a = '0.027'.to_d
b = '0.011'.to_d
a.to_r                     # (27/1000)
b.to_r                     # (11/1000)
a * b                      # 0.000297
a + b                      # 0.038
a - 2 * b                  # 0.005
a.round(2)                 # 0.03
'4.55'.to_d % '0.05'.to_d  # 0.0
``````

I'll check out the `BigDecimal` doc to see what else I can do with it.
Are we done now?

# Saving Money with Ruby

...we havn't even started. Will you fix the code before shipping?

``````savings     = 50000  # amount invested
interest    = 0.02   # annual interest rate: 2%
withholding = 0.25   # flat rate withholding tax: 25%
soli        = 0.055  # solidarity supplement: 5.5%
church      = 0.04   # church tax: 4%
exempt      = 801    # tax exemption: 801

PerfectClass.perfect_method(savings, interest, withholding, soli, church, exempt)``````

Hm, it worked alright so far. I have my doubts it needs fixing.

# Saving Money with Ruby

...let me give you a warning. Really, I mean it!

## A Ruby warning.

Ok, what would that look like?

# Saving Money with Ruby

...like this.

``````/* bigdecimal.c */
BigDecimal_new(int argc, VALUE *argv)
{
...
case T_FLOAT:
rb_warn("initializing BigDecimal with an instance of Float.");
if (mf > DBL_DIG+1) {
rb_raise(rb_eArgError, "precision too large.");
}
/* fall through */
...
StringValueCStr(iniValue);
return VpAlloc(mf, RSTRING_PTR(iniValue));
}``````

01-bigdecimal_float_warning.patch#L9

That's only BigDecimal.new though, what about .to_d?

# Saving Money with Ruby

Well spotted! Here we go...

``````# util.rb
class Float < Numeric
# Convert +flt+ to a BigDecimal and return it.
#     0.5.to_d
#     # => #
#
def to_d(precision=nil)
location = caller[0].gsub(/:in `.*'\$/, '')
warn("#{location}: warning: calling .to_d on an instance of Float.")
old_verbose, \$VERBOSE = \$VERBOSE, nil
BigDecimal(self, precision || Float::DIG)
ensure
\$VERBOSE = old_verbose
end
end``````

01-bigdecimal_float_warning.patch#L22

Could you show me?

# Saving Money with Ruby

...sure.

``````\$> curl -sSL https://get.rvm.io | bash
\$> rvm install 2.2.3 --patch float_warnings -n float_warnings
\$> rvm use ruby-2.2.3-float_warnings
\$> irb
2.2.3-float_warnings :001 >
``````

DEMO

# Saving Money with Ruby

DEMO output

``````\$> curl -sSL https://get.rvm.io | bash
\$> rvm install 2.2.3 --patch float_warnings -n float_warnings
\$> rvm use ruby-2.2.3-float_warnings
\$> irb
2.2.3-float_warnings :001 > require 'bigdecimal'
=> true
2.2.3-float_warnings :002 > BigDecimal.new(1.0, 2)
(irb):2: warning: initializing BigDecimal with an instance of Float.
=> #<BigDecimal:1c8e370,'0.1E1',9(27)>
2.2.3-float_warnings :003 > require 'bigdecimal/util'
=> true
2.2.3-float_warnings :004 > 1.2.to_d
(irb):4: warning: calling .to_d on an instance of Float.
=> #<BigDecimal:1c58ab8,'0.12E1',18(36)>
``````

# Saving Money with Ruby

## Q & A

Knowing all that, you could even be

# Making Money with Ruby

I could start with throwing out some `Float`s ;-)

# Saving Money with Ruby

## Thank You.

https://slides.com/wolfgangteuber/saving-money-with-ruby https://github.com/knugie/rvm-patchsets