Search code examples
rubybigdecimal

Ruby BigDecimal - Having Conceptual Problems


I am trying to transfer money from one "account" to another:

puts ("\nTransfer how much?")

require 'bigdecimal'

amount = gets.chomp
amount = BigDecimal(amount) #<--Does BigDecimal have to work with strings???

puts ("\nTransfer to which account?")

acct_to = gets.chomp.to_i #<--Accounts are numbered previously for the user.
acct_to = acct_to - 1 #<--Since the account numbers are stored in an array...

#having a problem with #{amount} in the string below.  It is showing something like
    #1.2E0???
puts ("\nTransfer #{amount} from #{names[acct_from]} to #{names[acct_to]}? [1 - yes] / [2 - no]")

#Makes transfer==============================

e = gets.chomp.to_i

    if e == 1

        puts ("\nTransferring!")

        sum1 = 0
        sum2 = 0

        sum1 = BigDecimal(ac[names[acct_from]].to_s) #<-- ac is a hash
        sum2 = BigDecimal(ac[names[acct_to]].to_s)

        ac[names[acct_from]] = sum1 - amount
        ac[names[acct_to]] = sum2 + amount

        puts ("\n#{names[acct_from]}'s new balance is #{ac[names[acct_from]]}.")
        puts ("\n#{names[acct_to]}'s new balance is #{ac[names[acct_to]]}.")

    end


end 

Ok, I have this working really well with numbers operating as floats; however, as you know, floats are causing problems.

Please help me get an introductory grasp of how bigdecimal works.

Also, if you are really awesome, help me get it to work in this specific situation.


Solution

  • First, if it is working with floats, you can get it to work with BigDecimal as well, and you should, because of the obvious reasons.

    So, to answer your first question in the comments to your code: Yes, BigDecimal instantiation has to work with strings, the reason is quite obvious: Stringified number values are not prone to any inaccuracies and do not share the limits of float representation:

    # Think of this number
    float = 23.12323423142342348273498721348923748712340982137490823714089374
    
    # Ruby will truncate its precision to 17, because Float's are not made for precise, but for fast calculation
    float #=> 23.123234231423424
    
    # Now, if BigDecimal would initialize with a float value, the precision would get lost on the way, too. Therefore, BigDecimal needs strings
    big_decimal_from_float = BigDecimal.new(23.12323423142342348273498721348923748712340982137490823714089374.to_s)
    big_decimal_from_string = BigDecimal.new("23.12323423142342348273498721348923748712340982137490823714089374")
    
    # Now you'll see that the BigDecimal initialized "with a float value" will have lost some precision
    

    To answer your second question, 1.2E0 is just Scientific Notation for 1.2. BigDecimal always uses Scientific Notation since it is intented for use in really precise calculations used in science and financial math.

    To comment on your example, using BigDecimal is surely the right way to go, but you have to use it throughout and store your values accordingly. That means that if you write to an SQL database, you will have to use a decimal format with the right precision. Also, all instantiations thereform have to be instances of BigDecimal and never Float. One float in your entire finance application can rain on your parade if you intend to do financial math with really tiny fractures or high values.

    To relieve you of some of the pitfalls of money handling, have a look at the Exchange Gem. I wrote it to have a way of representing money in a ruby application using BigDecimal and ISO4217 compatible instantiation. It may help you handling money throughout an application and avoid some of the pitfalls involved.