Calculate Mathematical Text Expression in Apex

 I recently came across the following issue. I created custom metadata, where I can set mathematical expression with combination of fixed values and merge fields. 

For example: (5+{!Total_Days__c})*10

Then, in apex code, I needed to calculate this expression based on the record values.

Replacing the merge fields with actual value was very easy, using String methods like substring/replace, but calculating the expression was less easy, as there is no build in method that handling it. 

When searching for similar solution in other language, most of the suggestion are based on Javascript method eval.

Eventually I took some time and wrote such service myself.

It might missing some error handling for wrong input, so when using it might want to wrap the function call with try-catch, but generally with correct input it does produce the correct number.


public class StringToNumber{

    public static Decimal convertStringToNum(String expression){

        Decimal val = 0;
        String copyExp=expression;

        while(copyExp.contains('(')){

            String innerLogic = copyExp.substring(copyExp.lastIndexOf('(') + 1).subStringBefore(')');
            
            Decimal innerCalc=getFormulaInnerValue(innerLogic);

            if(copyExp.contains('(' +  innerLogic + ')')){
                copyExp=copyExp.replace('(' +  innerLogic + ')', String.valueOf(innerCalc));
            }
            else{
                copyExp=copyExp.replace('(', '');
            }
        }

        return getFormulaInnerValue(copyExp);
    }

    private static Decimal getFormulaInnerValue(String innerExp){

        Decimal val = 0;
        String copyExp = innerExp;

        //Should not have indexes at this point only numbers and operators
        list<String> l_logicValues = copyExp.split('[-+//*]');


        //Get list of operators
        list<String> l_operators = new list<String>();

        for(Integer formulaIndex = 0; formulaIndex < innerExp.length(); formulaIndex ++){
            if(innerExp.charAt(formulaIndex) == 43
                || innerExp.charAt(formulaIndex) == 45
                || innerExp.charAt(formulaIndex) == 42
                || innerExp.charAt(formulaIndex) == 47){

                l_operators.add(innerExp.substring(formulaIndex, formulaIndex + 1));
            }
        }
        
        Integer nextIndex=0;
    
        //empty value indicate minus value
        if(String.isBlank(l_logicValues.get(0))){
            val = Decimal.valueOf(l_logicValues.get(1).trim()) * (-1);
            l_logicValues.remove(0);
            l_operators.remove(0);
        }
        else{
            val =Decimal.valueOf(l_logicValues.get(0).trim());
        }
            
            
        for(Integer valIndex=1; valIndex<l_logicValues.size(); valIndex++){
            String sign = l_operators.get(nextIndex);
            Decimal nextVal = 0;
            
            //empty value indicate minus value
            if(String.isBlank(l_logicValues.get(valIndex))){
                valIndex++;
                nextIndex++;
                nextVal = decimal.valueOf(l_logicValues.get(valIndex).trim()) * (-1);
            }
            else{
                nextVal = decimal.valueOf(l_logicValues.get(valIndex).trim());
            }
            
            
            val = addFixValue(val, nextVal, sign);
            nextIndex++;
        }
        return val;
    }

    private static Decimal addFixValue(
        Decimal currenctVal,
        decimal nextVal,
        String sign){

        Decimal newVal = currenctVal;
            
        if(sign == '+'){
            newVal = nextVal + currenctVal;
        }
        else if(sign == '-'){
            newVal = currenctVal - nextVal;
        }
        else if(sign == '*'){
            newVal = nextVal * currenctVal;
        }
        else if(sign == '/'){
            if(nextVal == 0){
                newVal = 0;
            }
            else{
                newVal =  currenctVal/nextVal;
            }
        }

        return newVal;
    }

}


Some testing...

System.assertEquals(11, StringToNumber.convertStringToNum('5+6'));
System.assertEquals(1, StringToNumber.convertStringToNum('-5+6'));
System.assertEquals(-0.4, StringToNumber.convertStringToNum('-0.8/2'));
System.assertEquals(-18, StringToNumber.convertStringToNum('6*(-3)'));
System.assertEquals(9, StringToNumber.convertStringToNum('6*(-3)/-2'));
System.assertEquals(-29, StringToNumber.convertStringToNum('-2*(6*(-3)/-2+5.5)'));
System.assertEquals(24, StringToNumber.convertStringToNum('(5+(6/2))*3'));
System.assertEquals(-6, StringToNumber.convertStringToNum('(5+(14/-2))*3'));
System.assertEquals(124, StringToNumber.convertStringToNum('(5+(6/2))*3+100'));
System.assertEquals(74, StringToNumber.convertStringToNum('(5+(6/2))*3+(100/2)'));
System.assertEquals(740, StringToNumber.convertStringToNum('((5+(6/2))*3+(100/2))*10'));


4 comments:

  1. Hi, Could you explain to me why this code has the condition: "if(innerExp.charAt(formulaIndex) == 43 || ... ) and where the number 43, 45, 42, 47 come from? By the way, thank you so much for your code. It works well for my case and helps me a lot. ^^

    ReplyDelete
    Replies
    1. Those are characters for arithmetic:
      +, -, *, /

      The code store all of them in list "l_operators", and later it being used to identify the correct action between 2 values. See line:
      String sign = l_operators.get(nextIndex);

      Delete
    2. + The specific codes number are ascii code

      Delete
    3. Oh, I got it. Thank you!

      Delete

Retire of Permission on Profiles

If you are working as a Salesforce admin/developer you've probably heard somewhere that Salesforce is planning to make a significant cha...