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]]