8

I'm looking for a method that returns a boolean if the String it is passed is a valid number (e.g. "123.55e-9", "-333,556"). I don't want to just do:

public boolean isANumber(String s) {
    try { 
        BigDecimal a = new BigDecimal(s); 
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
}

Clearly, the function should use a state machine (DFA) to parse the string to make sure invalid examples don't fool it (e.g. "-21,22.22.2", "33-2"). Do you know if any such library exists? I don't really want to write it myself as it's such an obvious problem that I'm sure I'd be re-inventing the wheel.

Thanks,

Nick

tddmonkey
  • 20,798
  • 10
  • 58
  • 67
  • Why exactly don't you want to use the parsing from BigDecimal? It's the easiest way, really. – Jorn Jul 22 '09 at 08:11
  • I'd like to know why you do not want to use the snippet you gave? If you need to _use_ the number you must parse it? It is just to validate that a string can go to the backend as-is? – Thorbjørn Ravn Andersen Jul 22 '09 at 08:52
  • Because Exceptions are not ment for this case. Exceptions are, well, for exceptions not for program control. – Malax Jul 22 '09 at 08:54
  • 2
    I have encountered this question a few times too, and while it feels incorrect to exploit an Exception this way, it still is the easiest way to do it. It is simple, well tested and works. I no longer search for other ways. – Narayan Raman Jul 22 '09 at 11:52
  • 1
    I agree with that in theory. If you could control the parsing used in BigDecimal, you could change it to return some value indicating parsing failed, instead of throw an exception. However, since you can't control that, I wouldn't make my own implementation for something that already exists, just because the BigDecimal way is "not very nice because it uses exceptions". – Jorn Jul 22 '09 at 11:52
  • -1 as this is getting seriously picky. BigDecimal handles it for you. If you don't like the exception, put that in a Utils class somewhere and call it "safeIsNum()" or some such. Apache Commnons has some string validation checks that will do this sort of thing as well. – Chris Kessel Jul 22 '09 at 17:44
  • I didn't want to use exceptions as they are slow - they make a native call to fill in the stack trace (fillInStackTrace() in Throwable). I didn't realise commons had a method like this...that's exactly what I was looking for. –  Jul 24 '09 at 12:48

5 Answers5

6

I would avoid re-inventing this method and go with Apache Commons. If your using Spring, Struts or many other commonly used java libraries, they often have Apache commons included. You will want the commons-lang.jar file. Here is the method in NumberUtils you would want:

isNumber[1]

public static boolean isNumber(java.lang.String str)
Checks whether the String a valid Java number.

Valid numbers include hexadecimal marked with the 0x qualifier, scientific notation and numbers marked with a type qualifier (e.g. 123L).

Null and empty String will return false.

Parameters:
str - the String to check
Returns:
true if the string is a correctly formatted number
Brian
  • 13,412
  • 10
  • 56
  • 82
3

use a regexp

dfa
  • 114,442
  • 31
  • 189
  • 228
  • exactly. and here http://java.sun.com/j2se/1.5.0/docs/api/java/math/BigDecimal.html#BigDecimal(java.lang.String) you have some food for your unit tests. – flybywire Jul 22 '09 at 07:39
3

The exact regular expression is specified in the Javadocs for Double.valueOf(String).

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
}
Community
  • 1
  • 1
Michael Myers
  • 188,989
  • 46
  • 291
  • 292
2

Yeah a regular expression should do the trick. I only know .Net regexp but all regex languages are fairly similar so this should get you started. I didn't test it so you might want to kick it around a bit with the Java regex class.

"-?(([0-9]{1,3}(,[0-9{3,3})*)|[0-9]*)(\.[0-9]+(e-?[0-9]*)?)?"

Some of the Regex control syntax:
? - Optional element
| - OR operator. Basically I allowed numbers with or without commas if they were formatted correctly.
[ ] - Set of allowed characters
{ , } - Minimum maximum of element
* - Any number of elements, 0 to infinity
+ - At least one element, 1 to infinity
\ - Escape character
. - Any character (Hence why it was escaped)

Spencer Ruport
  • 34,865
  • 12
  • 85
  • 147
2

Here is a regexp based utility function working fine (couldn't fit the "" check in the regexp while keeping it readable):

public class TestRegexp {
    static final String NUM_REGEX=
        "-?((([0-9]{1,3})(,[0-9]{3})*)|[0-9]*)(\\.[0-9]+)?([Ee][0-9]*)?";
    public static boolean isNum(String s) {
            return s!=null && s.length()>0 && s.matches(NUM_REGEX);  
    }
    public static void main(String[]args) {
        String[] values={
                "",
                "0",
                "0.1",
                ".1",
                "-.5E5",
                "-12,524.5E5",
                "-452,456,456,466.5E5",
                "-452,456,456,466E5",
                "22,22,2.14123415e1",
        };
        for (String value : values) {
            System.out.println(value+" is a number: "
            +isNum(value));
        }
    }

}
Nicolas Simonet
  • 533
  • 4
  • 10