4

I'm trying to catch the difference between a numeric string vs. an arbitrary string:

'0'.to_f
#=> 0.0

'hello'.to_f
#=> 0.0

Both of the above return a Float. How do I catch the difference if the user inputs the actual value '0' or if the user inputs the value 'hello'?

I am trying to create a simple Celsius to Fahrenheit calculator. If the user inputs "hello" the program should output Please type in a number: but if the user types in 0 then the program should output the correct Fahrenheit calculation.

3 Answers3

7

Use this:

number = Float( string_to_convert ) rescue nil
if number.nil? 
  puts "#{string_to_convert} is not a number"
else
  # DO the conversion
end

This will use a sensible set of rules for converting String values into Floats, supporting negative numbers, scientific notation, whilst not requiring you write a regular expression to try and capture all the valid ways of expressing floating point numbers in Ruby.

The rescue is required to catch the error from a failed conversion.


Potentially better Ruby code for your particular purpose (and combining design from Tamer's answer with feedback from Stefan in comments):

begin
  number = Float( string_to_convert )
rescue ArgumentError
  puts "'#{string_to_convert}' is not a number"
else
  # Do the conversion, using number variable
end

However, if the program flow is more complicated than input-or-bust-then-repeat, I still find the one-liner can be useful - provided of course you either raise an error later or can deal with missing values because the conversion to a Float failed.

Neil Slater
  • 25,116
  • 5
  • 71
  • 90
  • 1
    The `rescue nil` / `if nil` looks cumbersome, why not use `begin ... rescue ... else ... end` instead? – Stefan Sep 17 '15 at 13:40
  • Inline rescues can result in gotchas. Suppose, for example, you wrote `xval = 7`, then a little later, `sval = gets`. Working against a deadline, you then wrote `number = Float(xval) rescue nil` (rather than `Float(sval)`). You'd want that to raise an un-rescued exception, but of course it wouldn't. Instead, all user entries would be rejected. – Cary Swoveland Sep 17 '15 at 13:42
  • @Stefan: I think a very minor personal preference here not to use exception for controlling program flow. See e.g. http://stackoverflow.com/questions/729379/why-not-use-exceptions-as-regular-flow-of-control – Neil Slater Sep 17 '15 at 13:44
  • @CarySwoveland: Seems believable, but I'm not really following how that scenario is any more likely or worse than others where exception handling is used in a different way. Do you have any "best practice" links or something where this is laid out in more depth? Probably I'd protect against your suggested with an explicit capture of `ArgumentError` in a very short block form, but that would only be an improvement if `xval` didn't still exist in the scope (so would raise a NameError) – Neil Slater Sep 17 '15 at 13:49
  • I'd say it's okay to rescue an exception in order to print an error message. – Stefan Sep 17 '15 at 13:53
  • @Stefan: Yes. Probably my main reason looking at it in more detail is I'm just not used to using the `else` clause in `begin...rescue...` etc structure. So it didn't occur. – Neil Slater Sep 17 '15 at 13:55
  • You are right. The problem lies with the method `Float` and not with on-line rescues (which still can be problematic in other situations). Is there a way to guard against the coding error I described? Maybe not. – Cary Swoveland Sep 17 '15 at 13:55
  • This works flawlessly. It seems silly to have to resort to regex for something so simple. This should be a fundamental part of the language. Like in Python `float( '0' )` would simply return `0`. While `float( 'foo' )` will return a `ValueError`. – Clever Programmer Sep 18 '15 at 04:37
4

You could use a regular expression like:

/
\A   # anchor on start of a string, so there there is nothing more than float allowed
\-?  # optional minus
\d+  # non-empty row of digits
(
 \.  # dot
 \d+ # another row of digits
)?   # ? means both of the above are optional
\z   # anchor on end of a string, so there there is nothing more than float allowed
/x

single line version: /\A\-?\d+(\.\d+)?\z/

In typical use-case, Float( ) might be better, but using a regex separates you from Ruby's definition of Float literal, that can come handy if i.e. you want to allow a comma as decimal mark, or thousand separator (Ruby will only allow . and _ respectively). Regex match will also just return true or false, that can be useful if you want to avoid exception handling - ArgumentError thrown by Float( ) on fail is pretty generic, so it might get thrown by other nearby method call, and thus can get difficult to handle properly, or just make your code ugly.

Borsunho
  • 1,062
  • 7
  • 19
  • Can you please breakdown what the regex means here? I am not very familiar with regex syntax. – Clever Programmer Sep 17 '15 at 11:34
  • @Rafeh `\d+` means a row of digits(`\d` is a digit, and `+` means "1 or more"), optionally followed (indicated by `?`) by a dot (`\.`, backslash needed necause dot is a speciall symbol) and other row of digits (`\d+`). Parentheses group dot and second row so `?` applies to both of them at once. – Borsunho Sep 17 '15 at 11:38
  • I think it would be better for your explanation to be part of your answer. An effective (and easy) way to explain how a regex works is to write it in extended mode (`/x`), as, for example, I did [here](http://stackoverflow.com/questions/32052622/what-would-the-regular-expression-to-extract-the-3-from-be). – Cary Swoveland Sep 17 '15 at 13:11
  • What if the user enters a temperature of `'-20.3'`? – Cary Swoveland Sep 17 '15 at 13:19
  • @CarySwoveland, using regexes is not a good thing in first place, but I was not aware that `Float( s )` is any more strict that `#to_s` so I thought of it as 'necessary evil' here. I seriously consider that I should remove this answer instead of fixing it further, but still thanks for input ;) – Borsunho Sep 17 '15 at 13:55
  • @CarySwoveland Because, what about `1.234e10`? What about `1_000.89`? What about `0x09A`? To make a regex that works as nicely as `Float( s )` would be very hard, and there is no reason to do so. – Borsunho Sep 17 '15 at 13:58
  • @Borsunho it depends. Relying on a method that does some undocumented magic under the hood can be tricky, too. As a developer, I usually want to control all aspects of my application, including specifying the allowed input. And regular expressions look like a good tool for that job :-) – Stefan Sep 17 '15 at 14:17
  • Those are good points, but keep in mind that the user is entering temperatures, and probably has been given some instruction on what to enter. What is the cost if the user enters `"1_234"` or `"1.02e2"` and is told it's an invalid entry? I wouldn't delete your answer (but please permit negative temperatures) as I think it and this discussion is useful.btw, 1.234e10 is pretty darn hot. – Cary Swoveland Sep 17 '15 at 14:23
  • @CarySwoveland I'll follow your advice, though I honestly can't think of any use case when regex would be superior ;) – Borsunho Sep 17 '15 at 15:08
  • 1
    @Borsunho: I think Stefan and Cary make good points here. Although for OP using `Float` is better, that is because they "just want to detect a valid float" and are happy to take Ruby's built-in definition of that. Using a regex separates you from the language's definition, puts you in control and the end user doesn't have to care about how Ruby's Float literals work. Also, no exception handling, just an "is it a Float" detector. Consider changing the last paragraph into a description of those virtues - for when a regex would be better . . . – Neil Slater Sep 18 '15 at 06:47
2
begin
  value = Float(input)
  # do your correct logic here
rescue ArgumentError
  puts "Please type in a number!"
end
Tamer Shlash
  • 8,675
  • 4
  • 41
  • 74
  • Note that you may inadvertently rescue from a bug or other problem in your main logic with this structure, resulting in an incorrect error message. – Neil Slater Sep 17 '15 at 11:48
  • 1
    @NeilSlater it would inadvertently catch a bug that happens only within the code in place of `# do your correct logic here` and method calls in this place, not anything outside the begin block. – Tamer Shlash Sep 17 '15 at 11:51
  • 1
    Yes, that is what I meant to say. In addition scoping to just the expected problem with conversion by specifying `ArgumentError` is *good thing*, although unfortunately `ArgumentError` is relatively common error. – Neil Slater Sep 17 '15 at 11:51
  • 1
    @TamerShlash the `# do your correct logic here` part should be moved from the `begin` into an `else` block. – Stefan Sep 17 '15 at 13:46