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"
.
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'
Here's a one-line method that also simply uses string manipulation thereby completely bypassing the numeric issues:
cents.rjust(3, "0").insert(-3, ".")
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"
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With