6

A user enters the string '67.99'. I need to ultimately convert this into the integer 6799.

In other words: convert the currency amount entered via a string into cents via integer data type.

I notice that this happens:

('67.99'.to_f * 100).to_i
#=> 6798

Not expected behavior. I need to save it as 6799, not 6798.

The issue is multiplying this float number by 100:

'67.99'.to_f * 100
#=> 6798.999999999999

Question: How can I properly convert a decimal, entered as a string, into an integer?

Example input and output:

'67'    #=> 6700
'67.'   #=> 6700
'67.9'  #=> 6790
'67.99' #=> 6799

IMO: this is not a duplicate of this question because I am aware that float is not broken.

Community
  • 1
  • 1
Neil
  • 4,578
  • 14
  • 70
  • 155
  • 1
    You should not use floats to represent currency amount. Just try what happens if you calculate `0.10 + 0.20`... – spickermann Nov 07 '16 at 16:38
  • 2
    There's also [BigDecimal](http://ruby-doc.org/stdlib-2.3.1/libdoc/bigdecimal/rdoc/BigDecimal.html) for situations like this: `BigDecimal.new('67.99')` – tadman Nov 07 '16 at 16:43
  • @spickermann I am not doing any mathematical calculations on the float values. I am only taking the string value entered in currency format and saving it as cents into the database. Once there: I know It is safe to do mathematical calculations on the stored amounts as integers/cents. All mathematical calculations are on the integer values in the database. It is just a matter of properly converting them into integer values. – Neil Nov 07 '16 at 16:53
  • Possible duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken) – Holger Just Nov 07 '16 at 20:00
  • Will the user always enter a string that matches the following pattern? `/^\d+\.\d{2}$/`. If so, you can simply delete the period with `String#delete` and then parse that with `String#to_i`. – Jared Beck Nov 08 '16 at 00:10
  • 1
    This is not a duplicate of [Is floating point math broken?](http://stackoverflow.com/questions/588004/is-floating-point-math-broken), IMO. The author is clearly aware of the problems with floating point math already. – Jared Beck Nov 08 '16 at 00:12

1 Answers1

15

Use round:

('67.99'.to_f * 100).round
#=> 6799

As discussed in comments, there is a potentially better way to deal with such strings - BigDecimal class:

(BigDecimal.new('67.99') * 100).round
#=> 6799

This becomes relevant for large numbers:

input = '1000000000000001'

(input.to_f * 100).round
#=> 100000000000000096

(BigDecimal.new(input) * 100).round
#=> 100000000000000100
Stefan
  • 109,145
  • 14
  • 143
  • 218
Andrey Deineko
  • 51,333
  • 10
  • 112
  • 145
  • 1
    ahhhh! I knew it had to be round! Thanks! – Neil Nov 07 '16 at 16:29
  • 2
    @Neil although this works for your test cases, keep in mind that `to_f` is a very basic conversion method. Users might enter `1,000.00` or `1,99` or `$100` and get surprising results. – Stefan Nov 07 '16 at 17:40
  • Good point. I'm good there. The format of the currency input is validated beforehand to ensure it is a valid number and an expected number format as well. – Neil Nov 07 '16 at 19:22
  • This solves the user's immediate problem, but it would be far better to convert the string to a BigDecimal and round _that_ rather than converting the string to a float as the OP is doing. – Wayne Conrad Nov 07 '16 at 20:08
  • @WayneConrad 1. Thx for downvote. 2. OP has gotten info about BigDecimal and potential benefits of its usage. I can of course edit the answer to emphasise it again – Andrey Deineko Nov 07 '16 at 20:13
  • Comments are somewhat ephemeral, so I think that would be valuable to add that information to this answer, especially since it is checkmarked. I worry that future seekers won't know that there's a better way. – Wayne Conrad Nov 07 '16 at 20:17
  • 1
    @WayneConrad edited the answer with BigDecimal reference, if you feel there is more to it - please edit my answer :) – Andrey Deineko Nov 07 '16 at 20:23
  • 1
    @AndreyDeineko I've added another example to show the difference. You might lose 4 cents per 1 quadrillion dollars :-) – Stefan Nov 08 '16 at 07:43
  • @Stefan out loud! :D – Andrey Deineko Nov 08 '16 at 07:44