From 06d4f99fca45188de206b8258e99cd893be2f67a Mon Sep 17 00:00:00 2001 From: Blake Madden Date: Sat, 9 Mar 2024 12:47:23 -0500 Subject: [PATCH] Add bitwise NOT Closes #15 #13 --- TinyExprChanges.md | 6 +- docs/manual/compile-time-options.qmd | 2 + docs/manual/functions.qmd | 22 ++- docs/manual/operators.qmd | 21 ++- tests/tetests.cpp | 236 +++++++++++++++++++------ tinyexpr.cpp | 248 ++++++++++++++++++++++++--- 6 files changed, 452 insertions(+), 83 deletions(-) diff --git a/TinyExprChanges.md b/TinyExprChanges.md index 0b988e4..6ad6130 100644 --- a/TinyExprChanges.md +++ b/TinyExprChanges.md @@ -34,11 +34,12 @@ The following are changes from the original TinyExpr C library: - `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. - - `bitlrotate64`: bitwise (`uint64_t`) left rotate. (Only available if compiled as C++20.) + - `bitlrotate`: bitwise left rotate. Versions of this are available for 8-, 16-, 32-, and 64-bit integers (if supported by the platform). (Only available if compiled as C++20.) - `bitlshift`: left shift. Negative shift amount arguments (similar to *Excel*) are supported. + - `bitnot`: bitwise NOT. Versions of this are available for 8-, 16-, 32-, and 64-bit integers (if supported by the platform). - `bitor`: bitwise OR. - - `bitrrotate64`: bitwise (`uint64_t`) right rotate. (Only available if compiled as C++20.) + - `bitrrotate`: bitwise right rotate. Versions of this are available for 8-, 16-, 32-, and 64-bit integers (if supported by the platform). (Only available if compiled as C++20.) - `bitrshift`: right shift. Negative shift amount arguments (similar to *Excel*) are supported. - `bitxor`: bitwise XOR. @@ -87,6 +88,7 @@ The following are changes from the original TinyExpr C library: - `<<<` left (`uint64_t`) rotation operator. - `>>>` right (`uint64_t`) rotation operator. - `**` exponentiation (alias for `^`). + - `~` bitwise NOT. - `round` now supports negative number of digit arguments, similar to *Excel*. For example, `ROUND(-50.55,-2)` will yield `-100`. - Custom variables and functions are now stored in a `std::set` diff --git a/docs/manual/compile-time-options.qmd b/docs/manual/compile-time-options.qmd index 785f188..325bca2 100644 --- a/docs/manual/compile-time-options.qmd +++ b/docs/manual/compile-time-options.qmd @@ -67,9 +67,11 @@ If compiling as C++20 (and `TE_FLOAT` is not defined), then the following functi - `BITLROTATE16` - `BITLROTATE32` - `BITLROTATE64` +- `BITLROTATE` - `BITRROTATE8` - `BITRROTATE16` - `BITRROTATE32` - `BITRROTATE64` +- `BITRROTATE` - `<<<` (unsigned 64-bit left rotation) - `>>>` (unsigned 64-bit right rotation) diff --git a/docs/manual/functions.qmd b/docs/manual/functions.qmd index 5d94c3b..72f703d 100644 --- a/docs/manual/functions.qmd +++ b/docs/manual/functions.qmd @@ -52,19 +52,26 @@ Table: Math Functions\index{functions!math} | BITLROTATE8(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 8-bit integer.
\linebreak (Only available if compiled as C++20.) | | BITLROTATE16(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 16-bit integer.
\linebreak (Only available if compiled as C++20.) | | BITLROTATE32(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 32-bit integer.
\linebreak (Only available if compiled as C++20.) | -| BITLROTATE(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 53-bit (or 64-bit) integer.
\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.
\linebreak (Only available if compiled as C++20.) | -| BITLSHIFT(Number, ShiftAmount) | Returns *Number* left shifted by the specified number (*ShiftAmount*) of bits.
\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `64`, if supported). | +| BITLROTATE64(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 64-bit integer.
\linebreak (Only available if compiled as C++20.) | +| BITLROTATE(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as either a 32- or 64-bit integer (depending on what is supported by the compiler).
\linebreak (Only available if compiled as C++20.) | +| BITLSHIFT(Number, ShiftAmount) | Returns *Number* left shifted by the specified number (*ShiftAmount*) of bits.
\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `63`, if 64-bit is supported). | +| BITNOT8(Number) | Returns a bitwise 'NOT' of a 8-bit integer. | +| BITNOT16(Number) | Returns a bitwise 'NOT' of a 16-bit integer. | +| BITNOT32(Number) | Returns a bitwise 'NOT' of a 32-bit integer. | +| BITNOT64(Number) | Returns a bitwise 'NOT' of a 64-bit integer (if 64-bit integers are supported). | +| BITNOT(Number) | Returns a bitwise 'NOT' of a 32- or 64-bit integer (depending on whether 64-bit is supported). | | BITOR(Number1, Number2) | Returns a bitwise 'OR' of two (integral) numbers. (Both numbers must be positive and cannot exceed `(2^48)-1`.) | | BITRROTATE8(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 8-bit integer.
\linebreak (Only available if compiled as C++20.) | | BITRROTATE16(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 16-bit integer.
\linebreak (Only available if compiled as C++20.) | | BITRROTATE32(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 32-bit integer.
\linebreak (Only available if compiled as C++20.) | -| BITRROTATE(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 53-bit (or 64-bit) integer.
\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.
\linebreak (Only available if compiled as C++20.) | -| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits.
\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `64`, if supported). | +| BITRROTATE64(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as an unsigned 64-bit integer.
\linebreak (Only available if compiled as C++20.) | +| BITRROTATE(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.
\linebreak *Number* is rotated as either a 32- or 64-bit integer (depending on what is supported by the compiler).
\linebreak (Only available if compiled as C++20.) | +| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits.
\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `63`, if 64-bit is supported). | | BITXOR(Number1, Number2) | Returns a bitwise 'XOR' of two (integral) numbers. (Both numbers must be positive and cannot exceed `(2^48)-1`.) | | SUPPORTS32BIT() | Returns true if 32-bit integers are supported. This will affect the supported range of values for bitwise operations. | | SUPPORTS64BIT() | Returns true if 64-bit integers are supported. This will affect the supported range of values for bitwise operations. | -Table: Bitwise Functions\index{functions!bitwise} +Table: Engineering Functions\index{functions!engineering} ::: ::: {.notesection data-latex=""} @@ -96,3 +103,8 @@ Table: Logic Functions\index{functions!logical} The first argument to any logic function must be valid (i.e., not NaN). If the first argument evaluates to NaN, then NaN will be returned. Any subsequent arguments that evaluate to NaN will be ignored. ::: + +## Compatibility Note {-} + +`BITNOT` will call either `BITNOT32` or `BITNOT64`, depending on whether 64-bit integers are supported. +This differs from *Excel*, which only works with 16-bit integers. To match the behavior of *Excel*, explicitly call `BITNOT16`. diff --git a/docs/manual/operators.qmd b/docs/manual/operators.qmd index f5fbc27..38e99f7 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. | -| ^ | Either exponentiation or bitwise XOR. Exponentiation is the default; define `TE_BITWISE_OPERATORS` 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. | +| ^ | Either exponentiation (the default) or bitwise XOR. 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,26 +20,31 @@ 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 <>) | -| & | Either logical or bitwise conjunction (AND). Logical is the default, define `TE_BITWISE_OPERATORS` to enable bitwise behavior. | -| && | Either logical conjunction (AND). | -| \| | Logical or bitwise alternative (OR). Logical is the default, define `TE_BITWISE_OPERATORS` to enable bitwise behavior. | -| \|\| | Logical alternative (OR). | +| & | Either logical (the default) or bitwise conjunction (AND). | +| && | Logical conjunction (AND). | +| \| | Either Logical (the default) or bitwise alternative (OR). | +| \|\ | | Logical alternative (OR). | | ( ) | Groups sub-expressions, overriding the order of operations. | -| << | Bitwise left shift. | -| >> | Bitwise right shift. | +| ~ | Bitwise NOT. | +| << | Bitwise left shift. | +| >> | Bitwise right shift. | | <<< | Bitwise left rotate. (Only available if compiled as C++20.) | | >>> | Bitwise right rotate. (Only available if compiled as C++20.) | Table: Operators ::: +::: {.tipsection data-latex=""} +Define `TE_BITWISE_OPERATORS` to enable bitwise behavior for `&`, `|`, and `^`. +::: + ::: {.minipage data-latex="{\textwidth}"} For operators, the order of precedence is: | Operator | Description | | :-- | :-- | | ( ) | Instructions in parentheses are executed first. (If `TE_BRACKETS_AS_PARENS` is defined, then `[]` are treated the same way.) | -| \+ and - | Positive or negative sign for a value. | +| \+, -, ~ | Positive or negative sign for a value, and bitwise NOT. | | ^ | Exponentiation. | | \*, /, and % | Multiplication, division, and modulus. | | \+ and - | Addition and subtraction. | diff --git a/tests/tetests.cpp b/tests/tetests.cpp index 0576695..dd7e4a1 100644 --- a/tests/tetests.cpp +++ b/tests/tetests.cpp @@ -2179,9 +2179,9 @@ TEST_CASE("Bitwise operators", "[bitwise]") CHECK(tep.evaluate("8000 | 4294967295") == (8000 | 4294967295)); #endif - CHECK(tep.evaluate("=BITOR((2^48)-1, 1)") == 281474976710655); - CHECK(tep.evaluate("=BITOR((2^48)-1, (2^48)-1)") == 281474976710655); - CHECK(std::isnan(tep.evaluate("=BITOR((2^48)-1, (2^48))"))); + CHECK(tep.evaluate("=BITOR((2**48)-1, 1)") == 281474976710655); + CHECK(tep.evaluate("=BITOR((2**48)-1, (2**48)-1)") == 281474976710655); + CHECK(std::isnan(tep.evaluate("=BITOR((2**48)-1, (2**48))"))); CHECK(tep.evaluate("BITOR(23, 10)") == 31); CHECK(tep.evaluate("BITOR(23, 0)") == (23 | 0)); CHECK(tep.evaluate("BITOR(0, 10)") == (0 | 10)); @@ -2213,10 +2213,10 @@ TEST_CASE("Bitwise operators", "[bitwise]") CHECK(tep.evaluate("8000 ^ 4294967295") == (8000 ^ 4294967295)); #endif - CHECK(tep.evaluate("=BITXOR((2^48)-1, 1)") == 281474976710654); - CHECK(tep.evaluate("=BITXOR((2^48)-1, (2^48)-1)") == 0); - CHECK(tep.evaluate("=BITXOR((2^48)-1, 1587)") == 281474976709068); - CHECK(std::isnan(tep.evaluate("=BITXOR((2^48)-1, (2^48))"))); + CHECK(tep.evaluate("=BITXOR((2**48)-1, 1)") == 281474976710654); + CHECK(tep.evaluate("=BITXOR((2**48)-1, (2**48)-1)") == 0); + CHECK(tep.evaluate("=BITXOR((2**48)-1, 1587)") == 281474976709068); + CHECK(std::isnan(tep.evaluate("=BITXOR((2**48)-1, (2**48))"))); CHECK(tep.evaluate("BITXOR(5,3)") == 6); CHECK(tep.evaluate("BITXOR(5,9)") == 12); CHECK(tep.evaluate("BITXOR(23, 0)") == (23 ^ 0)); @@ -2240,10 +2240,10 @@ TEST_CASE("Bitwise operators", "[bitwise]") CHECK(tep.evaluate("23 & 0") == (23 & 0)); CHECK(tep.evaluate("0 & 10") == (0 & 10)); #endif - CHECK(tep.evaluate("=BITAND((2^48)-1, 1)") == 1); - CHECK(tep.evaluate("=BITAND((2^48)-1, (2^48)-1)") == 281474976710655); - CHECK(tep.evaluate("=BITAND((2^48)-1, 1587)") == 1587); - CHECK(std::isnan(tep.evaluate("=BITAND((2^48)-1, (2^48))"))); + CHECK(tep.evaluate("=BITAND((2**48)-1, 1)") == 1); + CHECK(tep.evaluate("=BITAND((2**48)-1, (2**48)-1)") == 281474976710655); + CHECK(tep.evaluate("=BITAND((2**48)-1, 1587)") == 1587); + CHECK(std::isnan(tep.evaluate("=BITAND((2**48)-1, (2**48))"))); CHECK(tep.evaluate("BITAND(1, 5)") == 1); CHECK(tep.evaluate("BITAND(13, 25)") == 9); CHECK(tep.evaluate("BITAND(23, 0)") == (23 & 0)); @@ -2258,6 +2258,84 @@ TEST_CASE("Bitwise operators", "[bitwise]") CHECK(tep.evaluate("BITAND(8000, 4294967295)") == (8000 & 4294967295)); } + + SECTION("BITNOT8") + { + for (uint8_t i = 0; i < std::numeric_limits::max(); ++i) + { + decltype(i) res = ~i; + REQUIRE(tep.evaluate("=BITNOT8(" + std::to_string(i) + ")") == res); + } + } + + SECTION("BITNOT16") + { + for (uint16_t i = 0; i < std::numeric_limits::max(); ++i) + { + decltype(i) res = ~i; + REQUIRE(tep.evaluate("=BITNOT16(" + std::to_string(i) + ")") == res); + } + // Excel example + REQUIRE(tep.evaluate("=BITNOT16(6)") == 65529); + } + + SECTION("BITNOT32") + { + // not practical to check every possible value, so do the extremes and middle + uint32_t val{ std::numeric_limits::max() }; + decltype(val) res = ~val; + CHECK(tep.evaluate("=BITNOT32(" + std::to_string(val) + ")") == res); + + val /= 2; + res = ~val; + CHECK(tep.evaluate("=BITNOT32(" + std::to_string(val) + ")") == res); + + val = 0; + res = ~val; + CHECK(tep.evaluate("=BITNOT32(" + std::to_string(val) + ")") == res); + + // ~ falling back to 32-bit + if constexpr(!te_parser::supports_64bit() && + te_parser::supports_32bit()) + { + val = std::numeric_limits::max(); + decltype(val) res = ~val; + CHECK(tep.evaluate("~" + std::to_string(val)) == res); + + val /= 2; + res = ~val; + CHECK(tep.evaluate("~" + std::to_string(val)) == res); + + val = 1986; + res = ~val; + CHECK(tep.evaluate("~" + std::to_string(val)) == res); + CHECK(tep.evaluate("+~1986") == res); + CHECK(tep.evaluate("~+1986") == res); + } + } + + SECTION("BITNOT64") + { + if constexpr(te_parser::supports_64bit()) + { + // not practical to check every possible value, so do the extremes and middle + uint64_t val{ std::numeric_limits::max() }; + decltype(val) res = ~val; + CHECK(tep.evaluate("=BITNOT64(" + std::to_string(val) + ")") == res); + + val /= 2; + res = ~val; + CHECK(tep.evaluate("=BITNOT64(" + std::to_string(val) + ")") == res); + + val = 0; + res = ~val; + CHECK(tep.evaluate("=BITNOT64(" + std::to_string(val) + ")") == res); + } + else + { + CHECK(std::isnan(tep.evaluate("=BITNOT64(0)"))); + } + } } #endif @@ -2303,20 +2381,40 @@ TEST_CASE("Rotate operators", "[rotate]") CHECK(std::isnan(tep.evaluate("5 <<"))); CHECK(std::isnan(tep.evaluate("5 <<<"))); - std::uint64_t i = 4294967295; - CHECK(tep.evaluate("0 <<< 0") == std::rotl((uint64_t)0, 0)); - CHECK(tep.evaluate("4294967295 <<< 0") == std::rotl(i, 0)); - CHECK(tep.evaluate("4294967295 <<< 1") == std::rotl(i, 1)); - CHECK(tep.evaluate("4294967295 <<< 4") == std::rotl(i, 4)); - CHECK(tep.evaluate("4294967295 <<< 9") == std::rotl(i, 9)); - CHECK(tep.evaluate("4294967295 <<< -1") == std::rotl(i, -1)); - - CHECK(tep.evaluate("BITLROTATE(0, 0)") == std::rotl((uint64_t)0, 0)); - CHECK(tep.evaluate("BITLROTATE(4294967295, 0)") == std::rotl(i, 0)); - CHECK(tep.evaluate("BITLROTATE(4294967295, 1)") == std::rotl(i, 1)); - CHECK(tep.evaluate("BITLROTATE(4294967295, 4)") == std::rotl(i, 4)); - CHECK(tep.evaluate("BITLROTATE(4294967295, 9)") == std::rotl(i, 9)); - CHECK(tep.evaluate("BITLROTATE(4294967295, -1)") == std::rotl(i, -1)); + if constexpr(te_parser::supports_64bit()) + { + const std::uint64_t i = 4294967295; + CHECK(tep.evaluate("0 <<< 0") == std::rotl((uint64_t)0, 0)); + CHECK(tep.evaluate("4294967295 <<< 0") == std::rotl(i, 0)); + CHECK(tep.evaluate("4294967295 <<< 1") == std::rotl(i, 1)); + CHECK(tep.evaluate("4294967295 <<< 4") == std::rotl(i, 4)); + CHECK(tep.evaluate("4294967295 <<< 9") == std::rotl(i, 9)); + CHECK(tep.evaluate("4294967295 <<< -1") == std::rotl(i, -1)); + + CHECK(tep.evaluate("BITLROTATE(0, 0)") == std::rotl((uint64_t)0, 0)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 0)") == std::rotl(i, 0)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 1)") == std::rotl(i, 1)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 4)") == std::rotl(i, 4)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 9)") == std::rotl(i, 9)); + CHECK(tep.evaluate("BITLROTATE(4294967295, -1)") == std::rotl(i, -1)); + } + else if constexpr (te_parser::supports_32bit()) + { + const std::uint32_t i = 4294967295; + CHECK(tep.evaluate("0 <<< 0") == std::rotl((uint32_t)0, 0)); + CHECK(tep.evaluate("4294967295 <<< 0") == std::rotl(i, 0)); + CHECK(tep.evaluate("4294967295 <<< 1") == std::rotl(i, 1)); + CHECK(tep.evaluate("4294967295 <<< 4") == std::rotl(i, 4)); + CHECK(tep.evaluate("4294967295 <<< 9") == std::rotl(i, 9)); + CHECK(tep.evaluate("4294967295 <<< -1") == std::rotl(i, -1)); + + CHECK(tep.evaluate("BITLROTATE(0, 0)") == std::rotl((uint32_t)0, 0)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 0)") == std::rotl(i, 0)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 1)") == std::rotl(i, 1)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 4)") == std::rotl(i, 4)); + CHECK(tep.evaluate("BITLROTATE(4294967295, 9)") == std::rotl(i, 9)); + CHECK(tep.evaluate("BITLROTATE(4294967295, -1)") == std::rotl(i, -1)); + } } SECTION("BITRROTATE8") @@ -2356,22 +2454,46 @@ TEST_CASE("Rotate operators", "[rotate]") CHECK(std::isnan(tep.evaluate("5 >>"))); CHECK(std::isnan(tep.evaluate("5 >>>"))); - // TODO limited to 32-bit int until possible long double support - // can be added (at least for some compilers) - constexpr std::uint64_t i = std::numeric_limits::max();; - CHECK(tep.evaluate("0 >>> 0") == std::rotr((uint64_t)0, 0)); - CHECK(tep.evaluate("4294967295 >>> 0") == std::rotr(i, 0)); - CHECK(tep.evaluate("4294967295 >>> 1") == std::rotr(i, 1)); - CHECK(tep.evaluate("4294967295 >>> 4") == std::rotr(i, 4)); - CHECK(tep.evaluate("4294967295 >>> 9") == std::rotr(i, 9)); - CHECK(tep.evaluate("4294967295 >>> -1") == std::rotr(i, -1)); - - CHECK(tep.evaluate("BITRROTATE(0, 0)") == std::rotr((uint64_t)0, 0)); - CHECK(tep.evaluate("BITRROTATE(4294967295, 0)") == std::rotr(i, 0)); - CHECK(tep.evaluate("BITRROTATE(4294967295, 1)") == std::rotr(i, 1)); - CHECK(tep.evaluate("BITRROTATE(4294967295, 4)") == std::rotr(i, 4)); - CHECK(tep.evaluate("BITRROTATE(4294967295, 9)") == std::rotr(i, 9)); - CHECK(tep.evaluate("BITRROTATE(4294967295, -1)") == std::rotr(i, -1)); + if constexpr (te_parser::supports_64bit()) + { + constexpr std::uint64_t i = std::numeric_limits::max(); + CHECK(tep.evaluate("0 >>> 0") == std::rotr((uint64_t)0, 0)); + CHECK(tep.evaluate(std::to_string(i) + " >>> 0") == std::rotr(i, 0)); + CHECK(tep.evaluate(std::to_string(i) + " >>> 1") == std::rotr(i, 1)); + CHECK(tep.evaluate(std::to_string(i) + " >>> 4") == std::rotr(i, 4)); + CHECK(tep.evaluate(std::to_string(i) + " >>> 9") == std::rotr(i, 9)); + CHECK(tep.evaluate(std::to_string(i) + " >>> -1") == std::rotr(i, -1)); + + CHECK(tep.evaluate("BITRROTATE64(" + std::to_string(i) + ", 0)") == std::rotr(i, 0)); + CHECK(tep.evaluate("BITRROTATE64(" + std::to_string(i) + ", 1)") == std::rotr(i, 1)); + CHECK(tep.evaluate("BITRROTATE64(" + std::to_string(i) + ", 4)") == std::rotr(i, 4)); + CHECK(tep.evaluate("BITRROTATE64(" + std::to_string(i) + ", 9)") == std::rotr(i, 9)); + CHECK(tep.evaluate("BITRROTATE64(" + std::to_string(i) + ", -1)") == std::rotr(i, -1)); + + CHECK(tep.evaluate("BITRROTATE(0, 0)") == std::rotr((uint64_t)0, 0)); + CHECK(tep.evaluate("BITRROTATE(" + std::to_string(i) + ", 0)") == std::rotr(i, 0)); + CHECK(tep.evaluate("BITRROTATE(" + std::to_string(i) + ", 1)") == std::rotr(i, 1)); + CHECK(tep.evaluate("BITRROTATE(" + std::to_string(i) + ", 4)") == std::rotr(i, 4)); + CHECK(tep.evaluate("BITRROTATE(" + std::to_string(i) + ", 9)") == std::rotr(i, 9)); + CHECK(tep.evaluate("BITRROTATE(" + std::to_string(i) + ", -1)") == std::rotr(i, -1)); + } + else if constexpr (te_parser::supports_32bit()) + { + constexpr std::uint32_t i = std::numeric_limits::max(); + CHECK(tep.evaluate("0 >>> 0") == std::rotr((uint32_t)0, 0)); + CHECK(tep.evaluate("4294967295 >>> 0") == std::rotr(i, 0)); + CHECK(tep.evaluate("4294967295 >>> 1") == std::rotr(i, 1)); + CHECK(tep.evaluate("4294967295 >>> 4") == std::rotr(i, 4)); + CHECK(tep.evaluate("4294967295 >>> 9") == std::rotr(i, 9)); + CHECK(tep.evaluate("4294967295 >>> -1") == std::rotr(i, -1)); + + CHECK(tep.evaluate("BITRROTATE(0, 0)") == std::rotr((uint32_t)0, 0)); + CHECK(tep.evaluate("BITRROTATE(4294967295, 0)") == std::rotr(i, 0)); + CHECK(tep.evaluate("BITRROTATE(4294967295, 1)") == std::rotr(i, 1)); + CHECK(tep.evaluate("BITRROTATE(4294967295, 4)") == std::rotr(i, 4)); + CHECK(tep.evaluate("BITRROTATE(4294967295, 9)") == std::rotr(i, 9)); + CHECK(tep.evaluate("BITRROTATE(4294967295, -1)") == std::rotr(i, -1)); + } } } #endif @@ -2381,16 +2503,20 @@ TEST_CASE("Shift operators", "[shift]") { te_parser tep; - for (uint64_t i = 0; i < te_parser::get_max_integer_bitness()+1; ++i) + for (uint64_t i = 0; i < te_parser::get_max_integer_bitness() + (te_parser::supports_64bit() ? 0 : 1); ++i) { + INFO("Shift: " + std::to_string(i)); CHECK(tep.evaluate((std::string("1 << ") + std::to_string(i)).c_str()) == ((uint64_t)1 << i)); CHECK(tep.evaluate((std::string("1 >> ") + std::to_string(i)).c_str()) == ((uint64_t)1 >> i)); } - for (uint64_t i = 0; i < te_parser::get_max_integer_bitness()+1; ++i) + for (uint64_t i = 0; i < te_parser::get_max_integer_bitness() + (te_parser::supports_64bit() ? -1 : 1); ++i) { + INFO("Shift: " + std::to_string(i)); CHECK(tep.evaluate((std::string("2 << ") + std::to_string(i)).c_str()) == ((uint64_t)2 << i)); CHECK(tep.evaluate((std::string("2 >> ") + std::to_string(i)).c_str()) == ((uint64_t)2 >> i)); } + // will be too large, even for 64-bit + CHECK(std::isnan(tep.evaluate("2 << 63"))); SECTION("BITLSHIFT") { CHECK(tep.evaluate("BITLSHIFT(2, 25)") == 67108864); @@ -2412,10 +2538,18 @@ TEST_CASE("Shift operators", "[shift]") } SECTION("Left") { - CHECK_FALSE(tep.compile("1 << 54")); - CHECK(std::isnan(tep.evaluate())); CHECK(tep.evaluate("0 << 4") == ((uint64_t)0 << 4)); - CHECK(std::isnan(tep.evaluate("1 << 64"))); + if constexpr(te_parser::supports_64bit()) + { + CHECK(tep.compile("1 << 54")); + CHECK(tep.evaluate("1 << 63") == ((uint64_t)1 << 63)); + } + else + { + CHECK_FALSE(tep.compile("1 << 54")); + CHECK(std::isnan(tep.evaluate())); + CHECK(std::isnan(tep.evaluate("1 << 63"))); + } CHECK(std::isnan(tep.evaluate("1 << -5"))); CHECK(tep.evaluate("31 << 53") == ((uint64_t)31 << 53)); // overflow @@ -2441,8 +2575,14 @@ TEST_CASE("Shift operators", "[shift]") SECTION("Right") { CHECK(tep.evaluate("0 >> 4") == ((uint64_t)0 >> 4)); - CHECK(std::isnan(tep.evaluate("1 >> 64"))); - CHECK(std::isnan(tep.get_result())); + if constexpr (te_parser::supports_64bit()) + { + CHECK(tep.evaluate("1 >> 63") == ((uint64_t)1 >> 63)); + } + else + { + CHECK(std::isnan(tep.evaluate("1 >> 63"))); + } CHECK(std::isnan(tep.evaluate("1 >> -5"))); CHECK(std::isnan(tep.get_result())); CHECK(tep.evaluate("32 >> 4") == ((uint64_t)32 >> 4)); diff --git a/tinyexpr.cpp b/tinyexpr.cpp index 0832673..2f98b07 100644 --- a/tinyexpr.cpp +++ b/tinyexpr.cpp @@ -518,6 +518,10 @@ namespace te_builtins [[nodiscard]] static te_type te_right_rotate32(te_type val1, te_type val2) { + if constexpr (!te_parser::supports_32bit()) + { + throw std::runtime_error("32-bit bitwise operations are not supported."); + } if (std::floor(val1) != val1 || std::floor(val2) != val2) { throw std::runtime_error("Bitwise RIGHT ROTATE operation must use integers."); @@ -538,6 +542,10 @@ namespace te_builtins [[nodiscard]] static te_type te_left_rotate32(te_type val1, te_type val2) { + if constexpr (!te_parser::supports_32bit()) + { + throw std::runtime_error("32-bit bitwise operations are not supported."); + } if (std::floor(val1) != val1 || std::floor(val2) != val2) { throw std::runtime_error("Bitwise LEFT ROTATE operation must use integers."); @@ -556,8 +564,12 @@ namespace te_builtins //-------------------------------------------------- [[nodiscard]] - static te_type te_right_rotate(te_type val1, te_type val2) + static te_type te_right_rotate64(te_type val1, te_type val2) { + if constexpr (!te_parser::supports_64bit()) + { + throw std::runtime_error("64-bit bitwise operations are not supported."); + } if (std::floor(val1) != val1 || std::floor(val2) != val2) { throw std::runtime_error("Bitwise RIGHT ROTATE operation must use integers."); @@ -566,10 +578,9 @@ namespace te_builtins { throw std::runtime_error("Bitwise RIGHT ROTATE value must be positive."); } - else if (val2 > te_parser::get_max_integer_bitness()) + else if (val2 > 63) { - throw std::runtime_error("Rotation operation must be between 0-" + - std::to_string(te_parser::get_max_integer_bitness())); + throw std::runtime_error("Rotation operation must be between 0-63"); } return static_cast(std::rotr(static_cast(val1), static_cast(val2))); @@ -577,8 +588,12 @@ namespace te_builtins //-------------------------------------------------- [[nodiscard]] - static te_type te_left_rotate(te_type val1, te_type val2) + static te_type te_left_rotate64(te_type val1, te_type val2) { + if constexpr (!te_parser::supports_64bit()) + { + throw std::runtime_error("64-bit bitwise operations are not supported."); + } if (std::floor(val1) != val1 || std::floor(val2) != val2) { throw std::runtime_error("Bitwise LEFT ROTATE operation must use integers."); @@ -587,15 +602,164 @@ namespace te_builtins { throw std::runtime_error("Bitwise LEFT ROTATE value must be positive."); } - else if (val2 > te_parser::get_max_integer_bitness()) + else if (val2 > 63) { - throw std::runtime_error("Rotation operation must be between 0-" + - std::to_string(te_parser::get_max_integer_bitness())); + throw std::runtime_error("Rotation operation must be between 0-63"); } return static_cast(std::rotl(static_cast(val1), static_cast(val2))); } + + //-------------------------------------------------- + [[nodiscard]] + static te_type te_right_rotate(te_type val1, te_type val2) + { + if constexpr (te_parser::supports_64bit()) + { + return te_right_rotate64(val1, val2); + } + else if constexpr (te_parser::supports_32bit()) + { + return te_right_rotate32(val1, val2); + } + else + { + return te_right_rotate16(val1, val2); + } + } + + //-------------------------------------------------- + [[nodiscard]] + static te_type te_left_rotate(te_type val1, te_type val2) + { + if constexpr (te_parser::supports_64bit()) + { + return te_left_rotate64(val1, val2); + } + else if constexpr (te_parser::supports_32bit()) + { + return te_left_rotate32(val1, val2); + } + else + { + return te_left_rotate16(val1, val2); + } + } #endif + //-------------------------------------------------- + [[nodiscard]] + static te_type te_bitwise_not8(te_type val) + { + if (std::floor(val) != val) + { + throw std::runtime_error("Bitwise NOT must use integers."); + } + else if (val < 0) + { + throw std::runtime_error("Bitwise NOT value must be positive."); + } + else if (val > std::numeric_limits::max()) + { + throw std::runtime_error("Value is too large for bitwise NOT."); + } + + // force the bit manipulation to stay unsigned, like what Excel does + const uint8_t intVal{ static_cast(val) }; + const decltype(intVal) result{ static_cast(~intVal)}; + return static_cast(result); + } + + //-------------------------------------------------- + [[nodiscard]] + static te_type te_bitwise_not16(te_type val) + { + if (std::floor(val) != val) + { + throw std::runtime_error("Bitwise NOT must use integers."); + } + else if (val < 0) + { + throw std::runtime_error("Bitwise NOT value must be positive."); + } + else if (val > std::numeric_limits::max()) + { + throw std::runtime_error("Value is too large for bitwise NOT."); + } + + const uint16_t intVal{ static_cast(val) }; + const decltype(intVal) result{ static_cast(~intVal) }; + return static_cast(result); + } + + //-------------------------------------------------- + [[nodiscard]] + static te_type te_bitwise_not32(te_type val) + { + if constexpr (!te_parser::supports_32bit()) + { + throw std::runtime_error("32-bit bitwise operations are not supported."); + } + if (std::floor(val) != val) + { + throw std::runtime_error("Bitwise NOT must use integers."); + } + else if (val < 0) + { + throw std::runtime_error("Bitwise NOT value must be positive."); + } + else if (val > std::numeric_limits::max()) + { + throw std::runtime_error("Value is too large for bitwise NOT."); + } + + const uint32_t intVal{ static_cast(val) }; + const decltype(intVal) result{ static_cast(~intVal) }; + return static_cast(result); + } + + //-------------------------------------------------- + [[nodiscard]] + static te_type te_bitwise_not64(te_type val) + { + if constexpr (!te_parser::supports_64bit()) + { + throw std::runtime_error("64-bit bitwise operations are not supported."); + } + if (std::floor(val) != val) + { + throw std::runtime_error("Bitwise NOT must use integers."); + } + else if (val < 0) + { + throw std::runtime_error("Bitwise NOT value must be positive."); + } + else if (val > std::numeric_limits::max()) + { + throw std::runtime_error("Value is too large for bitwise NOT."); + } + + const uint64_t intVal{ static_cast(val) }; + const decltype(intVal) result{ static_cast(~intVal) }; + return static_cast(result); + } + + //-------------------------------------------------- + [[nodiscard]] + static te_type te_bitwise_not(te_type val) + { + if constexpr (te_parser::supports_64bit()) + { + return te_bitwise_not64(val); + } + else if constexpr (te_parser::supports_32bit()) + { + return te_bitwise_not32(val); + } + else + { + return te_bitwise_not16(val); + } + } //-------------------------------------------------- [[nodiscard]] @@ -665,6 +829,11 @@ namespace te_builtins [[nodiscard]] static te_type te_left_shift(te_type val1, te_type val2) { + // For 64-bit, you can shift 63 bits. + // If we are limited to something like 53 bits, then we can use that (same as Excel) + constexpr static auto MAX_BITNESS_PARAM = (te_parser::supports_64bit() ? + te_parser::get_max_integer_bitness() - 1 : + te_parser::get_max_integer_bitness()); if (std::floor(val1) != val1) { throw std::runtime_error("Left side of left shift (<<) operation must be an integer."); @@ -682,13 +851,13 @@ namespace te_builtins { throw std::runtime_error("Value is too large for bitwise operation."); } - // bitness is limited to 53-bit, so ensure shift doesn't go beyond that + // bitness is limited to 53-bit or 64-bit, so ensure shift doesn't go beyond that // and cause undefined behavior - else if (val2 < 0 || val2 > te_parser::get_max_integer_bitness()) + else if (val2 < 0 || val2 > MAX_BITNESS_PARAM) { throw std::runtime_error( "Additive expression of left shift (<<) operation must be between 0-" + - std::to_string(te_parser::get_max_integer_bitness())); + std::to_string(MAX_BITNESS_PARAM)); } const auto multipler = (static_cast(1) << static_cast(val2)); @@ -705,6 +874,10 @@ namespace te_builtins [[nodiscard]] static te_type te_right_shift(te_type val1, te_type val2) { + constexpr static auto MAX_BITNESS_PARAM = (te_parser::supports_64bit() ? + te_parser::get_max_integer_bitness() - 1 : + te_parser::get_max_integer_bitness()); + if (std::floor(val1) != val1) { throw std::runtime_error("Left side of right shift (>>) operation must be an integer."); @@ -722,11 +895,11 @@ namespace te_builtins { throw std::runtime_error("Value is too large for bitwise operation."); } - else if (val2 < 0 || val2 > te_parser::get_max_integer_bitness()) + else if (val2 < 0 || val2 > MAX_BITNESS_PARAM) { throw std::runtime_error( "Additive expression of right shift (>>) operation must be between 0-" + - std::to_string(te_parser::get_max_integer_bitness())); + std::to_string(MAX_BITNESS_PARAM)); } return static_cast(static_cast(val1) >> static_cast(val2)); @@ -986,9 +1159,16 @@ const std::set te_parser::m_functions = { // NOLINT { "bitrrotate16", static_cast(te_builtins::te_right_rotate16), TE_PURE }, { "bitlrotate32", static_cast(te_builtins::te_left_rotate32), TE_PURE }, { "bitrrotate32", static_cast(te_builtins::te_right_rotate32), TE_PURE }, + { "bitlrotate64", static_cast(te_builtins::te_left_rotate64), TE_PURE }, + { "bitrrotate64", static_cast(te_builtins::te_right_rotate64), TE_PURE }, { "bitlrotate", static_cast(te_builtins::te_left_rotate), TE_PURE }, { "bitrrotate", static_cast(te_builtins::te_right_rotate), TE_PURE }, #endif + { "bitnot8", static_cast(te_builtins::te_bitwise_not8), TE_PURE }, + { "bitnot16", static_cast(te_builtins::te_bitwise_not16), TE_PURE }, + { "bitnot32", static_cast(te_builtins::te_bitwise_not32), TE_PURE }, + { "bitnot64", static_cast(te_builtins::te_bitwise_not64), TE_PURE }, + { "bitnot", static_cast(te_builtins::te_bitwise_not), 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 }, @@ -1226,6 +1406,18 @@ 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_sub; } +#ifndef TE_FLOAT + else if (tok == '~') + { + theState->m_type = te_parser::state::token_type::TOK_INFIX; + theState->m_value = te_builtins::te_bitwise_not; + } +#else + else if (tok == '~') + { + theState->m_type = te_parser::state::token_type::TOK_ERROR; + } +#endif else if (tok == '*' && (*theState->m_next == '*')) { theState->m_type = te_parser::state::token_type::TOK_INFIX; @@ -1851,28 +2043,44 @@ te_expr* te_parser::power(te_parser::state* theState) /* = {("-" | "+")} */ // highest level of operator precendence int theSign{ 1 }; + bool bitwiseNot{ false }; while (theState->m_type == te_parser::state::token_type::TOK_INFIX && - is_function2(theState->m_value) && - (get_function2(theState->m_value) == te_builtins::te_add || - get_function2(theState->m_value) == te_builtins::te_sub)) + ((is_function2(theState->m_value) && + (get_function2(theState->m_value) == te_builtins::te_add || + get_function2(theState->m_value) == te_builtins::te_sub)) || +#ifndef TE_FLOAT + (is_function1(theState->m_value) && + (get_function1(theState->m_value) == te_builtins::te_bitwise_not)) ) +#endif + ) { - if (get_function2(theState->m_value) == te_builtins::te_sub) + if (is_function2(theState->m_value) && + get_function2(theState->m_value) == te_builtins::te_sub) { theSign = -theSign; } + else if (is_function1(theState->m_value) && + get_function1(theState->m_value) == te_builtins::te_bitwise_not) + { + bitwiseNot = true; + } next_token(theState); } te_expr* ret{ nullptr }; - if (theSign == 1) + if (bitwiseNot) { - ret = base(theState); + ret = new_expr(TE_PURE, te_variant_type(te_builtins::te_bitwise_not), { base(theState) }); } - else + else if (theSign == -1) { ret = new_expr(TE_PURE, te_variant_type(te_builtins::te_negate), { base(theState) }); } + else + { + ret = base(theState); + } return ret; }