Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert cents into dollar string in Ruby without use of BigDecimal

I want to convert from cents to dollars correctly in Ruby. I will never have to work with fractions of cents.

Is it possible to do this correctly (without floating point errors) without having to use BigDecimal?

E.g., cents to dollars

"99" => "0.99"
"324" => "3.24"

The following seems to work, but is it correct?

(cents.to_i/100.0).to_s

Update: I noticed the line above doesn't work if cents = "10287349283923497624861294712974892742837833".

like image 667
ma11hew28 Avatar asked Sep 18 '11 11:09

ma11hew28


5 Answers

As Micheal Kohl already answered: Take a look to the money gem.

Example:

require 'money'
Money.use_i18n = false  #https://stackoverflow.com/q/31133229/676874
puts Money.new( 99, 'USD')
puts Money.new(324, 'USD')

The following seems to work, but is it correct?

(cents.to_i/100.0).to_s

On the first look, it is ok, but:

cents = '10'
p (cents.to_i/100.0).to_s # -> '0.1'

You don't have 2 digits.

Alternative:

p '%.2f' % (cents.to_i/100.0) # -> '0.10'
like image 165
knut Avatar answered Nov 15 '22 10:11

knut


Here's a one-line method that also simply uses string manipulation thereby completely bypassing the numeric issues:

cents.rjust(3, "0").insert(-3, ".")
like image 39
Michael B Avatar answered Oct 23 '22 17:10

Michael B


You can consider using Rationals as well. However, I am not sure do they get converted to floats when sprintf-ed:

"%.2f" % Rational("324".to_i,100)
#=> "3.24"
"%.2f" % Rational("99".to_i,100)
#=> "0.99"
"%.2f" % Rational("80".to_i,100)
#=> "0.80"
"%.2f" % Rational("12380".to_i,100)
#=> "123.80"
like image 6
Mladen Jablanović Avatar answered Nov 15 '22 11:11

Mladen Jablanović


If they're stings already you could use string manipulation and bypass the numeric problems completely:

# There are, of course, all sorts of ways to do this.
def add_decimal(s)
  pfx = [ '0.00', '0.0', '0.' ]
  if(pfx[s.length])
    s = pfx[s.length] + s
  else
    s = s.dup
    s[-2, 0] = '.'
  end
  s
end

add_decimal('')      #   "0.00" 
add_decimal('1')     #   "0.01" 
add_decimal('12')    #   "0.12" 
add_decimal('123')   #   "1.23" 
add_decimal('1234')  #  "12.34" 
add_decimal('12345') # "123.45"

No precision issues, no floating point, no bignums, no Rational, nothing tricky, nothing clever. Some simple modifications would be needed to deal with negative values but that will be as simple as what's already there.

like image 6
mu is too short Avatar answered Nov 15 '22 11:11

mu is too short


Personally I wouldn't try to re-invent this specific wheel and go with the money gem. From the docs (emphasis added):

Features

Provides a Money class which encapsulates all information about an certain amount of money, such as its value and its currency.

Provides a Money::Currency class which encapsulates all information about a monetary unit.

Represents monetary values as integers, in cents. This avoids floating point rounding errors.

Represents currency as Money::Currency instances providing an high level of flexibility.

Provides APIs for exchanging money from one currency to another.

Has the ability to parse a money and currency strings into the corresponding Money/Currency object.

like image 4
Michael Kohl Avatar answered Nov 15 '22 09:11

Michael Kohl