Most elegant way to detect if a String is a number?

14,945

Solution 1

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.

Solution 2

See java.text.NumberFormat (javadoc).

NumberFormat nf = NumberFormat.getInstance(Locale.FRENCH);
Number myNumber = nf.parse(myString);
int myInt = myNumber.intValue();
double myDouble = myNumber.doubleValue();

Solution 3

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.

Solution 4

Use StringUtils.isDouble(String) in Apache Commons.

Solution 5

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;
    }
}
Share:
14,945
Epaga
Author by

Epaga

Java developer @ i-net software by day Indie iOS developer of Mindscope and In-Flight Assistant by night Husband of 1, dad of 4

Updated on July 26, 2022

Comments

  • Epaga
    Epaga almost 2 years

    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.

  • Aaron Digulla
    Aaron Digulla over 15 years
    commons-lang also detects things that Java doesn't, like: Multiple commas, whitespace around the number and some more.
  • Jon Skeet
    Jon Skeet over 15 years
    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.
  • Paul Tomblin
    Paul Tomblin over 15 years
    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
    Paul Tomblin over 15 years
    ..or return a tiny class with a boolean and a double, or use an out-of-bounds double, depending on the need.
  • Jon Skeet
    Jon Skeet over 15 years
    @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".
  • user1568901
    user1568901 over 15 years
    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.
  • plinth
    plinth over 15 years
    Pssst... +1.0e-7 is a valid double.
  • plinth
    plinth over 15 years
    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
    plinth over 15 years
    That way you get a cheap, simple pre-filter that has false positives rather than a cheapish, complicated pre-filter that has false negatives.
  • John Gardner
    John Gardner over 15 years
    this answer also presumes that the decimal separator is . and you do not allow thousands separators either.
  • Jon Skeet
    Jon Skeet over 15 years
    I'm assuming both of those because Double.valueOf does (IIRC).
  • srgblnch
    srgblnch over 15 years
    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().
  • srgblnch
    srgblnch over 15 years
    It's nice to see an answer that doesn't make locale assumptions. +1
  • Ran Biron
    Ran Biron over 15 years
    "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.
  • Randy Stegbauer
    Randy Stegbauer over 15 years
    I see what you're saying. If the goal is simply to detect digits, then [0-9]+ would work.
  • opticyclic
    opticyclic about 11 years
    Although this is a theoretically interesting answer, it isn't really useful here as you don't provide info on how to construct such a machine and use it in context.
  • test
    test over 9 years
    Trying this for 1 gets me false when it should be true :P
  • David Leppik
    David Leppik about 7 years
    Moved in newer versions. See org.apache.commons.lang3.math.NumberUtils. In particular NumberUtils.isParseable() and NumberUtils.isCreatable().