6

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?

9

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)
);
  • I had this in thought. Well as the last resort would go with it. Thanks. – Pasan Eeriyagama Mar 20 '17 at 5:40
  • 2
    It's really more of a String utility, perhaps... – Adrian Larson Mar 20 '17 at 15:51
  • In retrospect, that does sound much better. @AdrianLarson – martin Mar 20 '17 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 Mar 20 '17 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 Mar 20 '17 at 16:04
4

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);
}
  • I thought regex might be a good alternative here, but I wonder if this approach is actually faster? – Adrian Larson Mar 20 '17 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 Mar 20 '17 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 Mar 20 '17 at 16:27
  • @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 Mar 20 '17 at 16:43
2

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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.