Why You Shouldn’t Use Float for Currency (floating point issues – explained for Ruby and RoR)

This article is a response to https://vladzloteanu.wordpress.com/2010/01/06/ruby-on-rails-interview-questions-update/

It is a VERY BAD IDEEA to use floating point arithmetics to deal with currency. In most of the programming languages. Basically, because you’ll end up loosing money :). And this (on the great majority of cases) is not desirable 🙂 .

I’ll show you some magic (that you may try at home):
~$ ruby --version
ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux]
~$ irb
irb(main):003:0> (10.12*100).to_i
=>; 1011

So, what just happened?

O.K. Now a little computer science lesson.

Floating Point (vs Fixed Point)

From wikipedia:

Numbers are in general represented approximately to a fixed number of significant digits and scaled using anexponent. The base for the scaling is normally 2, 10 or 16. The typical number that can be represented exactly is of the form:

significant digits × baseexponent

The term floating point refers to the fact that the radix point (decimal point, or, more commonly in computers, binary point) can “float”; that is, it can be placed anywhere relative to the significant digits of the number. This position is indicated separately in the internal representation, and floating-point representation can thus be thought of as a computer realization of scientific notation.

Short version:

– number is converted to scientific notation, and then coded in: sign bit, exponent, significand

– the precision is not fixed

– much larger range of representable numbers (much more than a fixed point representation), at the cost of precision

Decimal to Floating Point conversion

In this representation, a large group of rational numbers can not be represented in binary with a fixed number of digits.

For example, if you would like to convert 0.7 in binary format (the example is from http://sandbox.mc.edu/~bennet/cs110/flt/dtof.html) you’ll end up having an ‘endless’ number (in fact, a repeating fraction, in binary).

0.7 Ă— 2 = 1.4 1 Generate 1 and continue with the rest.
0.4 Ă— 2 = 0.8 0 Generate 0 and continue.
0.8 Ă— 2 = 1.6 1 Generate 1 and continue with the rest.
0.6 Ă— 2 = 1.2 1 Generate 1 and continue with the rest.
0.2 Ă— 2 = 0.4 0 Generate 0 and continue.
0.4 Ă— 2 = 0.8 0 Generate 0 and continue.
0.8 Ă— 2 = 1.6 1 Generate 1 and continue with the rest.
0.6 Ă— 2 = 1.2 1 Generate 1 and continue with the rest.

The reason why the process seems to continue endlessly is that it does. The number 7/10, which makes a perfectly reasonable decimal fraction, is a repeating fraction in binary, just as the faction 1/3 is a repeating fraction in decimal. (It repeats in binary as well.) We cannot represent this exactly as a floating point number. The closest we can come in four bits is .1011. Since we already have a leading 1, the best eight-bit number we can make is 1.1011.

A good simulator can be fount at http://babbage.cs.qc.edu/IEEE-754/Decimal.html

And now back to our issue..

Ruby floating point values are stored in binary format.
10.12 (decimal) can not be represented exactly in binary:

>> sprintf(“%0.50f”, 10.12)
=> “10.11999999999999921840299066388979554176330566406250”

The default conversion of float to string (e.g., for output) rounds
to 6 (or maybe 7) places and truncates trailing zeros:

>> sprintf(“%0.6f”, a).sub(/0+\Z/,”)
=> “10.12”

Float#to_i truncates:

>> sprintf(“%0.50f”, 10.12 * 100 )
=> “1011.99999999999988631316227838397026062011718750000000”
>> (10.12 * 100).to_i
=> 1011

A very usefull add/sub/mul/div can be found at http://www.ecs.umass.edu/ece/koren/arith/simulator/FPMul/ . A more detailed explanation from there:

A    + 1.0100001111010111000010100011110101110000101000111101 *23 = 10.12
B    + 1.1001000000000000000000000000000000000000000000000000 *26 = 100

A*B + 1.1111100111111111111111111111111111111111111111111111|011 *29

Postnormalization Step

A*B + 1.1111100111111111111111111111111111111111111111111111|01 *29

Round to Zero

A*B + 1.1111100111111111111111111111111111111111111111111111 *29 = 1011.9999999999999

Further references

Wikipedia provides even more examples of accuracy problems:http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems.

One Response

  1. Assignment(Can anyone please help me out to write the code)

    You will find the code for a program that calculates loan information here testerCodeTest.zip. Given a principal, interest rate, and term, the program will determine if these inputs are valid and inform the user of the amount of interest due at the end of the loan period. (Assume no payments and no compounding interest.) This is a simple application that is executed on the command line by changing the lib/directory and using the following command:

    ruby interest_calculator.rb {PRINCIPAL} {INTEREST RATE} {TERM}
    The requirements are listed below. Your assignment is to develop a set of automated acceptance tests for this software. For this exercise we will define acceptance tests as tests that investigate a system to determine whether it correctly implements a given responsibility. You may or may not find defects.

    Business Rules:

    1.The Amount will be equal to Principal x (Interest Rate / 100) x Term in years
    2.Term must be entered as an integer in months
    3.Principal must be entered as whole dollars, no cents
    4.The system will parse out commas and %
    5.The Interest Rate will be considered an annual rate and must be entered as a percentage
    6.The system will round the entered rate to hundredths of a percent
    7.The rate cannot be negative and cannot be greater than 100
    8.If a term is not entered, it is assumed to be one year
    9.Principal and Interest Rate are required fields
    10.Principal must be greater than zero and less than 10 million

    Expected Results

    You may use any Ruby acceptance testing frameworks of your choosing (Cucumber, Test::Unit, RSpec, etc…). Your acceptance tests should be able to be run with a simple command and output the test results in a readable manner.

    What To Submit

    Your files should be sent in a zip archive. Please make sure to include source files and build files in the archive (if applicable), along with a README file that describes how to build it and how to run it. You should also list the required libraries and versions, including the version of Ruby used for your solution.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: