9

Seems like String.IsNumeric() returns TRUE if it's integer.

String numString = '12';
System.Debug(numString.IsNumeric());

If it's a decimal (eg. 12.2) this is returning FALSE.

I believe it should not consider 12.2 as non-numeric.

Is there a proper workaround for this conundrum without writing much code?

4 Answers 4

11

I suppose you could make your own isNumeric() method to also cover decimal values:

public class StringUtilities{
    public static Boolean isNumeric(String s){
        Boolean ReturnValue;
        try{
            Decimal.valueOf(s);
            ReturnValue = TRUE; 
        } catch (Exception e) {
            ReturnValue = FALSE;
        }
        return ReturnValue;
    }
}

Sample runs:

list<String> slist = new list<String>{
  '12',       // TRUE
  '12.2',     // TRUE
  'string',   // FALSE
  '',         // FALSE
  NULL        // FALSE
};

for (String s : slist) system.debug(
    StringUtilities.isNumeric(s)
);
5
  • I had this in thought. Well as the last resort would go with it. Thanks. Commented Mar 20, 2017 at 5:40
  • 2
    It's really more of a String utility, perhaps...
    – Adrian Larson
    Commented Mar 20, 2017 at 15:51
  • In retrospect, that does sound much better. @AdrianLarson
    – martin
    Commented Mar 20, 2017 at 15:57
  • The problem with this approach is that a high number of non-numbers will produce lots of CPU time as the stack is repeatedly unwound during the exception construction (~50ms each time).
    – sfdcfox
    Commented Mar 20, 2017 at 16:01
  • @sfdcfox agreed, if this function is getting called a lot and it is impacting performance, a regex/pattern matching approach might be better.
    – martin
    Commented Mar 20, 2017 at 16:04
6

I wrote some code that does some simple checks for data conversion as part of my XmlToJson example code that I posted last year. It's simply a Pattern that we cache, and use it to quickly determine if a string is any sort of number. Here's the relevant code. First, we store a couple of patterns to pick out Booleans, decimals, dates, and date-times (everything else in the demo is treated as text):

static Pattern 
    boolPat = Pattern.compile('^(true|false)$'),  decPat = Pattern.compile('^[-+]?\\d+(\\.\\d+)?$'), 
    datePat = Pattern.compile('^\\d{4}.\\d{2}.\\d{2}$'), 
    timePat = Pattern.compile('^\\d{4}.\\d{2}.\\d{2} (\\d{2}:\\d{2}:\\d{2} ([-+]\\d{2}:\\d{2})?)?$');

Then, when we want to auto-detect our data type, we use the following cascading ternary:

        Object value = 
            //  Nothing
            String.isBlank(nodeText)? null:
        //  Try boolean
        boolPat.matcher(nodeText).find()? 
            (Object)Boolean.valueOf(nodeText):
        //  Try decimals
        decPat.matcher(nodeText).find()?
            (Object)Decimal.valueOf(nodeText):
        //  Try dates
        datePat.matcher(nodeText).find()?
            (Object)Date.valueOf(nodeText):
        //  Try times
        timePat.matcher(nodeText).find()? 
            (Object)DateTime.valueOf(nodeText):
        //  Give up, use plain text
        (Object)nodeText;

You'll note that this code is try-catch free--it runs in just a few milliseconds in any scenario, which is deal for processing large amounts of values.

So, to make this work for you, you'd do this:

static Pattern decimalPattern = Pattern.compile('^[-+]?\\d+(\\.\\d*)?$');
public static Decimal getNumber(String value) {
    return String.isBlank(value) || !decimalPattern.matcher(value).find()?
        null: Decimal.valueOf(value);
}
4
  • I thought regex might be a good alternative here, but I wonder if this approach is actually faster?
    – Adrian Larson
    Commented Mar 20, 2017 at 16:23
  • Great answer. I hate to nitpick, but shouldn't the string '12.' with the hanging decimal point be considered a valid xml decimal?
    – martin
    Commented Mar 20, 2017 at 16:24
  • @martin I was concerned about Decimal.valueOf throwing an exception... turns out, you're right, a hanging decimal is indeed legal. Changing the last + to a * accounts for that situation, too.
    – sfdcfox
    Commented Mar 20, 2017 at 16:27
  • 1
    @AdrianLarson It depends. If you know the values are numeric, isNumeric is faster than getNumber. If you don't know if they're numeric, getNumber is faster. It seems to be >50% numeric data favors isNumeric; <50% numeric data favors getNumber.
    – sfdcfox
    Commented Mar 20, 2017 at 16:43
3

So, if efficiency isn't your goal and you want to just write something that utilizes the current string functionality, something like this should work:

public Boolean isNumericOrDecimal(String s){
    if(s == null) return false;
    else if(s.contains('.') && s.indexOf('.') == s.lastIndexOf('.'))
        s = s.replace('.','');
    return s.isNumeric();
}

It would simply replace the decimal if and only if there is a decimal AND the decimal is in the same place (single decimal). Then it would return whether the remaining value was numeric or not.

Simple/quick test cases I tried:

'.123.' = false
'.123' = true
'123.' = true
'123' = true
'a123' = false

If you wanted the number to be correctly formatted you could write logic to say if the decimal was in the first or last position it would also return false, but this makes the assumption that there may be an inferred leading/trailing 0.

1
  • This won't return TRUE for negative values FYI.
    – rmarq423
    Commented Feb 18, 2021 at 19:09
0

i made a custom method for this case, which also takes care of negative Integers/Decimals

public Boolean isNumericOrDecimal(String s){
if(String.isBlank(s)) return false;
if(s.contains('-') && s.indexOf('-') == 0){
    s = s.replaceFirst('-', '');
}
if(s.contains('.') && s.indexOf('.') == s.lastIndexOf('.') && s.indexOf('.') != 0){
    s = s.replace('.','');
}
return s.isNumeric();
}

This should work for all cases i suppose..

PS: .123 will return false. 0.123 will return true.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .