views:

1672

answers:

11

Is there a better, more elegant (and/or possibly faster) way than

boolean isNumber = false;
try{
   Double.valueOf(myNumber);
   isNumber = true;
} catch (NumberFormatException e) {
}

...?


Edit: Since I can't pick two answers I'm going with the regex one because a) it's elegant and b) saying "Jon Skeet solved the problem" is a tautology because Jon Skeet himself is the solution to all problems.

+8  A: 

You could use a regex, i.e. something like String.matches("^[\\d\\-\\.]+$"); (if you're not testing for negative numbers or floating point numbers you could simplify a bit).

Not sure whether that would be faster than the method you outlined though.

Edit: in the light of all this controversy, I decided to make a test and get some data about how fast each of these methods were. Not so much the correctness, but just how quickly they ran.

You can read about my results on my blog. (Hint: Jon Skeet FTW).

Phill Sacre
It would also fail for negative numbers and non-integers :)
Jon Skeet
I will edit the post to make that very point :)
Phill Sacre
Has anybody tested to see if a regexp is faster or slower than Jon Skeet's method below? The regexp certainly looks cleaner if you can find one that works for the numbers you expect.
Paul Tomblin
Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems. ;)
DoctaJonez
@Paul: I did tests with .NET ages ago and regexes were significantly slower. If you want to use a regex, at least built it once and compile it :) (I suspect it will still be slower though. Btw, my code now covers a few more cases than it originally did, and it's starting to get complicated...)
Jon Skeet
both this answer and john skeet's presume that the decimal separator is . and you do not allow thousands separators either.
John Gardner
A regex will never be quite as fast as a well-written non-regex approach, but the difference won't necessarily matter. However, the regex in this answer - ^[\\d|\\-|\\.]+$ - isn't even correct. For example, it will flag "-", "." and "|" as valid numbers.
Alan Moore
ugh. NumberFormat.
Ran Biron
I think it all depends on what your requirements are. If you're parsing input from multiple countries, potentially with thousand separators, a regex is probably not for you!
Phill Sacre
@Phill: Even if you're not, NumberFormat is easier to work with, already have its regex debugged and is maintained by Sun. Why create a second solution if a canonical one already exists?
Ran Biron
Because you may know ahead of time what format of numbers you're getting. Looks, I agree 100% with DRY and not reinventing the wheel, I just thought this might be a quick and dirty way of checking a number in certain circumstances. Not all the time. I should have made that clearer.
Phill Sacre
+6  A: 

I don't believe there's anything built into Java to do it faster and still reliably, assuming that later on you'll want to actually parse it with Double.valueOf (or similar).

I'd use Double.parseDouble instead of Double.valueOf to avoid creating a Double unnecessarily, and you can also get rid of blatantly silly numbers quicker than the exception will by checking for digits, e/E, - and . beforehand. So, something like:

public boolean isDouble(String value)
{        
    boolean seenDot = false;
    boolean seenExp = false;
    boolean justSeenExp = false;
    boolean seenDigit = false;
    for (int i=0; i < value.length(); i++)
    {
        char c = value.charAt(i);
        if (c >= '0' && c <= '9')
        {
            seenDigit = true;
            continue;
        }
        if ((c == '-' || c=='+') && (i == 0 || justSeenExp))
        {
            continue;
        }
        if (c == '.' && !seenDot)
        {
            seenDot = true;
            continue;
        }
        justSeenExp = false;
        if ((c == 'e' || c == 'E') && !seenExp)
        {
            seenExp = true;
            justSeenExp = true;
            continue;
        }
        return false;
    }
    if (!seenDigit)
    {
        return false;
    }
    try
    {
        Double.parseDouble(value);
        return true;
    }
    catch (NumberFormatException e)
    {
        return false;
    }
}

Note that despite taking a couple of tries, this still doesn't cover "NaN" or hex values. Whether you want those to pass or not depends on context.

In my experience regular expressions are slower than the hard-coded check above.

Jon Skeet
In my experience, I usually want the Double *and* to know if it's a legal double or not, so I'd probably change your method to return Double or throw a NumberFormatException.
Paul Tomblin
..or return a tiny class with a boolean and a double, or use an out-of-bounds double, depending on the need.
Paul Tomblin
@Paul: Indeed. At that point you've basically implemented .NET's "bool Double.TryParse(string text, out double). In Java an alternative would be to return a Double (object) or null for "invalid".
Jon Skeet
Pssst... +1.0e-7 is a valid double.
plinth
Pssst... it will still fail. There are two possible signs in a double: preceding the mantissa and after the e/E, and a sign can be a '+'. There's actually a lesson here, which is that the spec for a double is more complicated than your code: better to do if ("0123456789eE-+".Contains(c)) continue;
plinth
That way you get a cheap, simple pre-filter that has false positives rather than a cheapish, complicated pre-filter that has false negatives.
plinth
Yes, I think you're right. I fixed the possible signs bit though (before seeing your comment :)
Jon Skeet
this answer also presumes that the decimal separator is . and you do not allow thousands separators either.
John Gardner
I'm assuming both of those because Double.valueOf does (IIRC).
Jon Skeet
It might be faster to return false if it's an empty string then going into the try/catch and having the empty string passed to Double.parseDouble().
J c
@J c - Good point. Editing appropriately...
Jon Skeet
Downvoters: please add an explanatory comment.
Jon Skeet
+2  A: 

I would use the Jakarta commons-lang, as always ! But I have no idea if their implementation is fast or not. It doesnt rely on Exceptions, which might be a good thig performance wise ...

Guillaume
commons-lang also detects things that Java doesn't, like: Multiple commas, whitespace around the number and some more.
Aaron Digulla
It depends what you want to do. There are numbers which aren't representable as Java doubles - should they be included or not? If you really want to know whether you'll eventually be able to convert to a Java double, I suspect you'll have a hard time doing so reliably without a call to Double.
Jon Skeet
+3  A: 

Use StringUtils.isDouble(String) in Apache Commons.

Nick Holt
A: 

Following Phill's answer can I suggest another regex?

String.matches("^-?\\d+(\\.\\d+)?$");
bruno conde
+2  A: 

Most of these answers are somewhat acceptable solutions. All of the regex solutions have the issue of not being correct for all cases you may care about.

If you really want to ensure that the String is a valid number, then I would use your own solution. Don't forget that, I imagine, that most of the time the String will be a valid number and won't raise an exception. So most of the time the performance will be identical to that of Double.valueOf().

I guess this really isn't an answer, except that it validates your initial instinct.

Randy

Randy Stegbauer
I like the regex approach. While it's true that the one's presented may not be correct for the OP's requirements, that's simply because the requirements are vague. Presenting the way to develop the solution is the best we can do.
Brian Knoblauch
I see what you're saying. If the goal is simply to detect digits, then [0-9]+ would work.
Randy Stegbauer
+6  A: 

See java.text.NumberFormat (javadoc).

NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
Number myNumber = nf.parse(myString);
int myInt = myNumber.intValue();
double myDouble = myNumber.doubleValue();
Ran Biron
It's nice to see an answer that doesn't make locale assumptions. +1
J c
"Global community" - you have to have proper locale formatting / parsing. 1.024,00 in Germany is 1 024.00 in Paris and 1,024.00 in New York.
Ran Biron
+3  A: 

Leveraging off Mr. Skeet:

private boolean IsValidDoubleChar(char c)
{
    return "0123456789.+-eE".indexOf(c) >= 0;
}

public boolean isDouble(String value)
{
    for (int i=0; i < value.length(); i++)
    {
        char c = value.charAt(i);
        if (IsValidDoubleChar(c))
            continue;
        return false;
    }
    try
    {
        Double.parseDouble(value);
        return true;
    }
    catch (NumberFormatException e)
    {
        return false;
    }
}
plinth
+4  A: 

The correct regex is actually given in the Double javadocs:

To avoid calling this method on an invalid string and having a NumberFormatException be thrown, the regular expression below can be used to screen the input string:

    final String Digits     = "(\\p{Digit}+)";
    final String HexDigits  = "(\\p{XDigit}+)";
    // an exponent is 'e' or 'E' followed by an optionally 
    // signed decimal integer.
    final String Exp        = "[eE][+-]?"+Digits;
    final String fpRegex    =
        ("[\\x00-\\x20]*"+  // Optional leading "whitespace"
         "[+-]?(" + // Optional sign character
         "NaN|" +           // "NaN" string
         "Infinity|" +      // "Infinity" string

         // A decimal floating-point string representing a finite positive
         // number without a leading sign has at most five basic pieces:
         // Digits . Digits ExponentPart FloatTypeSuffix
         // 
         // Since this method allows integer-only strings as input
         // in addition to strings of floating-point literals, the
         // two sub-patterns below are simplifications of the grammar
         // productions from the Java Language Specification, 2nd 
         // edition, section 3.10.2.

         // Digits ._opt Digits_opt ExponentPart_opt FloatTypeSuffix_opt
         "((("+Digits+"(\\.)?("+Digits+"?)("+Exp+")?)|"+

         // . Digits ExponentPart_opt FloatTypeSuffix_opt
         "(\\.("+Digits+")("+Exp+")?)|"+

   // Hexadecimal strings
   "((" +
    // 0[xX] HexDigits ._opt BinaryExponent FloatTypeSuffix_opt
    "(0[xX]" + HexDigits + "(\\.)?)|" +

    // 0[xX] HexDigits_opt . HexDigits BinaryExponent FloatTypeSuffix_opt
    "(0[xX]" + HexDigits + "?(\\.)" + HexDigits + ")" +

    ")[pP][+-]?" + Digits + "))" +
         "[fFdD]?))" +
         "[\\x00-\\x20]*");// Optional trailing "whitespace"

    if (Pattern.matches(fpRegex, myString))
        Double.valueOf(myString); // Will not throw NumberFormatException
    else {
        // Perform suitable alternative action
    }

This does not allow for localized representations, however:

To interpret localized string representations of a floating-point value, use subclasses of NumberFormat.

Michael Myers
A: 

If you want something that's blisteringly fast, and you have a very clear idea of what formats you want to accept, you can build a state machine DFA by hand. This is essentially how regexes work under the hood anyway, but you can avoid the regex compilation step this way, and it may well be faster than a generic regex compiler.

Draemon
A: 

I prefer using a loop over the Strings's char[] representation and using the Character.isDigit() method. If elegance is desired, I think this is the most readable:

package tias;

public class Main {
  private static final String NUMERIC = "123456789";
  private static final String NOT_NUMERIC = "1L5C";

  public static void main(String[] args) {
    System.out.println(isStringNumeric(NUMERIC));
    System.out.println(isStringNumeric(NOT_NUMERIC));
  }

  private static boolean isStringNumeric(String aString) {
    if (aString == null || aString.length() == 0) {
      return false;
    }
    for (char c : aString.toCharArray() ) {
      if (!Character.isDigit(c)) {
        return false;
      }
    }
    return true;
  }

}