diff --git a/TinyExprChanges.md b/TinyExprChanges.md
index 57cc5c0..3dd4f84 100644
--- a/TinyExprChanges.md
+++ b/TinyExprChanges.md
@@ -33,10 +33,13 @@ The following are changes from the original TinyExpr C library:
- Added new built-in functions:
- `and`: returns true (i.e., non-zero) if all conditions are true (accepts 1-7 arguments).
- `average`: returns the mean for a range of values (accepts 1-7 arguments).
+ - `bitand`: bitwise AND.
- `bitlshift`: left shift operator.
Negative shift amount arguments (similar to *Excel*) is supported.
+ - `bitor`: bitwise OR.
- `bitrshift`: right shift operator.
Negative shift amount arguments (similar to *Excel*) is supported.
+ - `bitxor`: bitwise XOR.
- `cot`: returns the cotangent of an angle.
- `combin`: alias for `ncr()`, like the *Excel* function.
- `clamp`: constrains a value to a range.
diff --git a/docs/manual/compile-time-options.qmd b/docs/manual/compile-time-options.qmd
index a1c4ed4..6fe678c 100644
--- a/docs/manual/compile-time-options.qmd
+++ b/docs/manual/compile-time-options.qmd
@@ -7,6 +7,11 @@ Compile with `TE_FLOAT` defined to use `float` instead.
Refer to [floating-point numbers](#fp-numbers) for more information.
+## `TE_BITWISE_OPERATIONS` {-#te-bitwise-ops}
+
+By default, the operators `&`, `|`, and `^` represent logical AND, logical OR, and exponentiation. If `TE_BITWISE_OPERATIONS` is defined,
+then they will represent bitwise AND, OR, and XOR.
+
## `TE_NO_BOOKKEEPING` {-}
By default, the parser will keep track of all functions and variables used in the last expression it evaluated.
diff --git a/docs/manual/functions.qmd b/docs/manual/functions.qmd
index aa8297d..0e57f39 100644
--- a/docs/manual/functions.qmd
+++ b/docs/manual/functions.qmd
@@ -10,8 +10,11 @@ The following built-in functions are available:
| ASIN(Number) | Returns the arcsine, or inverse sine function, of *Number*, where -1 <= *Number* <= 1. The arcsine is the angle whose sine is *Number*. The returned angle is given in radians where -pi/2 <= angle <= pi/2. |
| ATAN(x) | Returns the principal value of the arc tangent of *x*, expressed in radians.. |
| ATAN2(y, x) | Returns the principal value of the arc tangent of *y*,*x*, expressed in radians. |
+| BITAND(Number1, Number2) | Returns a bitwise 'AND' of two (integral) numbers. (Both numbers must be positive.) |
| BITLSHIFT(Number, ShiftAmount) | Returns *Number* left shifted by the specified number (*ShiftAmount*) of bits. |
-| BITRSHIFT(Number, ShiftAmount) |Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits. |
+| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits. |
+| BITOR(Number1, Number2) | Returns a bitwise 'OR' of two (integral) numbers. (Both numbers must be positive.) |
+| BITXOR(Number1, Number2) | Returns a bitwise 'XOR' of two (integral) numbers. (Both numbers must be positive.) |
| CEIL(Number) | Smallest integer not less than *Number*.
\linebreak `CEIL(-3.2)` = -3
\linebreak `CEIL(3.2)` = 4 |
| CLAMP(Number, Start, End) | Constrains *Number* within the range of *Start* and *End*. |
| COMBIN(Number, NumberChosen) | Returns the number of combinations for a given number (*NumberChosen*) of items from *Number* of items. Note that for combinations, order of items is not important. |
@@ -24,8 +27,8 @@ The following built-in functions are available:
| FLOOR(Number) | Returns the largest integer not greater than *Number*.
\linebreak `FLOOR(-3.2)` = -4
\linebreak `FLOOR(3.2)` = 3 |
| LN(Number) | Natural logarithm of *Number* (base Euler). |
| LOG10(Number) | Common logarithm of *Number* (base 10). |
-| MIN(Value1, Value2, ...) | Returns the lowest value from a specified range of values. |
-| MAX(Value1, Value2, ...) | Returns the highest value from a specified range of values. |
+| MIN(Number1, Number2, ...) | Returns the lowest value from a specified range of values. |
+| MAX(Number1, Number2, ...) | Returns the highest value from a specified range of values. |
| MOD(Number, Divisor) | Returns the remainder after *Number* is divided by *Divisor*. The result has the same sign as divisor. |
| NCR(Number, NumberChosen) | Alias for `COMBIN()`. |
| NPR(Number, NumberChosen) | Alias for `PERMUT()`. |
diff --git a/docs/manual/operators.qmd b/docs/manual/operators.qmd
index 5a6e40a..9cbc1de 100644
--- a/docs/manual/operators.qmd
+++ b/docs/manual/operators.qmd
@@ -10,7 +10,7 @@ The following operators\index{operators} are supported within math expressions:
| % | Modulus: Divides two values and returns the remainder. |
| + | Addition. |
| \- | Subtraction. |
-| ^ | Exponentiation. The number in front of ^ is the base, the number after it is the power to raise it to. |
+| ^ | Either exponentiation or bitwise XOR. Exponentiation is the default; define `TE_BITWISE_OPERATIONS` to enable bitwise behavior. For exponentiation, the number in front of ^ is the base, the number after it is the power to raise it to. |
| ** | Exponentiation. (This is an alias for ^) |
| < | Less than. |
| \> | Greater than. |
@@ -20,9 +20,9 @@ The following operators\index{operators} are supported within math expressions:
| \=\= | Equals. (This is an alias for \=) |
| <> | Not equal to. |
| \!\= | Not equal to. (This is an alias for <>) |
-| & | Logical conjunction (AND). |
-| && | Logical conjunction (AND). |
-| \| | Logical alternative (OR). |
+| & | Either logical or bitwise conjunction (AND). Logical is the default, define `TE_BITWISE_OPERATIONS` to enable bitwise behavior. |
+| && | Either logical conjunction (AND). |
+| \| | Logical or bitwise alternative (OR). Logical is the default, define `TE_BITWISE_OPERATIONS` to enable bitwise behavior. |
| \|\| | Logical alternative (OR). |
| ( ) | Groups sub-expressions, overriding the order of operations. |
| << | Bitwise left shift. |
@@ -46,6 +46,9 @@ For operators, the order of precedence is:
| \= and \!\= | Equality comparisons. |
| && | Logical conjunction (AND). |
| \|\| | Logical alternative (OR). |
+| & | Bitwise AND (if `TE_BITWISE_OPERATIONS` is defined). |
+| ^ | Bitwise XOR (if `TE_BITWISE_OPERATIONS` is defined). |
+| \| | Bitwise OR (if `TE_BITWISE_OPERATIONS` is defined). |
:::
For example, the following:
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 17bcdc6..b3a2255 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -47,6 +47,9 @@ endif(TE_FLOAT)
if(TE_POW_FROM_RIGHT)
add_definitions(-DTE_POW_FROM_RIGHT)
endif(TE_POW_FROM_RIGHT)
+if(TE_BITWISE_OPERATIONS)
+ add_definitions(-TE_BITWISE_OPERATIONS)
+endif(TE_BITWISE_OPERATIONS)
# place Catch2 at the same folder level as this repo if it isn't installed
# (you will need to do this on Windows or macOS or if version 3 of Catch2 isn't installed)
diff --git a/tests/tetests.cpp b/tests/tetests.cpp
index db5131b..e2e6364 100644
--- a/tests/tetests.cpp
+++ b/tests/tetests.cpp
@@ -261,13 +261,17 @@ TEST_CASE("Main tests", "[main]")
CHECK(tep.evaluate("log10(1000)") == 3);
CHECK(tep.evaluate("log10(1e3)") == 3);
CHECK(tep.evaluate("log10 1.0e3") == 3);
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("10^5*5e-5") == 5);
+#endif
+ CHECK(tep.evaluate("10**5*5e-5") == 5);
CHECK_THAT(tep.evaluate("ln 1000"), Catch::Matchers::WithinRel(6.9078, 0.00001));
CHECK_THAT(tep.evaluate("ln e"), Catch::Matchers::WithinRel(1.0, 0.00001));
CHECK(tep.evaluate("ln(exp(3))") == 3);
CHECK_THAT(tep.evaluate("ln(2.7182818)"), Catch::Matchers::WithinRel(1.0, 0.00001));
CHECK_THAT(tep.evaluate("ln(86)"), Catch::Matchers::WithinRel(4.454373, 0.00001));
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("ln (e^10)") == 10);
CHECK(tep.evaluate("ln (e^10)") == 10);
CHECK(tep.evaluate("100^.5+1") == 11);
@@ -281,6 +285,20 @@ TEST_CASE("Main tests", "[main]")
CHECK(tep.evaluate("100^+---.5+1") == static_cast(1.1));
CHECK(tep.evaluate("1e2^+---.5e0+1e0") == static_cast(1.1));
CHECK(tep.evaluate("--(1e2^(+(-(-(-.5e0))))+1e0)") == static_cast(1.1));
+#endif
+ CHECK(tep.evaluate("ln (e**10)") == 10);
+ CHECK(tep.evaluate("ln (e**10)") == 10);
+ CHECK(tep.evaluate("100**.5+1") == 11);
+ CHECK(tep.evaluate("100 **.5+1") == 11);
+ CHECK(tep.evaluate("100**+.5+1") == 11);
+ CHECK(tep.evaluate("100**--.5+1") == 11);
+ CHECK(tep.evaluate("100**---+-++---++-+-+-.5+1") == 11);
+
+ CHECK(tep.evaluate("100**-.5+1") == static_cast(1.1));
+ CHECK(tep.evaluate("100**---.5+1") == static_cast(1.1));
+ CHECK(tep.evaluate("100**+---.5+1") == static_cast(1.1));
+ CHECK(tep.evaluate("1e2**+---.5e0+1e0") == static_cast(1.1));
+ CHECK(tep.evaluate("--(1e2**(+(-(-(-.5e0))))+1e0)") == static_cast(1.1));
CHECK(tep.evaluate("sqrt 100 + 7") == 17);
CHECK(tep.evaluate("sqrt 100 * 7") == 70);
@@ -297,7 +315,9 @@ TEST_CASE("Main tests", "[main]")
CHECK(tep.evaluate("1,(2,3)") == 3);
CHECK(tep.evaluate("-(1,(2,3))") == -3);
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("2^2") == 4);
+#endif
CHECK(tep.evaluate("2**2") == 4);
CHECK(tep.evaluate("2 ** 2") == 4);
CHECK(tep.evaluate("pow(2,2)") == 4);
@@ -322,12 +342,19 @@ TEST_CASE("Main tests", "[main]")
CHECK(tep.evaluate("sign(0)") == 0);
CHECK(tep.evaluate("trunc(9.57878423)") == 9);
CHECK(tep.evaluate("trunc(9.3)") == 9);
-
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("2**4") == 16);
+#endif
CHECK(tep.evaluate("1+2**4") == 17);
+
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("1+2^4") == 17);
+#endif
CHECK(tep.evaluate("(1+2)**4") == 81);
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("(1+2)^4") == 81);
+#endif
+ CHECK(tep.evaluate("(1+2)**4") == 81);
SECTION("variadic functions")
{
@@ -404,12 +431,20 @@ TEST_CASE("Main tests", "[main]")
CHECK(std::isnan(tep.evaluate("NOT(NAN)")));
// garbage values
+#ifndef TE_BITWISE_OPERATIONS
CHECK_FALSE(tep.evaluate("NAN & 5"));
CHECK_FALSE(tep.evaluate("1 & NAN"));
CHECK(std::isnan(tep.evaluate("NAN & NAN")));
CHECK(std::isnan(tep.evaluate("NAN | NAN")));
CHECK(tep.evaluate("NAN | 5") == 1);
CHECK(tep.evaluate("1 | NAN") == 1);
+#endif
+ CHECK_FALSE(tep.evaluate("NAN && 5"));
+ CHECK_FALSE(tep.evaluate("1 && NAN"));
+ CHECK(std::isnan(tep.evaluate("NAN && NAN")));
+ CHECK(std::isnan(tep.evaluate("NAN || NAN")));
+ CHECK(tep.evaluate("NAN || 5") == 1);
+ CHECK(tep.evaluate("1 || NAN") == 1);
CHECK(tep.evaluate("IF(NAN, 9, 7)") == 7);
@@ -426,6 +461,7 @@ TEST_CASE("Main tests", "[main]")
SECTION("operators")
{
+#ifndef TE_BITWISE_OPERATIONS
CHECK(tep.evaluate("0.0 & 5") == 0);
CHECK(tep.evaluate("0.0 & 0") == 0);
CHECK(tep.evaluate("-1 & 5") == 1);
@@ -435,7 +471,7 @@ TEST_CASE("Main tests", "[main]")
CHECK(tep.evaluate("-1 | 5") == 1);
CHECK(tep.evaluate("1 | 1") == 1);
CHECK(tep.evaluate("-1 | 0.0") == 1);
-
+#endif
CHECK(tep.evaluate("0.0 && 5") == 0);
CHECK(tep.evaluate("0.0 && 0") == 0);
CHECK(tep.evaluate("-1 && 5") == 1);
@@ -610,9 +646,12 @@ TEST_CASE("Variables", "[variables]")
ev = tep.evaluate("x+x+x-y");
CHECK_THAT(ev, Catch::Matchers::WithinRel(x+x+x-y));
-
+#ifndef TE_BITWISE_OPERATIONS
ev = tep.evaluate("x*y^3");
CHECK_THAT(ev, Catch::Matchers::WithinRel(x*y*y*y));
+#endif
+ ev = tep.evaluate("x*y**3");
+ CHECK_THAT(ev, Catch::Matchers::WithinRel(x * y * y * y));
test = x;
ev = tep.evaluate("te_st+5");
@@ -817,6 +856,48 @@ TEST_CASE("Power", "[power]")
{"(-1)^0", "1"},
{"(-5)^0", "1"},
{"-2^-3^-4", "-(2^(-(3^-4)))"}*/
+#elif defined(TE_BITWISE_OPERATIONS)
+ CHECK(tep.evaluate("2**3**4") == tep.evaluate("(2**3)**4"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("-2**2") == tep.evaluate("(-2)**2"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("(-2)**2") == tep.evaluate("4"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("--2**2") == tep.evaluate("2**2"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("---2**2") == tep.evaluate("(-2)**2"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("-2**2") == tep.evaluate("4"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("2**1.1**1.2**1.3") == tep.evaluate("((2**1.1)**1.2)**1.3"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("-a**b") == tep.evaluate("(-a)**b"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("-a**-b") == tep.evaluate("(-a)**(-b)"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("1**0") == tep.evaluate("1"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("(1)**0") == tep.evaluate("1"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("(-1)**0") == tep.evaluate("1"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("(-5)**0") == tep.evaluate("1"));
+ CHECK(tep.success());
+
+ CHECK(tep.evaluate("-2**-3**-4") == tep.evaluate("((-2)**(-3))**(-4)"));
+ CHECK(tep.success());
#else
CHECK(tep.evaluate("2^3^4") == tep.evaluate("(2^3)^4"));
CHECK(tep.success());
@@ -1072,10 +1153,17 @@ TEST_CASE("Precedence", "[precedence]")
Catch::Matchers::WithinRel(tep.evaluate("5+2-1*31/2-20+21%2*2")));
CHECK_THAT(-26.5,
Catch::Matchers::WithinRel(tep.evaluate("5+2-1*31/2-20+MOD(21,2)*2")));
+#ifndef TE_BITWISE_OPERATIONS
CHECK_THAT(-12.75,
Catch::Matchers::WithinRel(tep.evaluate("5+2^3-1*31/2^2-20+MOD(21,2)*2")));
CHECK_THAT(-12.75,
Catch::Matchers::WithinRel(tep.evaluate("5+2^3-1*31/2^2-20+ 21% 2*2")));
+#endif
+ CHECK_THAT(-12.75,
+ Catch::Matchers::WithinRel(tep.evaluate("5+2**3-1*31/2**2-20+MOD(21,2)*2")));
+ CHECK_THAT(-12.75,
+ Catch::Matchers::WithinRel(tep.evaluate("5+2**3-1*31/2**2-20+ 21% 2*2")));
+
// The precedence that C++ compiler use for << and >>
// Excel doesn't have operators for these, although there are
// related functions like BITLSHIFT().
@@ -1085,9 +1173,14 @@ TEST_CASE("Precedence", "[precedence]")
CHECK_THAT((32 >> 1 + 2 * 2),
Catch::Matchers::WithinRel(
tep.evaluate("(32 >> 1 + 2 * 2)")));
+#ifndef TE_BITWISE_OPERATIONS
CHECK_THAT(27, // this is what Excel does
Catch::Matchers::WithinRel(
tep.evaluate("5 ^ 2 + 2")));
+#endif
+ CHECK_THAT(27, // this is what Excel does
+ Catch::Matchers::WithinRel(
+ tep.evaluate("5 ** 2 + 2")));
}
TEST_CASE("Round", "[round]")
@@ -1213,16 +1306,27 @@ TEST_CASE("Logical operators", "[logic]")
CHECK(p.evaluate("1 + 1 - 2 < 1 + 1") == 1);
CHECK(p.evaluate("1 + 1 - 2 = 1 + 1 - 2") == 1);
CHECK(p.evaluate("1 + 1 - 2 <> 1 + 1 - 7") == 1);
+#ifndef TE_BITWISE_OPERATIONS
CHECK(p.evaluate("1 - 1 & 2") == 0);
CHECK(p.evaluate("1 - 1 | 2 - 2") == 0);
CHECK(p.evaluate("1 - 1 | 2*4 - 2") == 1);
CHECK(p.evaluate("1 - 1 < 1 & 2") == 1);
+#endif
+ CHECK(p.evaluate("1 - 1 && 2") == 0);
+ CHECK(p.evaluate("1 - 1 || 2 - 2") == 0);
+ CHECK(p.evaluate("1 - 1 || 2*4 - 2") == 1);
+
+ CHECK(p.evaluate("1 - 1 < 1 && 2") == 1);
// examples from manual
CHECK_THAT(12.5, Catch::Matchers::WithinRel(p.evaluate("5+5+5/2")));
CHECK_THAT(7.5, Catch::Matchers::WithinRel(p.evaluate("(5+5+5)/2")));
+#ifndef TE_BITWISE_OPERATIONS
CHECK_THAT(49, Catch::Matchers::WithinRel(p.evaluate("(2+5)^2")));
CHECK_THAT(27, Catch::Matchers::WithinRel(p.evaluate("2+5^2")));
+#endif
+ CHECK_THAT(49, Catch::Matchers::WithinRel(p.evaluate("(2+5)**2")));
+ CHECK_THAT(27, Catch::Matchers::WithinRel(p.evaluate("2+5**2")));
}
TEST_CASE("Statistics", "[stats]")
@@ -1363,10 +1467,16 @@ TEST_CASE("Math operators", "[math]")
CHECK_THAT(19.5, Catch::Matchers::WithinRel(p.evaluate()));
p.compile(("9*((3/2)+(8-2))")); // change up the order of operations
CHECK_THAT(67.5, Catch::Matchers::WithinRel(p.evaluate()));
+#ifndef TE_BITWISE_OPERATIONS
p.compile(("9*3^3/2+8-(11%2)"));
CHECK_THAT(128.5, Catch::Matchers::WithinRel(p.evaluate()));
p.compile(("9.2*3.4^3/2+8.7-(11%2)"));
CHECK_THAT(188.4984, Catch::Matchers::WithinRel(p.evaluate()));
+#endif
+ p.compile(("9*3**3/2+8-(11%2)"));
+ CHECK_THAT(128.5, Catch::Matchers::WithinRel(p.evaluate()));
+ p.compile(("9.2*3.4**3/2+8.7-(11%2)"));
+ CHECK_THAT(188.4984, Catch::Matchers::WithinRel(p.evaluate()));
}
TEST_CASE("Division", "[math]")
@@ -1692,10 +1802,16 @@ NAN)
CHECK(std::isnan(tep.get_result()));
// complicated formula
+#ifndef TE_BITWISE_OPERATIONS
CHECK_THAT(4.5, Catch::Matchers::WithinRel(tep.evaluate("ABS(((5+2) / (ABS(-2))) * -9 + 2) - 5^2")));
+#endif
+ CHECK_THAT(4.5, Catch::Matchers::WithinRel(tep.evaluate("ABS(((5+2) / (ABS(-2))) * -9 + 2) - 5**2")));
// make it look like an Excel function
+#ifndef TE_BITWISE_OPERATIONS
CHECK_THAT(4.5, Catch::Matchers::WithinRel(tep.evaluate("=ABS(((5+2) / (ABS(-2))) * -9 + 2) - 5^2")));
+#endif
+ CHECK_THAT(4.5, Catch::Matchers::WithinRel(tep.evaluate("=ABS(((5+2) / (ABS(-2))) * -9 + 2) - 5**2")));
}
TEST_CASE("Permutation & Combination", "[math]")
@@ -1974,6 +2090,92 @@ TEST_CASE("Available functions", "[available]")
CHECK_NOTHROW(tep.list_available_functions_and_variables());
}
+TEST_CASE("Bitwise operators", "[bitwise]")
+ {
+ te_parser tep;
+
+ SECTION("BITOR")
+ {
+#ifdef TE_BITWISE_OPERATIONS
+ CHECK(tep.evaluate("23 | 10") == 31);
+ CHECK(tep.evaluate("23 | 0") == (23 | 0));
+ CHECK(tep.evaluate("0 | 10") == (0 | 10));
+ // technical, negative should be OK, but Excel and LibreOffice
+ // do not allow them, so we don't either
+ CHECK(std::isnan(tep.evaluate("-15 | 17")));
+ CHECK(std::isnan(tep.evaluate("17 | -15")));
+ // max int 32-bit (64-bit int won't be support if long double can't hold it)
+ CHECK(tep.evaluate("4294967295 | 8000") ==
+ (4294967295 | 8000));
+ CHECK(tep.evaluate("8000 | 4294967295") ==
+ (8000 | 4294967295));
+#endif
+ CHECK(tep.evaluate("BITOR(23, 10)") == 31);
+ CHECK(tep.evaluate("BITOR(23, 0)") == (23 | 0));
+ CHECK(tep.evaluate("BITOR(0, 10)") == (0 | 10));
+ // technical, negative should be OK, but Excel and LibreOffice
+ // do not allow them, so we don't either
+ CHECK(std::isnan(tep.evaluate("BITOR(-15, 17)")));
+ CHECK(std::isnan(tep.evaluate("BITOR(17, -15)")));
+ // max int 32-bit (64-bit int won't be support if long double can't hold it)
+ CHECK(tep.evaluate("BITOR(4294967295, 8000)") ==
+ (4294967295 | 8000));
+ CHECK(tep.evaluate("BITOR(8000, 4294967295)") ==
+ (8000 | 4294967295));
+ }
+
+ SECTION("BITXOR")
+ {
+#ifdef TE_BITWISE_OPERATIONS
+ CHECK(tep.evaluate("5 ^ 3") == 6);
+ CHECK(tep.evaluate("5 ^ 9") == 12);
+ CHECK(tep.evaluate("23^ 0") == (23 ^ 0));
+ CHECK(tep.evaluate("0 ^10") == (0 ^ 10));
+ // technical, negative should be OK, but Excel and LibreOffice
+ // do not allow them, so we don't either
+ CHECK(std::isnan(tep.evaluate("-15 ^ 17")));
+ CHECK(std::isnan(tep.evaluate("17 ^ -15")));
+ // max int 32-bit (64-bit int won't be support if long double can't hold it)
+ CHECK(tep.evaluate("4294967295^8000") ==
+ (4294967295 ^ 8000));
+ CHECK(tep.evaluate("8000 ^ 4294967295") ==
+ (8000 ^ 4294967295));
+#endif
+ CHECK(tep.evaluate("BITXOR(5,3)") == 6);
+ CHECK(tep.evaluate("BITXOR(5,9)") == 12);
+ CHECK(tep.evaluate("BITXOR(23, 0)") == (23 ^ 0));
+ CHECK(tep.evaluate("BITXOR(0, 10)") == (0 ^ 10));
+ // technical, negative should be OK, but Excel and LibreOffice
+ // do not allow them, so we don't either
+ CHECK(std::isnan(tep.evaluate("BITXOR(-15, 17)")));
+ CHECK(std::isnan(tep.evaluate("BITXOR(17, -15)")));
+ // max int 32-bit (64-bit int won't be support if long double can't hold it)
+ CHECK(tep.evaluate("BITXOR(4294967295, 8000)") ==
+ (4294967295 ^ 8000));
+ CHECK(tep.evaluate("BITXOR(8000, 4294967295)") ==
+ (8000 ^ 4294967295));
+ }
+
+ SECTION("BITAND")
+ {
+#ifdef TE_BITWISE_OPERATIONS
+#endif
+ CHECK(tep.evaluate("BITAND(1,5)") == 1);
+ CHECK(tep.evaluate("BITAND(13,25)") == 9);
+ CHECK(tep.evaluate("BITAND(23, 0)") == (23 & 0));
+ CHECK(tep.evaluate("BITAND(0, 10)") == (0 & 10));
+ // technical, negative should be OK, but Excel and LibreOffice
+ // do not allow them, so we don't either
+ CHECK(std::isnan(tep.evaluate("BITAND(-15, 17)")));
+ CHECK(std::isnan(tep.evaluate("BITAND(17, -15)")));
+ // max int 32-bit (64-bit int won't be support if long double can't hold it)
+ CHECK(tep.evaluate("BITAND(4294967295, 8000)") ==
+ (4294967295 & 8000));
+ CHECK(tep.evaluate("BITAND(8000, 4294967295)") ==
+ (8000 & 4294967295));
+ }
+ }
+
TEST_CASE("Shift operators", "[shift]")
{
te_parser tep;
@@ -1990,8 +2192,8 @@ TEST_CASE("Shift operators", "[shift]")
}
SECTION("BITLSHIFT")
{
- CHECK(tep.evaluate("BITLSHIFT(2,25)") == 67108864);
- CHECK(tep.evaluate("BITLSHIFT(0,25)") == 0);
+ CHECK(tep.evaluate("BITLSHIFT(2, 25)") == 67108864);
+ CHECK(tep.evaluate("BITLSHIFT(0, 25)") == 0);
CHECK(tep.evaluate("BITLSHIFT(5, 8)") == 1280);
CHECK(tep.evaluate("BITLSHIFT(5, 0)") == 5);
// negative turns it into a right shift
@@ -1999,9 +2201,9 @@ TEST_CASE("Shift operators", "[shift]")
}
SECTION("BITRSHIFT")
{
- CHECK(tep.evaluate("BITRSHIFT(13,2)") == 3);
- CHECK(tep.evaluate("BITRSHIFT(10,0)") == 10);
- CHECK(tep.evaluate("BITRSHIFT(1024,4)") == 64);
+ CHECK(tep.evaluate("BITRSHIFT(13, 2)") == 3);
+ CHECK(tep.evaluate("BITRSHIFT(10, 0)") == 10);
+ CHECK(tep.evaluate("BITRSHIFT(1024, 4)") == 64);
CHECK(tep.evaluate("BITRSHIFT(500, 2)") == 125);
CHECK(tep.get_last_error_message().empty());
// negative turns it into a left shift
diff --git a/tinyexpr.cpp b/tinyexpr.cpp
index 5dcbe1d..8d0f36d 100644
--- a/tinyexpr.cpp
+++ b/tinyexpr.cpp
@@ -433,6 +433,60 @@ namespace te_builtins
return val1 * val2;
}
+ //--------------------------------------------------
+ [[nodiscard]]
+ static te_type te_bitwise_or(te_type val1, te_type val2)
+ {
+ if (std::floor(val1) != val1 || std::floor(val2) != val2)
+ {
+ throw std::runtime_error("Bitwise OR operation must use integers.");
+ }
+ // negative technically should be allowed, but spreadsheet programs do
+ // not allow them; hence, we won't either
+ else if (val1 < 0 || val2 < 0)
+ {
+ throw std::runtime_error("Bitwise OR operation must use positive integers.");
+ }
+
+ return static_cast(static_cast(val1) | static_cast(val2));
+ }
+
+ //--------------------------------------------------
+ [[nodiscard]]
+ static te_type te_bitwise_xor(te_type val1, te_type val2)
+ {
+ if (std::floor(val1) != val1 || std::floor(val2) != val2)
+ {
+ throw std::runtime_error("Bitwise XOR operation must use integers.");
+ }
+ // negative technically should be allowed, but spreadsheet programs do
+ // not allow them; hence, we won't either
+ else if (val1 < 0 || val2 < 0)
+ {
+ throw std::runtime_error("Bitwise XOR operation must use positive integers.");
+ }
+
+ return static_cast(static_cast(val1) ^ static_cast(val2));
+ }
+
+ //--------------------------------------------------
+ [[nodiscard]]
+ static te_type te_bitwise_and(te_type val1, te_type val2)
+ {
+ if (std::floor(val1) != val1 || std::floor(val2) != val2)
+ {
+ throw std::runtime_error("Bitwise AND operation must use integers.");
+ }
+ // negative technically should be allowed, but spreadsheet programs do
+ // not allow them; hence, we won't either
+ else if (val1 < 0 || val2 < 0)
+ {
+ throw std::runtime_error("Bitwise AND operation must use positive integers.");
+ }
+
+ return static_cast(static_cast(val1) & static_cast(val2));
+ }
+
// Shift operators
//--------------------------------------------------
[[nodiscard]]
@@ -725,8 +779,11 @@ const std::set te_parser::m_functions = { // NOLINT
{ "atan2", static_cast(te_builtins::te_atan2), TE_PURE },
{ "average", static_cast(te_builtins::te_average),
static_cast(TE_PURE | TE_VARIADIC) },
+ { "bitand", static_cast(te_builtins::te_bitwise_and), TE_PURE },
+ { "bitor", static_cast(te_builtins::te_bitwise_or), TE_PURE },
{ "bitlshift", static_cast(te_builtins::te_left_shift_or_right), TE_PURE },
{ "bitrshift", static_cast(te_builtins::te_right_shift_or_left), TE_PURE },
+ { "bitxor", static_cast(te_builtins::te_bitwise_xor), TE_PURE },
{ "ceil", static_cast(te_builtins::te_ceil), TE_PURE },
{ "clamp",
static_cast(
@@ -967,11 +1024,19 @@ void te_parser::next_token(te_parser::state* theState)
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = te_builtins::te_divide;
}
+#ifdef TE_BITWISE_OPERATIONS
+ else if (tok == '^')
+ {
+ theState->m_type = te_parser::state::token_type::TOK_INFIX;
+ theState->m_value = static_cast(te_builtins::te_bitwise_xor);
+ }
+#else
else if (tok == '^')
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = static_cast(te_builtins::te_pow);
}
+#endif
else if (tok == '%')
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
@@ -1054,22 +1119,38 @@ void te_parser::next_token(te_parser::state* theState)
theState->m_value = static_cast(te_builtins::te_and);
std::advance(theState->m_next, 1);
}
+#ifdef TE_BITWISE_OPERATIONS
+ else if (tok == '&')
+ {
+ theState->m_type = te_parser::state::token_type::TOK_INFIX;
+ theState->m_value = static_cast(te_builtins::te_bitwise_and);
+ }
+#else
else if (tok == '&')
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = static_cast(te_builtins::te_and);
}
+#endif
else if (tok == '|' && (*theState->m_next == '|'))
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = static_cast(te_builtins::te_or);
std::advance(theState->m_next, 1);
}
+#ifdef TE_BITWISE_OPERATIONS
+ else if (tok == '|')
+ {
+ theState->m_type = te_parser::state::token_type::TOK_INFIX;
+ theState->m_value = static_cast(te_builtins::te_bitwise_or);
+ }
+#else
else if (tok == '|')
{
theState->m_type = te_parser::state::token_type::TOK_INFIX;
theState->m_value = static_cast(te_builtins::te_or);
}
+#endif
else if (tok == ' ' || tok == '\t' || tok == '\n' || tok == '\r')
{ /*noop*/
}
@@ -1251,7 +1332,7 @@ te_expr* te_parser::expr_level2(te_parser::state* theState)
{
/* = {(logic operations) } */
// next to lowest in precedence...
- te_expr* ret = expr_level6(theState);
+ te_expr* ret = expr_level3(theState);
while (theState->m_type == te_parser::state::token_type::TOK_INFIX &&
is_function2(theState->m_value) &&
@@ -1259,14 +1340,69 @@ te_expr* te_parser::expr_level2(te_parser::state* theState)
{
const te_fun2 func = get_function2(theState->m_value);
next_token(theState);
+ ret = new_expr(TE_PURE, func, { ret, expr_level3(theState) });
+ }
+
+ return ret;
+ }
+
+//--------------------------------------------------
+te_expr* te_parser::expr_level3(te_parser::state* theState)
+ {
+ /* = {(logic operations) } */
+ // next to lowest in precedence...
+ te_expr* ret = expr_level4(theState);
+
+ while (theState->m_type == te_parser::state::token_type::TOK_INFIX &&
+ is_function2(theState->m_value) &&
+ get_function2(theState->m_value) == te_builtins::te_bitwise_or)
+ {
+ const te_fun2 func = get_function2(theState->m_value);
+ next_token(theState);
+ ret = new_expr(TE_PURE, func, { ret, expr_level4(theState) });
+ }
+
+ return ret;
+ }
+
+//--------------------------------------------------
+te_expr* te_parser::expr_level4(te_parser::state* theState)
+ {
+ /* = {(logic operations) } */
+ // next to lowest in precedence...
+ te_expr* ret = expr_level5(theState);
+
+ while (theState->m_type == te_parser::state::token_type::TOK_INFIX &&
+ is_function2(theState->m_value) &&
+ get_function2(theState->m_value) == te_builtins::te_bitwise_xor)
+ {
+ const te_fun2 func = get_function2(theState->m_value);
+ next_token(theState);
+ ret = new_expr(TE_PURE, func, { ret, expr_level5(theState) });
+ }
+
+ return ret;
+ }
+
+//--------------------------------------------------
+te_expr* te_parser::expr_level5(te_parser::state* theState)
+ {
+ /* = {(logic operations) } */
+ // next to lowest in precedence...
+ te_expr* ret = expr_level6(theState);
+
+ while (theState->m_type == te_parser::state::token_type::TOK_INFIX &&
+ is_function2(theState->m_value) &&
+ get_function2(theState->m_value) == te_builtins::te_bitwise_and)
+ {
+ const te_fun2 func = get_function2(theState->m_value);
+ next_token(theState);
ret = new_expr(TE_PURE, func, { ret, expr_level6(theState) });
}
return ret;
}
-// levels 3-5 open for possible future extensions
-// of bitwise OR, XOR, and AND
//--------------------------------------------------
te_expr* te_parser::expr_level6(te_parser::state* theState)
{
diff --git a/tinyexpr.h b/tinyexpr.h
index 0d47079..8afcf5b 100644
--- a/tinyexpr.h
+++ b/tinyexpr.h
@@ -1032,8 +1032,12 @@ class te_parser
te_expr* expr_level1(state* theState);
[[nodiscard]]
te_expr* expr_level2(state* theState);
- // levels 3-5 open for possible future extensions
- // of bitwise OR, XOR, and AND
+ [[nodiscard]]
+ te_expr* expr_level3(state* theState);
+ [[nodiscard]]
+ te_expr* expr_level4(state* theState);
+ [[nodiscard]]
+ te_expr* expr_level5(state* theState);
[[nodiscard]]
te_expr* expr_level6(state* theState);
[[nodiscard]]