Powerbuilder – Evaluating Arithmetic Expressions

Posted on Thursday, March 11th, 2010 at 8:22 pm in

Below is a function which allows the user to enter an arithmetic expression and then evaluate it and return the result.  This will work for addition (+), subtraction(-), multiplication(*), division(/), and exponentiation(^).   You can also enter parenthesis to change the order of precedence if needed.  You would call this from where ever you like to validate and convert user entered expressions.

Example:  you need to calculate the area of a circle the user types in.  User would type “3.14*8^2” in the case of a circle with a radius of 8.  Function would return a string of “200.96”

public function string uf_eval_arithmetic_expression (string infix_expr, integer precision);

// convert an expression into a numeric value (string in, number converted to string out)
//
decimal ld_result, ld_operand_stack [], ld_operand1, ld_operand2, ld_temp_result
string ls_infix_str, ls_currchar
string ls_infix_token [], ls_operator_stack [], ls_postfix_token [], ls_token
integer li_stack_top, li_itoken_sub, li_ptoken_sub
integer li_incoming_priority, li_instack_priority
char lc_temp_char
string ls_temp_string
ld_result = 0
//********************************************************************
// first check the syntax of the infix arithmetic expression and put
// each token into the ls_infix_token array
//********************************************************************
ls_infix_str = lefttrim (infix_expr) + "@"
ls_currchar = left (ls_infix_str, 1)
ls_token = ""
DO WHILE 1 = 1
 CHOOSE CASE ls_currchar
  CASE "@"
    exit
  CASE "^", "*", "/", "+", "-", "(", ")"
    li_itoken_sub = li_itoken_sub + 1
    ls_infix_token [li_itoken_sub] = ls_currchar
    ls_infix_str = lefttrim (mid (ls_infix_str, 2))
    ls_currchar = left (ls_infix_str, 1)
  CASE '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.'
    DO WHILE 1 = 1
      ls_token = ls_token + ls_currchar
      ls_infix_str = Mid(ls_infix_str, 2)
      lc_temp_char = Left(ls_infix_str, 1)
      ls_temp_string = Mid(ls_infix_str, 2, 1)
      IF lc_temp_char = " " AND IsNumber(ls_temp_string) THEN
        ls_token = " "
        Exit
      END IF
      ls_infix_str = LeftTrim(ls_infix_str)
      ls_currchar = left (ls_infix_str, 1)
      CHOOSE CASE ls_currchar
        CASE "@", "^", "*", "/", "+", "-", "(", ")"
           exit
        END CHOOSE
    LOOP
    IF isnumber (ls_token) THEN
      li_itoken_sub = li_itoken_sub + 1
      ls_infix_token [li_itoken_sub] = ls_token
      ls_token = ""
    ELSE
      //messagebox ("uf_eval_arithmetic_expression ", "invalid number")
      return (infix_expr)
    END IF
    IF ls_currchar = "@" THEN
      exit
    END IF
  CASE ELSE
    //messagebox ("uf_eval_arithmetic_expression ", "invalid expression")
    return (infix_expr)
 END CHOOSE
LOOP
//********************************************************************
// second convert the infix expression into postfix notation
//********************************************************************
ls_infix_token [li_itoken_sub + 1] = "@"
li_stack_top = 0
li_itoken_sub = 0
li_ptoken_sub = 0
DO WHILE 1 = 1
 li_itoken_sub = li_itoken_sub + 1
 ls_token = ls_infix_token [li_itoken_sub]
 CHOOSE CASE ls_token
   CASE "@"
     DO WHILE li_stack_top > 0
       li_ptoken_sub = li_ptoken_sub + 1
       ls_postfix_token [li_ptoken_sub] = ls_operator_stack [li_stack_top]
       li_stack_top = li_stack_top - 1
     LOOP
     exit
   CASE ")"
     DO WHILE ls_operator_stack [li_stack_top] <> "("
        li_ptoken_sub = li_ptoken_sub + 1
        ls_postfix_token [li_ptoken_sub] = ls_operator_stack [li_stack_top]
        li_stack_top = li_stack_top - 1
     LOOP
     li_stack_top = li_stack_top - 1
   CASE "^", "*", "/", "+", "-", "("
     DO WHILE 1 = 1
       CHOOSE CASE ls_token
         CASE "+", "-"
            li_incoming_priority = 1
         CASE "*", "/"
            li_incoming_priority = 2
         CASE ELSE
            li_incoming_priority = 4
       END CHOOSE
       IF li_stack_top = 0 THEN
          li_instack_priority = 0
       ELSE
         CHOOSE CASE ls_operator_stack [li_stack_top]
           CASE "+", "-"
             li_instack_priority = 1
           CASE "*", "/"
             li_instack_priority = 2
           CASE "^"
             li_instack_priority = 3
           CASE ELSE
             li_instack_priority = 0
           END CHOOSE
       END IF
       IF li_incoming_priority > li_instack_priority THEN
         exit
       END IF
       li_ptoken_sub = li_ptoken_sub + 1
       ls_postfix_token [li_ptoken_sub] = ls_operator_stack [li_stack_top]
       li_stack_top = li_stack_top - 1
     LOOP
     li_stack_top = li_stack_top + 1
     ls_operator_stack [li_stack_top] = ls_token
 CASE ELSE  /* ls_token is an operand */
    li_ptoken_sub = li_ptoken_sub + 1
    ls_postfix_token [li_ptoken_sub] = ls_token
 END CHOOSE
LOOP
//********************************************************************
// third evaluate the postfix arithmetic expression. each token is
// stored as a string occurance in the ls_postfix_token array
//********************************************************************
ls_postfix_token [li_ptoken_sub + 1] = "@"
ld_result = 0
li_stack_top = 0
li_ptoken_sub = 0
DO WHILE 1 = 1
 li_ptoken_sub = li_ptoken_sub + 1
 ls_token = ls_postfix_token [li_ptoken_sub]
 CHOOSE CASE ls_token
   CASE "@"
     IF li_stack_top = 0 THEN
       return ""
     END IF
     ld_result = round (ld_operand_stack [li_stack_top], precision)
     return (string(ld_result, "0.####"))
   CASE "^", "*", "/", "+", "-"
      IF ls_token = "/" and ld_operand_stack [li_stack_top] = 0 THEN
        //messagebox ("fx_eval_arith_expr", "divide by zero error")
        return (infix_expr)
      END IF
      IF li_stack_top < 2 THEN
         //messagebox ("uf_eval_arithmetic_expression ", "invalid expression")
         return (infix_expr)
      END IF
      ld_operand1 = ld_operand_stack [li_stack_top - 1]
      ld_operand2 = ld_operand_stack [li_stack_top]
      CHOOSE CASE ls_token
        CASE "+"
          ld_temp_result = ld_operand1 + ld_operand2
        CASE "-"
          ld_temp_result = ld_operand1 - ld_operand2
        CASE "*"
          ld_temp_result = ld_operand1 * ld_operand2
        CASE "/"
          ld_temp_result = ld_operand1 / ld_operand2
        CASE ELSE
          ld_temp_result = ld_operand1 ^ ld_operand2
      END CHOOSE
      ld_operand_stack [li_stack_top - 1] = ld_temp_result
      li_stack_top = li_stack_top - 1
  CASE ELSE  /* have an operand so add to stack */
    li_stack_top = li_stack_top + 1
    ld_operand_stack [li_stack_top] = dec (ls_token)
 END CHOOSE
LOOP

Updated March 2021

Top