MiniExpr

ET This page contains a C program that is a numerical text expression to number (int32, int64, or double) converter. It is prototype for a side-effect-free fast lightweight expr like C function. It includes two extra functions at the end (to test and demonstrate the code: a main, and a timer routine) which would not be included in a user program.

Note source code moved to github https://github.com/rocketship88/expression

Update Note: Since I first posted this here, I've decided that the original approach where the main parser was duplicated twice to handle the 2 integer modes is too messy and once I got the double version to be within 95% as fast, I then removed the two extra versions, and just wrapped the double version with integer conversions. However, the comments in the code still reference the other 2 versions. Also, there are no calls to strtod any longer (a big cpu hog), although there's an older version still there that did use it (if it saw a . in a number).

Update added sqrt function but broke sin, now fixed

Update 3/11/23 added recursion level check (set a #define RECURSION_MAX 10) deepest ()'s allowed or returns an error

It is called with one of 3 sequences

status = evaluate_d_expression(expr, &ansd);
status = evaluate_l_expression(expr, &ansl);
status = evaluate_ll_expression(expr, &ansll);

status will be either 0 for good and the answer will be placed in a variable

char* expr; // input

// output, one of 
double ansd;
int ansl;
long long ansll;

Or if a failure (bad expression) then status will be 2 for unbalanced parens, or 1000+n where n is the character position of the error token.

The two integer versions are just a wrapper around the double version, which then uses lrint and llrint to round to an integer. 

If placed in a file, say expression.c, it can be built on linux using the included main function at the end.

Please NOTE The above 3 functions are declared static in the file, so if you need to call them from another file, just remove the static keyword. When testing inside tcl, it is convenient to just place the code in the file, for example, tclutil.c, and then call them from within the file.

gcc   -O2 -o srctest expression.c  -lm

to enable the debug trace in interactive mode, use -D Debug

gcc   -D Debug O2 -o srctest expression.c  -lm

The trace only is available for mode 0, double. To get a quick Usage help for running timing tests or interactive mode just run the command without any arguments. E.G.

./srctest

It has been incorporated into a tcl build and used as an index expression evaluator and tested using the lindex command. It can process expressions on par with the [expr] (and slightly faster) command as the timings below demonstrate:

#---------------------------------------------------------- braced expr in a proc
proc foo1 {} {
            lindex $::lst [expr {int(abs(sin($::a*3.141592/180))+1)}] 
        }
#----------------------------------------------------------- unbraced, un-expr'd in a proc
proc foo2 {} {
            lindex $::lst        int(abs(sin($::a*3.141592/180))+1)
        }
#----------------------------------------------------------

set ::a 90
#-----------------------------------------------
 % time foo1 1000000
 1.1457018 microseconds per iteration - expr
#-----------------------------------------------

#-----------------------------------------------
 % time foo2 1000000
 0.9298474 microseconds per iteration - mini-expr with decimal numbers and a variable
#-----------------------------------------------

Here are some timings that can be seen by running the program as it's here supplied.

$ time ./extest 0 30,000,000 '-floor(10.01**2.00001*100.33+100.22*(abs(-100.111)))'


0 [./.extest] 
1 [0] mode double
2 [30,000,000] count 
3 [-floor(10.01**2.00001*100.33+100.22*(abs(-100.111)))] expression

i=[29999999] stat=0 -20086 
---end  elapsed: 8009 ms - us per iteration: 0.26697

real    0m8.011s
user    0m7.989s
sys 0m0.012s

vs. with expr inside tcl

% expr {-floor(10.01**2.00001*100.33+100.22*(abs(-100.111)))}
-20086.0
% time {expr {-floor(10.01**2.00001*100.33+100.22*(abs(-100.111)))}} 1000000
0.473333 microseconds per iteration

Tested on an i7-4790k 4ghz inside a linux VM on windows 10.