From 2ed8c051bbd34637a08ce486615d1bcb71fd9975 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Mon, 2 Nov 2020 17:05:16 +0100 Subject: [PATCH 1/4] This PR addresses the following issues: Replaces unsafe usages of const void* with appropriate pointer types for data and functions, making the code maximum portable, safe, and easier to maintain. Detail: Some compilers does not allow casting between data and function pointers, even void* (pointers may have different sizes). Library now compilable both in c11 and c++20. Useful if you need to customize the code with a c++ allocator or use it in projects where c++ source code is required. Broadens usability of the library. For c++, the PR forces the correct overloaded math functions to be picked during function pointer assignments and comparisons, e.g. like pow and fmod. Simplifies casting code in te_eval() by adding a few macros, can extend number of function parameters cleanly. --- tinyexpr.c | 173 ++++++++++++++++++++++++++++------------------------- tinyexpr.h | 15 +++-- 2 files changed, 101 insertions(+), 87 deletions(-) diff --git a/tinyexpr.c b/tinyexpr.c index 4facfbc..a7bec7f 100755 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -50,8 +50,6 @@ For log = natural log uncomment the next line. */ #endif -typedef double (*te_fun2)(double, double); - enum { TOK_NULL = TE_CLOSURE7+1, TOK_ERROR, TOK_END, TOK_SEP, TOK_OPEN, TOK_CLOSE, TOK_NUMBER, TOK_VARIABLE, TOK_INFIX @@ -65,8 +63,8 @@ typedef struct state { const char *start; const char *next; int type; - union {double value; const double *bound; const void *function;}; - void *context; + union {double value; const double *bound; te_fun0 fun0; te_fun1 fun1; te_fun2 fun2;}; + te_expr *context; const te_variable *lookup; int lookup_len; @@ -79,13 +77,18 @@ typedef struct state { #define IS_FUNCTION(TYPE) (((TYPE) & TE_FUNCTION0) != 0) #define IS_CLOSURE(TYPE) (((TYPE) & TE_CLOSURE0) != 0) #define ARITY(TYPE) ( ((TYPE) & (TE_FUNCTION0 | TE_CLOSURE0)) ? ((TYPE) & 0x00000007) : 0 ) +#ifdef __cplusplus +#include +#define NEW_EXPR(type, ...) new_expr((type), &*std::initializer_list({__VA_ARGS__}).begin()) +#else #define NEW_EXPR(type, ...) new_expr((type), (const te_expr*[]){__VA_ARGS__}) +#endif -static te_expr *new_expr(const int type, const te_expr *parameters[]) { +static te_expr *new_expr(const int type, const te_expr * const parameters[]) { const int arity = ARITY(type); const int psize = sizeof(void*) * arity; const int size = (sizeof(te_expr) - sizeof(void*)) + psize + (IS_CLOSURE(type) ? sizeof(void*) : 0); - te_expr *ret = malloc(size); + te_expr *ret = (te_expr *) malloc(size); memset(ret, 0, size); if (arity && parameters) { memcpy(ret->parameters, parameters, psize); @@ -151,34 +154,34 @@ static double npr(double n, double r) {return ncr(n, r) * fac(r);} static const te_variable functions[] = { /* must be in alphabetical order */ - {"abs", fabs, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"acos", acos, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"asin", asin, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"atan", atan, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"atan2", atan2, TE_FUNCTION2 | TE_FLAG_PURE, 0}, - {"ceil", ceil, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"cos", cos, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"cosh", cosh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"e", e, TE_FUNCTION0 | TE_FLAG_PURE, 0}, - {"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"fac", fac, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"ln", log, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {.name="abs", .fun1=fabs, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="acos", .fun1=acos, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="asin", .fun1=asin, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="atan", .fun1=atan, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="atan2", .fun2=atan2, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, + {.name="ceil", .fun1=ceil, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="cos", .fun1=cos, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="cosh", .fun1=cosh, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="e", .fun0=e, .type=TE_FUNCTION0 | TE_FLAG_PURE, .context=0}, + {.name="exp", .fun1=exp, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="fac", .fun1=fac, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="floor", .fun1=floor, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="ln", .fun1=log, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, #ifdef TE_NAT_LOG - {"log", log, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {.name="log", .fun1=log, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, #else - {"log", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {.name="log", .fun1=log10, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, #endif - {"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"ncr", ncr, TE_FUNCTION2 | TE_FLAG_PURE, 0}, - {"npr", npr, TE_FUNCTION2 | TE_FLAG_PURE, 0}, - {"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE, 0}, - {"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE, 0}, - {"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"sinh", sinh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"sqrt", sqrt, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"tan", tan, TE_FUNCTION1 | TE_FLAG_PURE, 0}, - {"tanh", tanh, TE_FUNCTION1 | TE_FLAG_PURE, 0}, + {.name="log10", .fun1=log10, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="ncr", .fun2=ncr, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, + {.name="npr", .fun2=npr, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, + {.name="pi", .fun0=pi, .type=TE_FUNCTION0 | TE_FLAG_PURE, .context=0}, + {.name="pow", .fun2=pow, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, + {.name="sin", .fun1=sin, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="sinh", .fun1=sinh, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="sqrt", .fun1=sqrt, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="tan", .fun1=tan, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="tanh", .fun1=tanh, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {0, 0, 0, 0} }; @@ -267,7 +270,7 @@ void next_token(state *s) { case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: /* Falls through. */ case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: /* Falls through. */ s->type = var->type; - s->function = var->address; + s->fun1 = var->fun1; break; } } @@ -275,12 +278,12 @@ void next_token(state *s) { } else { /* Look for an operator or special character. */ switch (s->next++[0]) { - case '+': s->type = TOK_INFIX; s->function = add; break; - case '-': s->type = TOK_INFIX; s->function = sub; break; - case '*': s->type = TOK_INFIX; s->function = mul; break; - case '/': s->type = TOK_INFIX; s->function = divide; break; - case '^': s->type = TOK_INFIX; s->function = pow; break; - case '%': s->type = TOK_INFIX; s->function = fmod; break; + case '+': s->type = TOK_INFIX; s->fun2 = add; break; + case '-': s->type = TOK_INFIX; s->fun2 = sub; break; + case '*': s->type = TOK_INFIX; s->fun2 = mul; break; + case '/': s->type = TOK_INFIX; s->fun2 = divide; break; + case '^': s->type = TOK_INFIX; s->fun2 = pow; break; + case '%': s->type = TOK_INFIX; s->fun2 = fmod; break; case '(': s->type = TOK_OPEN; break; case ')': s->type = TOK_CLOSE; break; case ',': s->type = TOK_SEP; break; @@ -318,7 +321,7 @@ static te_expr *base(state *s) { case TE_FUNCTION0: case TE_CLOSURE0: ret = new_expr(s->type, 0); - ret->function = s->function; + ret->fun0 = s->fun0; if (IS_CLOSURE(s->type)) ret->parameters[0] = s->context; next_token(s); if (s->type == TOK_OPEN) { @@ -334,7 +337,7 @@ static te_expr *base(state *s) { case TE_FUNCTION1: case TE_CLOSURE1: ret = new_expr(s->type, 0); - ret->function = s->function; + ret->fun1 = s->fun1; if (IS_CLOSURE(s->type)) ret->parameters[1] = s->context; next_token(s); ret->parameters[0] = power(s); @@ -347,7 +350,7 @@ static te_expr *base(state *s) { arity = ARITY(s->type); ret = new_expr(s->type, 0); - ret->function = s->function; + ret->fun2 = s->fun2; if (IS_CLOSURE(s->type)) ret->parameters[arity] = s->context; next_token(s); @@ -395,8 +398,8 @@ static te_expr *base(state *s) { static te_expr *power(state *s) { /* = {("-" | "+")} */ int sign = 1; - while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { - if (s->function == sub) sign = -sign; + while (s->type == TOK_INFIX && (s->fun2 == add || s->fun2 == sub)) { + if (s->fun2 == sub) sign = -sign; next_token(s); } @@ -406,7 +409,7 @@ static te_expr *power(state *s) { ret = base(s); } else { ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, base(s)); - ret->function = negate; + ret->fun1 = negate; } return ret; @@ -419,7 +422,7 @@ static te_expr *factor(state *s) { int neg = 0; - if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { + if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->fun1 == negate) { te_expr *se = ret->parameters[0]; free(ret); ret = se; @@ -427,27 +430,27 @@ static te_expr *factor(state *s) { } te_expr *insertion = 0; - - while (s->type == TOK_INFIX && (s->function == pow)) { - te_fun2 t = s->function; + te_fun2 dpow = pow; /* resolve overloading for g++ */ + while (s->type == TOK_INFIX && (s->fun2 == dpow)) { + te_fun2 t = s->fun2; next_token(s); if (insertion) { /* Make exponentiation go right-to-left. */ te_expr *insert = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, insertion->parameters[1], power(s)); - insert->function = t; + insert->fun2 = t; insertion->parameters[1] = insert; insertion = insert; } else { ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); - ret->function = t; + ret->fun2 = t; insertion = ret; } } if (neg) { ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret); - ret->function = negate; + ret->fun1 = negate; } return ret; @@ -456,12 +459,12 @@ static te_expr *factor(state *s) { static te_expr *factor(state *s) { /* = {"^" } */ te_expr *ret = power(s); - - while (s->type == TOK_INFIX && (s->function == pow)) { - te_fun2 t = s->function; + te_fun2 dpow = pow; /* resolve c++ overloading */ + while (s->type == TOK_INFIX && (s->fun2 == dpow)) { + te_fun2 t = s->fun2; next_token(s); ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); - ret->function = t; + ret->fun2 = t; } return ret; @@ -473,12 +476,12 @@ static te_expr *factor(state *s) { static te_expr *term(state *s) { /* = {("*" | "/" | "%") } */ te_expr *ret = factor(s); - - while (s->type == TOK_INFIX && (s->function == mul || s->function == divide || s->function == fmod)) { - te_fun2 t = s->function; + te_fun2 dmod = fmod; /* resolve c++ overloading */ + while (s->type == TOK_INFIX && (s->fun2 == mul || s->fun2 == divide || s->fun2 == dmod)) { + te_fun2 t = s->fun2; next_token(s); ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, factor(s)); - ret->function = t; + ret->fun2 = t; } return ret; @@ -489,11 +492,11 @@ static te_expr *expr(state *s) { /* = {("+" | "-") } */ te_expr *ret = term(s); - while (s->type == TOK_INFIX && (s->function == add || s->function == sub)) { - te_fun2 t = s->function; + while (s->type == TOK_INFIX && (s->fun2 == add || s->fun2 == sub)) { + te_fun2 t = s->fun2; next_token(s); ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, term(s)); - ret->function = t; + ret->fun2 = t; } return ret; @@ -507,15 +510,23 @@ static te_expr *list(state *s) { while (s->type == TOK_SEP) { next_token(s); ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, expr(s)); - ret->function = comma; + ret->fun2 = comma; } return ret; } -#define TE_FUN(...) ((double(*)(__VA_ARGS__))n->function) +#define TE_FUN(...) ((double(*)(__VA_ARGS__))n->fun1) #define M(e) te_eval(n->parameters[e]) +#define D(e) double +#define TE_R1(x) x(0) +#define TE_R2(x) TE_R1(x), x(1) +#define TE_R3(x) TE_R2(x), x(2) +#define TE_R4(x) TE_R3(x), x(3) +#define TE_R5(x) TE_R4(x), x(4) +#define TE_R6(x) TE_R5(x), x(5) +#define TE_R7(x) TE_R6(x), x(6) double te_eval(const te_expr *n) { @@ -528,28 +539,28 @@ double te_eval(const te_expr *n) { case TE_FUNCTION0: case TE_FUNCTION1: case TE_FUNCTION2: case TE_FUNCTION3: case TE_FUNCTION4: case TE_FUNCTION5: case TE_FUNCTION6: case TE_FUNCTION7: switch(ARITY(n->type)) { - case 0: return TE_FUN(void)(); - case 1: return TE_FUN(double)(M(0)); - case 2: return TE_FUN(double, double)(M(0), M(1)); - case 3: return TE_FUN(double, double, double)(M(0), M(1), M(2)); - case 4: return TE_FUN(double, double, double, double)(M(0), M(1), M(2), M(3)); - case 5: return TE_FUN(double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4)); - case 6: return TE_FUN(double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5)); - case 7: return TE_FUN(double, double, double, double, double, double, double)(M(0), M(1), M(2), M(3), M(4), M(5), M(6)); + case 0: return n->fun0(); + case 1: return n->fun1( M(0) ); + case 2: return n->fun2( M(0), M(1) ); + case 3: return TE_FUN(TE_R3(D))( TE_R3(M) ); + case 4: return TE_FUN(TE_R4(D))( TE_R4(M) ); + case 5: return TE_FUN(TE_R5(D))( TE_R5(M) ); + case 6: return TE_FUN(TE_R6(D))( TE_R6(M) ); + case 7: return TE_FUN(TE_R7(D))( TE_R7(M) ); default: return NAN; } case TE_CLOSURE0: case TE_CLOSURE1: case TE_CLOSURE2: case TE_CLOSURE3: case TE_CLOSURE4: case TE_CLOSURE5: case TE_CLOSURE6: case TE_CLOSURE7: switch(ARITY(n->type)) { - case 0: return TE_FUN(void*)(n->parameters[0]); - case 1: return TE_FUN(void*, double)(n->parameters[1], M(0)); - case 2: return TE_FUN(void*, double, double)(n->parameters[2], M(0), M(1)); - case 3: return TE_FUN(void*, double, double, double)(n->parameters[3], M(0), M(1), M(2)); - case 4: return TE_FUN(void*, double, double, double, double)(n->parameters[4], M(0), M(1), M(2), M(3)); - case 5: return TE_FUN(void*, double, double, double, double, double)(n->parameters[5], M(0), M(1), M(2), M(3), M(4)); - case 6: return TE_FUN(void*, double, double, double, double, double, double)(n->parameters[6], M(0), M(1), M(2), M(3), M(4), M(5)); - case 7: return TE_FUN(void*, double, double, double, double, double, double, double)(n->parameters[7], M(0), M(1), M(2), M(3), M(4), M(5), M(6)); + case 0: return ((double(*)(te_expr*))n->fun1)( n->parameters[0] ); + case 1: return TE_FUN(te_expr*, TE_R1(D))( n->parameters[1], TE_R1(M) ); + case 2: return TE_FUN(te_expr*, TE_R2(D))( n->parameters[2], TE_R2(M) ); + case 3: return TE_FUN(te_expr*, TE_R3(D))( n->parameters[3], TE_R3(M) ); + case 4: return TE_FUN(te_expr*, TE_R4(D))( n->parameters[4], TE_R4(M) ); + case 5: return TE_FUN(te_expr*, TE_R5(D))( n->parameters[5], TE_R5(M) ); + case 6: return TE_FUN(te_expr*, TE_R6(D))( n->parameters[6], TE_R6(M) ); + case 7: return TE_FUN(te_expr*, TE_R7(D))( n->parameters[7], TE_R7(M) ); default: return NAN; } @@ -558,7 +569,7 @@ double te_eval(const te_expr *n) { } -#undef TE_FUN +#undef D #undef M static void optimize(te_expr *n) { diff --git a/tinyexpr.h b/tinyexpr.h index c9afa6c..19073fb 100644 --- a/tinyexpr.h +++ b/tinyexpr.h @@ -29,13 +29,16 @@ #ifdef __cplusplus extern "C" { #endif +/* Private */ +typedef double (*te_fun0)(void); +typedef double (*te_fun1)(double); +typedef double (*te_fun2)(double, double); - - +/* Public */ typedef struct te_expr { int type; - union {double value; const double *bound; const void *function;}; - void *parameters[1]; + union {double value; const double *bound; te_fun0 fun0; te_fun1 fun1; te_fun2 fun2;}; + struct te_expr *parameters[1]; } te_expr; @@ -53,9 +56,9 @@ enum { typedef struct te_variable { const char *name; - const void *address; + union {const double *address; te_fun0 fun0; te_fun1 fun1; te_fun2 fun2;}; int type; - void *context; + te_expr *context; } te_variable; From 191cc0c20c4da998f11d6c540b5a14fb1c99aaec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Mon, 2 Nov 2020 19:16:17 +0100 Subject: [PATCH 2/4] type-safe function assignment. --- example3.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example3.c b/example3.c index 80fe9da..e8c29e9 100644 --- a/example3.c +++ b/example3.c @@ -12,7 +12,7 @@ double my_sum(double a, double b) { int main(int argc, char *argv[]) { te_variable vars[] = { - {"mysum", my_sum, TE_FUNCTION2} + {.name="mysum", .fun2=my_sum, .type=TE_FUNCTION2} }; const char *expression = "mysum(5, 6)"; From cfa448b34498fa609bf130e287a901e58b651e09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Wed, 27 Jan 2021 16:08:07 +0100 Subject: [PATCH 3/4] Updated fac() to use tgamma() so that it works for real number as well. --- tinyexpr.c | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/tinyexpr.c b/tinyexpr.c index a7bec7f..634c663 100755 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -122,20 +122,8 @@ void te_free(te_expr *n) { static double pi(void) {return 3.14159265358979323846;} static double e(void) {return 2.71828182845904523536;} -static double fac(double a) {/* simplest version of fac */ - if (a < 0.0) - return NAN; - if (a > UINT_MAX) - return INFINITY; - unsigned int ua = (unsigned int)(a); - unsigned long int result = 1, i; - for (i = 1; i <= ua; i++) { - if (i > ULONG_MAX / result) - return INFINITY; - result *= i; - } - return (double)result; -} +static double fac(double a) {return tgamma(a + 1);} + static double ncr(double n, double r) { if (n < 0.0 || r < 0.0 || n < r) return NAN; if (n > UINT_MAX || r > UINT_MAX) return INFINITY; From 5211d6e56e50f029c023d9f436cc26afdc0771e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tyge=20L=C3=B8vset?= Date: Wed, 27 Jan 2021 23:25:14 +0100 Subject: [PATCH 4/4] Replaced ^ with ** operator: always right-associative. Added gamma(), cbrt(), log2(), gcd() math functions. Removed ln(): now log(), log2() and log10(). --- README.md | 59 ++++++++++++++----------------------------- example.c | 4 +-- tinyexpr.c | 74 +++++++++++++++++++++++++----------------------------- 3 files changed, 55 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 8db7611..565a5c0 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,11 @@ the standard C math functions and runtime binding of variables. ## Features - **C99 with no dependencies**. +- Can also be compiled with C++20. - Single source file and header file. - Simple and fast. - Implements standard operators precedence. -- Exposes standard C math functions (sin, sqrt, ln, etc.). +- Exposes standard C math functions (sin, sqrt, log, etc.). - Can add custom functions and variables easily. - Can bind variables at eval-time. - Released under the zlib license - free for nearly any use. @@ -101,7 +102,7 @@ After you're finished, make sure to call `te_free()`. int err; /* Compile the expression with variables. */ - te_expr *expr = te_compile("sqrt(x^2+y^2)", vars, 2, &err); + te_expr *expr = te_compile("sqrt(x**2+y**2)", vars, 2, &err); if (expr) { x = 3; y = 4; @@ -163,20 +164,20 @@ line. It also does error checking and binds the variables `x` and `y` to *3* and This produces the output: - - $ example2 "sqrt(x^2+y2)" +``` + $ example2 "sqrt(x**2+y2)" Evaluating: - sqrt(x^2+y2) + sqrt(x**2+y2) ^ Error near here - $ example2 "sqrt(x^2+y^2)" + $ example2 "sqrt(x**2+y**2)" Evaluating: - sqrt(x^2+y^2) + sqrt(x**2+y**2) Result: 5.000000 - +``` ## Binding to Custom Functions @@ -228,12 +229,12 @@ Here is some example performance numbers taken from the included **benchmark.c** program: | Expression | te_eval time | native C time | slowdown | -| :------------- |-------------:| -----:|----:| -| sqrt(a^1.5+a^2.5) | 15,641 ms | 14,478 ms | 8% slower | -| a+5 | 765 ms | 563 ms | 36% slower | -| a+(5*2) | 765 ms | 563 ms | 36% slower | -| (a+5)*2 | 1422 ms | 563 ms | 153% slower | -| (1/(a+1)+2/(a+2)+3/(a+3)) | 5,516 ms | 1,266 ms | 336% slower | +| :---------------------------|----------:|----------:|------------:| +| `sqrt(a**1.5+a**2.5)` | 15,641 ms | 14,478 ms | 8% slower | +| `a+5` | 765 ms | 563 ms | 36% slower | +| `a+(5*2)` | 765 ms | 563 ms | 36% slower | +| `(a+5)*2` | 1422 ms | 563 ms | 153% slower | +| `(1/(a+1)+2/(a+2)+3/(a+3))` | 5,516 ms | 1,266 ms | 336% slower | @@ -244,7 +245,7 @@ TinyExpr parses the following grammar: = {"," } = {("+" | "-") } = {("*" | "/" | "%") } - = {"^" } + = {"*\*" } = {("-" | "+")} = | @@ -265,47 +266,26 @@ for *0.5*) ## Functions supported TinyExpr supports addition (+), subtraction/negation (-), multiplication (\*), -division (/), exponentiation (^) and modulus (%) with the normal operator +division (/), exponentiation (*\*) and modulus (%) with the normal operator precedence (the one exception being that exponentiation is evaluated left-to-right, but this can be changed - see below). The following C math functions are also supported: -- abs (calls to *fabs*), acos, asin, atan, atan2, ceil, cos, cosh, exp, floor, ln (calls to *log*), log (calls to *log10* by default, see below), log10, pow, sin, sinh, sqrt, tan, tanh +- abs, acos, asin, atan, atan2, cbrt, ceil, cos, cosh, exp, floor, gamma, log, log2, log10, pow, sin, sinh, sqrt, tan, tanh The following functions are also built-in and provided by TinyExpr: - fac (factorials e.g. `fac 5` == 120) - ncr (combinations e.g. `ncr(6,2)` == 15) - npr (permutations e.g. `npr(6,2)` == 30) +- gcd (common denominator e.g. `gcd(30, 50)` == 10) Also, the following constants are available: - `pi`, `e` -## Compile-time options - - -By default, TinyExpr does exponentiation from left to right. For example: - -`a^b^c == (a^b)^c` and `-a^b == (-a)^b` - -This is by design. It's the way that spreadsheets do it (e.g. Excel, Google Sheets). - - -If you would rather have exponentiation work from right to left, you need to -define `TE_POW_FROM_RIGHT` when compiling `tinyexpr.c`. There is a -commented-out define near the top of that file. With this option enabled, the -behaviour is: - -`a^b^c == a^(b^c)` and `-a^b == -(a^b)` - -That will match how many scripting languages do it (e.g. Python, Ruby). - -Also, if you'd like `log` to default to the natural log instead of `log10`, -then you can define `TE_NAT_LOG`. - ## Hints - All functions/types start with the letters *te*. @@ -316,4 +296,3 @@ then you can define `TE_NAT_LOG`. parentheses are important, because TinyExpr will not change the order of evaluation. If you instead compiled "x+1+5" TinyExpr will insist that "1" is added to "x" first, and "5" is added the result second. - diff --git a/example.c b/example.c index 040093f..b9a182e 100644 --- a/example.c +++ b/example.c @@ -1,9 +1,9 @@ -#include "tinyexpr.h" +#include "tinyexpr.c" #include int main(int argc, char *argv[]) { - const char *c = "sqrt(5^2+7^2+11^2+(8-2)^2)"; + const char *c = "sqrt(5**2 * 2 + 7**2 + 11**2 + (8 - 2)**2)"; double r = te_interp(c, 0); printf("The expression:\n\t%s\nevaluates to:\n\t%f\n", c, r); return 0; diff --git a/tinyexpr.c b/tinyexpr.c index 634c663..42ffb3f 100755 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -22,17 +22,9 @@ * 3. This notice may not be removed or altered from any source distribution. */ -/* COMPILE TIME OPTIONS */ - /* Exponentiation associativity: -For a^b^c = (a^b)^c and -a^b = (-a)^b do nothing. -For a^b^c = a^(b^c) and -a^b = -(a^b) uncomment the next line.*/ -/* #define TE_POW_FROM_RIGHT */ - -/* Logarithms -For log = base 10 log do nothing -For log = natural log uncomment the next line. */ -/* #define TE_NAT_LOG */ + a**b**c = a**(b**c) and -a**b = -(a**b) +*/ #include "tinyexpr.h" #include @@ -122,9 +114,12 @@ void te_free(te_expr *n) { static double pi(void) {return 3.14159265358979323846;} static double e(void) {return 2.71828182845904523536;} -static double fac(double a) {return tgamma(a + 1);} -static double ncr(double n, double r) { +static double fac(double a) { + return tgamma(a + 1); +} + +static double ncr(double n, double r) { // combinations if (n < 0.0 || r < 0.0 || n < r) return NAN; if (n > UINT_MAX || r > UINT_MAX) return INFINITY; unsigned long int un = (unsigned int)(n), ur = (unsigned int)(r), i; @@ -138,7 +133,23 @@ static double ncr(double n, double r) { } return result; } -static double npr(double n, double r) {return ncr(n, r) * fac(r);} + +static double npr(double n, double r) { // permutations + return ncr(n, r) * fac(r); +} + +typedef unsigned long long te_ull; + +static double gcd(double x, double y) { + unsigned long long a = (unsigned int)(x), b = (unsigned int)(y), r; + while (b > 0) { + r = a % b; + a = b; + b = r; + } + return (double) a; +} + static const te_variable functions[] = { /* must be in alphabetical order */ @@ -147,20 +158,19 @@ static const te_variable functions[] = { {.name="asin", .fun1=asin, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="atan", .fun1=atan, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="atan2", .fun2=atan2, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, + {.name="cbrt", .fun1=cbrt, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="ceil", .fun1=ceil, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="cos", .fun1=cos, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="cosh", .fun1=cosh, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="e", .fun0=e, .type=TE_FUNCTION0 | TE_FLAG_PURE, .context=0}, {.name="exp", .fun1=exp, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, - {.name="fac", .fun1=fac, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="fac", . fun1=fac, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="floor", .fun1=floor, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, - {.name="ln", .fun1=log, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, -#ifdef TE_NAT_LOG + {.name="gamma", .fun1=tgamma,.type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="gcd", .fun2=gcd, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, {.name="log", .fun1=log, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, -#else - {.name="log", .fun1=log10, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, -#endif {.name="log10", .fun1=log10, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, + {.name="log2", .fun1=log2, .type=TE_FUNCTION1 | TE_FLAG_PURE, .context=0}, {.name="ncr", .fun2=ncr, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, {.name="npr", .fun2=npr, .type=TE_FUNCTION2 | TE_FLAG_PURE, .context=0}, {.name="pi", .fun0=pi, .type=TE_FUNCTION0 | TE_FLAG_PURE, .context=0}, @@ -268,9 +278,11 @@ void next_token(state *s) { switch (s->next++[0]) { case '+': s->type = TOK_INFIX; s->fun2 = add; break; case '-': s->type = TOK_INFIX; s->fun2 = sub; break; - case '*': s->type = TOK_INFIX; s->fun2 = mul; break; + case '*': + if (*s->next=='*') {++s->next; s->fun2 = pow;} + else s->fun2 = mul; + s->type = TOK_INFIX; break; case '/': s->type = TOK_INFIX; s->fun2 = divide; break; - case '^': s->type = TOK_INFIX; s->fun2 = pow; break; case '%': s->type = TOK_INFIX; s->fun2 = fmod; break; case '(': s->type = TOK_OPEN; break; case ')': s->type = TOK_CLOSE; break; @@ -403,9 +415,8 @@ static te_expr *power(state *s) { return ret; } -#ifdef TE_POW_FROM_RIGHT static te_expr *factor(state *s) { - /* = {"^" } */ + /* = {"**" } */ te_expr *ret = power(s); int neg = 0; @@ -443,23 +454,6 @@ static te_expr *factor(state *s) { return ret; } -#else -static te_expr *factor(state *s) { - /* = {"^" } */ - te_expr *ret = power(s); - te_fun2 dpow = pow; /* resolve c++ overloading */ - while (s->type == TOK_INFIX && (s->fun2 == dpow)) { - te_fun2 t = s->fun2; - next_token(s); - ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); - ret->fun2 = t; - } - - return ret; -} -#endif - - static te_expr *term(state *s) { /* = {("*" | "/" | "%") } */