6

I want a method like to_numeric(str) which convert numeric string 'str' into its numeric form else return nil. By numeric form if string is in integer method should return integer and it string is in float it should return float.

I have tried with following code. It works fine but need better solution if possible.

def to_numeric(str)
  Integer(str)
rescue
  Float(str) if Float(str) rescue nil
end

One important thing I forgot to mention is "I don't know the type of my input".

My use case:

arr = [1, 1.5, 2, 2.5, 4]
some_input = get_input_from_some_source

if arr.include?(to_numeric(some_input))
  # do something
end
15
  • try to_i method for string class ruby-doc.org/core-1.9.3/String.html#method-i-to_i Commented Nov 21, 2013 at 8:55
  • By 'numeric' I mean integer and float. I want to convert "1" or "1.34" to its numeric form. Commented Nov 21, 2013 at 8:59
  • 1
    to_i gives integer or float, particularly, an integer. Commented Nov 21, 2013 at 9:08
  • 1
    @budhram just use floats [4].include? 4.0 #=> true Commented Nov 21, 2013 at 9:19
  • 1
    @budhram: from your use-case, it seems like myInput.to_f would do the trick, but if it's your precise use case, then you may need to change it. Comparisons between floats/doubles/.. is not always what you'd expect. The number 1.5 might not be possible to store exactly-like-it-is in a "numeric type". It might be 1.5000000001. If after parsing the input you get 1.49999999, you wouldn't find it in the array by simple include?. Commented Nov 21, 2013 at 9:20

5 Answers 5

11

You can use BigDecimal#frac to achieve what you want

require 'bigdecimal'

def to_numeric(anything)
  num = BigDecimal.new(anything.to_s)
  if num.frac == 0
    num.to_i
  else
    num.to_f
  end
end

It can handle

#floats
to_numeric(2.3) #=> 2.3

#rationals
to_numeric(0.2E-4) #=> 2.0e-05

#integers
to_numeric(1) #=> 1

#big decimals
to_numeric(BigDecimal.new("2"))

And floats, rationals and integers in form of strings, too

Sign up to request clarification or add additional context in comments.

4 Comments

+1 This solution leaves the actual conversion to a ruby library and is easy to read (=maintain) (although it can only handle strings as input)
@tessi Now it can take anything responding to #to_s as input :)
Feel da Beat! Nice solution! But keep in mind the following two cases aren't really handled by the proposed solution nicely: [CASE-1] to_numeric(1.0000000000000001) / 2 => 0, while if we omit to_numeric call then we'll get different result: 1.0000000000000001 / 2 => 0.5 [CASE-2] to_numeric(1.0) => 1, so again if we're to use "to_numeric()" to distill stream of strings that represent numbers, then those two cases can bite us in our arse ;) ps: It wasn't me downvoting : )
I'll just leave this here: Integer('0010') => 8
4

Convert it to Float using String#to_f method. Since ruby using duck typing you may not care if it can be an Integer.

If it looks like a numeric, swims like a numeric and quacks like a numeric, then it probably is a numeric.

But be aware! to_f does not throw any exceptions:

"foobar".to_f # => 0 

4 Comments

"1.2".to_f # 1.2 OK. But "1".to_f # 1.0 No Ok. I need converter which will will "1.2" to 1.2 and "1" to 1.
and if you want to specifically have Integer, then use "123".to_i
@user2422869: actually, even in Ruby you should "care about integers". The math operators like / work differently on them, so having 2 / 3 is not the same as 2.0 / 3.0, and having everything in floats is not always the right way :) I'm not saying your answer is wrong. I just say you have to care.
@quetzalcoatl yes, thanks. And I am pretty sure that float operations are slower.
3

If you really insist to differentiate between Integer and Floats, then you can implement to_numeric like this:

def to_numeric(thing)
  return thing.to_s.to_i if thing.to_s == thing.to_s.to_i.to_s  
  return thing.to_s.to_f if thing.to_s == thing.to_s.to_f.to_s  
  thing
end

It converts an object to an integer, if its string representation looks like an integer (same with float), or returns the unchanged thing if not:

['1', '1.5', 'foo', :bar, '2', '2.5', File].map {|obj| to_numeric obj}
# => [1, 1.5, "foo", :bar, 2, 2.5, File]

5 Comments

This looks nice answer to my use case. Thanks.
I need to reconsider this solution as to_numeric("0.2E-4") is returning "0.2E-4"(string). Found that "0.2E-4".to_s.to_f.to_s is returning "2.0e-05" (Weird).
Sad, but true :) I think @beat-richarts's solution handles this better.
Yes. Have you tried my solution. It works for all cases except BigDecimal. :)
This approach doesn't work for strings such as 0.000001 where the to_s function may return "1.0e-05". The expression s == s.to_f.to_s also makes assumptions about floating point precision which may be incorrect.
3

Here are some options:

  1. Use floats for comparison:

    arr = [1, 1.5, 2, 2.5, 4]
    arr.include? "4.0".to_f #=> true
    
  2. Use strings for comparison:

    arr = %w(1 1.5 2 2.5 4)
    arr.include? "4" #=> true
    
  3. Use eval for conversion:

    eval("4.0") #=> 4.0
    eval("4")   #=> 4
    

    But you have to be very careful when using eval, see @tessi's comment.

5 Comments

The OP gets his input from the get_input_from_some_source. This does not sound like a good input for eval.
Yes, you would have to check the input before passing it to eval. A simple Regexp should do the trick.
Right, but one should be careful suggesting eval. A "simple regexp" like /^\d*\.?\d*$/ might still be harmful for a string like "42\nFile.delete(__FILE__)". eval is a valid solution (just use \A and \z in the regexp) but is not a good recommendation for beginners.
Of course and I don't think it's necessary to use eval here, I've just added it as an example to convert a String to either Float or Integer (the OP's original question).
Eval was the perfect solution for my code golf challenge, thanks!
0
def to_numeric(str)
  str.include?(".") ? str.to_f : str.to_i
end

1 Comment

It wouldn't work with Strings containing "e" or "E" like "5e3". Or, for an original String s="5 is ok.", your method converts it to a Float, 5.0.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.