From af6c47ef2835e6e258a000a4cbe40e6a1dbc5bd6 Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Mon, 22 Aug 2016 21:20:28 -0500 Subject: [PATCH 1/4] Added option TE_POW_FROM_RIGHT --- test.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ tinyexpr.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/test.c b/test.c index 59941f0..21d270f 100644 --- a/test.c +++ b/test.c @@ -32,6 +32,10 @@ typedef struct { double answer; } test_case; +typedef struct { + const char *expr1; + const char *expr2; +} test_equ; @@ -517,6 +521,52 @@ void test_optimize() { } } +void test_pow() { +#ifdef TE_POW_FROM_RIGHT + test_equ cases[] = { + {"2^3^4", "2^(3^4)"}, + {"2^1.1^1.2^1.3", "2^(1.1^(1.2^1.3))"}, + {"-a^b", "-(a^b)"}, + {"-a^-b", "-(a^-b)"} + }; +#else + test_equ cases[] = { + {"2^3^4", "(2^3)^4"}, + {"2^1.1^1.2^1.3", "((2^1.1)^1.2)^1.3"}, + {"-a^b", "(-a)^b"}, + {"-a^-b", "(-a)^(-b)"} + }; +#endif + + double a = 2, b = 3; + + te_variable lookup[] = { + {"a", &a}, + {"b", &b} + }; + + int i; + for (i = 0; i < sizeof(cases) / sizeof(test_equ); ++i) { + const char *expr1 = cases[i].expr1; + const char *expr2 = cases[i].expr2; + + te_expr *ex1 = te_compile(expr1, lookup, sizeof(lookup)/sizeof(te_variable), 0); + te_expr *ex2 = te_compile(expr2, lookup, sizeof(lookup)/sizeof(te_variable), 0); + + lok(ex1); + lok(ex2); + + double r1 = te_eval(ex1); + double r2 = te_eval(ex2); + + fflush(stdout); + lfequal(r1, r2); + + te_free(ex1); + te_free(ex2); + } + +} int main(int argc, char *argv[]) { @@ -528,6 +578,7 @@ int main(int argc, char *argv[]) lrun("Dynamic", test_dynamic); lrun("Closure", test_closure); lrun("Optimize", test_optimize); + lrun("Pow", test_pow); lresults(); return lfails != 0; diff --git a/tinyexpr.c b/tinyexpr.c index 0246580..9021cab 100644 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -22,6 +22,14 @@ * 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 */ + + #include "tinyexpr.h" #include #include @@ -357,7 +365,48 @@ 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; + te_expr *insertion = 0; + + if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { + te_expr *se = ret->parameters[0]; + if (se->type != TE_CONSTANT) { + free(ret); + ret = se; + neg = 1; + } + } + + while (s->type == TOK_INFIX && (s->function == pow)) { + te_fun2 t = s->function; + 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; + insertion->parameters[1] = insert; + insertion = insert; + } else { + ret = NEW_EXPR(TE_FUNCTION2 | TE_FLAG_PURE, ret, power(s)); + ret->function = t; + insertion = ret; + } + } + + if (neg) { + ret = NEW_EXPR(TE_FUNCTION1 | TE_FLAG_PURE, ret); + ret->function = negate; + } + + return ret; +} +#else static te_expr *factor(state *s) { /* = {"^" } */ te_expr *ret = power(s); @@ -371,6 +420,8 @@ static te_expr *factor(state *s) { return ret; } +#endif + static te_expr *term(state *s) { From f70b786ec27fcd589700f51a0e86d3b2770576c9 Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Mon, 22 Aug 2016 21:23:16 -0500 Subject: [PATCH 2/4] Added TE_NAT_LOG option. --- tinyexpr.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tinyexpr.c b/tinyexpr.c index 9021cab..f9b9015 100644 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -29,6 +29,10 @@ 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 */ #include "tinyexpr.h" #include @@ -124,7 +128,12 @@ static const te_variable functions[] = { {"exp", exp, TE_FUNCTION1 | TE_FLAG_PURE}, {"floor", floor, TE_FUNCTION1 | TE_FLAG_PURE}, {"ln", log, TE_FUNCTION1 | TE_FLAG_PURE}, +#ifdef TE_NAT_LOG + {"log", log, TE_FUNCTION1 | TE_FLAG_PURE}, +#else {"log", log10, TE_FUNCTION1 | TE_FLAG_PURE}, +#endif + {"log10", log10, TE_FUNCTION1 | TE_FLAG_PURE}, {"pi", pi, TE_FUNCTION0 | TE_FLAG_PURE}, {"pow", pow, TE_FUNCTION2 | TE_FLAG_PURE}, {"sin", sin, TE_FUNCTION1 | TE_FLAG_PURE}, From b4310cc55cbf09d55de8a0504bbef28ee3684faa Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Mon, 22 Aug 2016 22:29:33 -0500 Subject: [PATCH 3/4] Added section about compile-time options. --- README.md | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 90d0ca6..be61141 100644 --- a/README.md +++ b/README.md @@ -258,17 +258,39 @@ Valid variable names are any combination of the lower case letters *a* through TinyExpr supports addition (+), subtraction/negation (-), multiplication (\*), division (/), exponentiation (^) and modulus (%) with the normal operator precedence (the one exception being that exponentiation is evaluated -left-to-right). +left-to-right, but this can be changed - see below). In addition, 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*), pow, sin, sinh, sqrt, tan, tanh +- 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 Also, the following constants are available: - `pi`, `e` +##Compile-time options + + +By default, TinyExpr does exponentation 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 exponentation 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*. From 2d58ae9fb24da93e5810af4eb4bf2ea0eb1cacbb Mon Sep 17 00:00:00 2001 From: Lewis Van Winkle Date: Tue, 23 Aug 2016 15:05:59 -0500 Subject: [PATCH 4/4] Changed the way POW_FROM_RIGHT works with negative constants. --- test.c | 6 ++++++ tinyexpr.c | 8 +++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/test.c b/test.c index 21d270f..cd5779c 100644 --- a/test.c +++ b/test.c @@ -525,6 +525,10 @@ void test_pow() { #ifdef TE_POW_FROM_RIGHT test_equ cases[] = { {"2^3^4", "2^(3^4)"}, + {"-2^2", "-(2^2)"}, + {"-(2)^2", "-(2^2)"}, + {"-(2*1)^2", "-(2^2)"}, + {"-2^2", "-4"}, {"2^1.1^1.2^1.3", "2^(1.1^(1.2^1.3))"}, {"-a^b", "-(a^b)"}, {"-a^-b", "-(a^-b)"} @@ -532,6 +536,8 @@ void test_pow() { #else test_equ cases[] = { {"2^3^4", "(2^3)^4"}, + {"-2^2", "(-2)^2"}, + {"-2^2", "4"}, {"2^1.1^1.2^1.3", "((2^1.1)^1.2)^1.3"}, {"-a^b", "(-a)^b"}, {"-a^-b", "(-a)^(-b)"} diff --git a/tinyexpr.c b/tinyexpr.c index f9b9015..58717c2 100644 --- a/tinyexpr.c +++ b/tinyexpr.c @@ -384,11 +384,9 @@ static te_expr *factor(state *s) { if (ret->type == (TE_FUNCTION1 | TE_FLAG_PURE) && ret->function == negate) { te_expr *se = ret->parameters[0]; - if (se->type != TE_CONSTANT) { - free(ret); - ret = se; - neg = 1; - } + free(ret); + ret = se; + neg = 1; } while (s->type == TOK_INFIX && (s->function == pow)) {