1

I have a sanitised string I want to evaluate using ruby eval. The string may contain any simple arithmetic formula, like '44/5'.

The problem is that the result of eval('44/5') will be 8 (instead of 8.8).

It happens as a part of evaluating (because 1.0*eval('44/5') gives 8.0).

I didn't find any extra parameter for eval to manage it.

I even tried to prepend or wrap the string to convert it to:

'0.0+44/5'

or

'1.0*(44/5)'

But it still doesn't give me 8.8.

eval('44.0/5') gives me the desired result but I don't want to insert anything inside the string (I am ready just to wrap it if necessary).

Any idea how can I solve the problem?

Alexander
  • 7,484
  • 4
  • 51
  • 65

4 Answers4

6

Here it is using Rational#to_f:

Rational('44/5').to_f # => 8.8
'44/5'.to_r.to_f # => 8.8
Arup Rakshit
  • 116,827
  • 30
  • 260
  • 317
  • 2
    String has a `to_r`method: '44/5'.to_r – steenslag Dec 22 '13 at 21:58
  • @steenslag Yes.. `'44/5'.to_r # => (44/5)`, but after that `#to_f` needs to be called. Anyway I have added that also.. Thanks – Arup Rakshit Dec 22 '13 at 22:02
  • It doesn't really help in my case (as I told the string can represent any arithmetic formula, for example '2+3/4') – Alexander Dec 23 '13 at 08:40
  • @Alexander I don't get you. And how the accepted answer differs from this one? Reading at your question what I can see is - your problem was - you are getting 8, whereas you are looking for 8.8. – Arup Rakshit Dec 23 '13 at 08:42
5

You could also use mathn from Ruby's core library:

require 'mathn'

res = eval('44/5')      #=> (44/5)
res.to_f                #=> 8.8

This library will automatically convert to rationals where appropriate.

Patrick Oscity
  • 53,604
  • 17
  • 144
  • 168
  • this is brilliant +1 for the knowledge think this needs to be added to the ruby documentation – bjhaid Dec 22 '13 at 21:23
  • @bjhaid It is in the docs ;-) I've linked to it. – Patrick Oscity Dec 22 '13 at 22:00
  • the docs does not explain that you get a Rational object (the object type), its not straightforward for some beings, had to just check pry/irb to know that it was a rational object – bjhaid Dec 22 '13 at 22:04
  • Ah now I see what you mean. Yes that'd be a good idea. – Patrick Oscity Dec 22 '13 at 22:06
  • Requiring the mathn may effect other parts of the code (imagine that the eval code is just a small method of a class in a huge system) because it changes the behaviour Fixnum and Bignum (look at http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/25644). I would like to have a way to isolate it (something like mixin mathn into my module). Any tested solution for this problem? Thanks! – Alexander Dec 30 '13 at 10:39
  • That's a general problem with how monkey patches are handled in Ruby. This needs to be implemented in the programming language itself before we can do such things. [Ruby 2.0 introduces refinements](http://www.ruby-doc.org/core-2.1.0/doc/syntax/refinements_rdoc.html) as an experimental feature to solve this problem, with the effect that monkeypatches can be applied to a single file instead of your whole ecosystem. Unfortunately, the MathN library would have to be rewritten to actually use refinements instead of just monkey patching the builtin classes. – Patrick Oscity Dec 30 '13 at 16:57
  • There is no official implementation of MathN with refinements yet, but I have put one together for you in this Gist: https://gist.github.com/padde/8184747. Note that you will get warnings because refinements are still an experimental feature. – Patrick Oscity Dec 30 '13 at 17:02
0

I didn't find any extra parameter for eval to manage it.

Because eval is for all things, not only mathematicall equations.

Arup's solution looks nicer but here is mine:

str = '4/5'
eval('1.0*'+ str) #0.8

ps. 2nd string (1.0*(44/5)) won't work because (44/5) is evaluated first(8 = integer) and then you multiply by 1.0(= 8.0).

Community
  • 1
  • 1
Darek Nędza
  • 1,420
  • 1
  • 12
  • 19
0

Not sure why no one has mentioned that to force a float put a float in the divisor. i.e.

eval('44/5') 
#=> 8
eval('44/5.0')
#=> 8.8

Although if you lack control over the string it might be more difficult but this would work

eval('44/5'.split("/").map(&:to_f).join("/"))
#=> 8.8

to handle multiple arthimitic symbols using eval

 pattern = /([0-9]\.?[0-9]+|[0-9]+|[+-\/*\(\)])/
 str = "2+3/4"
 str.scan(pattern).flatten.map{|e| e =~(/[0-9]/) ? e.to_f : e}
 #=> [2.0,"+",3.0,"/",4.0]
 str.scan(pattern).flatten.map{|e| e =~(/[0-9]/) ? e.to_f : e}.join
 #=> "2.0+3.0/4.0"
 eval(str.scan(pattern).flatten.map{|e| e =~(/[0-9]/) ? e.to_f : e}.join)
 #=> 2.75

Works with parentheses too and existing floats

 str = "(2+3)/4"
 eval(str.scan(pattern).flatten.map{|e| e =~(/[0-9]/) ? e.to_f : e}.join)
 #=> 1.25

 str = "(2.3+1)/4"
 eval(str.scan(pattern).flatten.map{|e| e =~(/[0-9]/) ? e.to_f : e}.join)
 #=> 0.825

Issue with Rational method

 str = "(2.3+3)/4*2"
 str.to_r.to_f
 #=>0.0
 eval(str.scan(pattern).flatten.map{|e| e =~(/[0-9]/) ? e.to_f : e}.join)
 #=> 2.65

Very simple without additional libraries.

engineersmnky
  • 25,495
  • 2
  • 36
  • 52