Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert Input Value to Integer or Float, as Appropriate Using Ruby

Tags:

numbers

ruby

I believe I have a good answer to this issue, but I wanted to make sure ruby-philes didn't have a much better way to do this.

Basically, given an input string, I would like to convert the string to an integer, where appropriate, or a float, where appropriate. Otherwise, just return the string.

I'll post my answer below, but I'd like to know if there is a better way out there.

Ex:

to_f_or_i_or_s("0523.49") #=> 523.49
to_f_or_i_or_s("0000029") #=> 29
to_f_or_i_or_s("kittens") #=> "kittens"
like image 420
WattsInABox Avatar asked Nov 09 '11 20:11

WattsInABox


4 Answers

I would avoid using regex whenever possible in Ruby. It's notoriously slow.

def to_f_or_i_or_s(v)
  ((float = Float(v)) && (float % 1.0 == 0) ? float.to_i : float) rescue v
end

# Proof of Ruby's slow regex
def regex_float_detection(input)
  input.match('\.')
end

def math_float_detection(input)
  input % 1.0 == 0
end

n = 100_000
Benchmark.bm(30) do |x|
  x.report("Regex") { n.times { regex_float_detection("1.1") } }
  x.report("Math") { n.times { math_float_detection(1.1) } }
end

#                                     user     system      total        real
# Regex                           0.180000   0.000000   0.180000 (  0.181268)
# Math                            0.050000   0.000000   0.050000 (  0.048692)

A more comprehensive benchmark:

def wattsinabox(input)
  input.match('\.').nil? ? Integer(input) : Float(input) rescue input.to_s
end

def jaredonline(input)
  ((float = Float(input)) && (float % 1.0 == 0) ? float.to_i : float) rescue input
end

def muistooshort(input)
  case(input)
  when /\A\s*[+-]?\d+\.\d+\z/
      input.to_f
  when /\A\s*[+-]?\d+(\.\d+)?[eE]\d+\z/
      input.to_f
  when /\A\s*[+-]?\d+\z/ 
      input.to_i     
  else  
      input
  end
end

n = 1_000_000
Benchmark.bm(30) do |x|
  x.report("wattsinabox") { n.times { wattsinabox("1.1") } }
  x.report("jaredonline") { n.times { jaredonline("1.1") } }
  x.report("muistooshort") { n.times { muistooshort("1.1") } }
end

#                                     user     system      total        real
# wattsinabox                     3.600000   0.020000   3.620000 (  3.647055)
# jaredonline                     1.400000   0.000000   1.400000 (  1.413660)
# muistooshort                    2.790000   0.010000   2.800000 (  2.803939)
like image 82
jaredonline Avatar answered Oct 14 '22 20:10

jaredonline


def to_f_or_i_or_s(v)
    v.match('\.').nil? ? Integer(v) : Float(v) rescue v.to_s
end
like image 25
WattsInABox Avatar answered Oct 14 '22 20:10

WattsInABox


A pile of regexes might be a good idea if you want to handle numbers in scientific notation (which String#to_f does):

def to_f_or_i_or_s(v)
    case(v)
    when /\A\s*[+-]?\d+\.\d+\z/
        v.to_f
    when /\A\s*[+-]?\d+(\.\d+)?[eE]\d+\z/
        v.to_f
    when /\A\s*[+-]?\d+\z/ 
        v.to_i     
    else  
        v
    end
end

You could mash both to_f cases into one regex if you wanted.

This will, of course, fail when fed '3,14159' in a locale that uses a comma as a decimal separator.

like image 31
mu is too short Avatar answered Oct 14 '22 19:10

mu is too short


Depends on security requirements.

def to_f_or_i_or_s s
    eval(s) rescue s
end
like image 34
Nakilon Avatar answered Oct 14 '22 20:10

Nakilon