Skip to content

Commit

Permalink
Add bitwise rotation functions
Browse files Browse the repository at this point in the history
Provides 8, 16, 32, and 64-bit functions.
#13
  • Loading branch information
Blake-Madden committed Feb 28, 2024
1 parent 87fb541 commit eb4cfe7
Show file tree
Hide file tree
Showing 8 changed files with 328 additions and 11 deletions.
12 changes: 8 additions & 4 deletions TinyExprChanges.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ 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.
- `bitlshift`: left shift operator.
Negative shift amount arguments (similar to *Excel*) is supported.
- `bitlrotate64`: bitwise (`uint64_t`) left rotate. (Only available if compiled as C++20.)
- `bitlshift`: left shift.
Negative shift amount arguments (similar to *Excel*) are supported.
- `bitor`: bitwise OR.
- `bitrshift`: right shift operator.
Negative shift amount arguments (similar to *Excel*) is supported.
- `bitrrotate64`: bitwise (`uint64_t`) right rotate. (Only available if compiled as C++20.)
- `bitrshift`: right shift.
Negative shift amount arguments (similar to *Excel*) are supported.
- `bitxor`: bitwise XOR.
- `cot`: returns the cotangent of an angle.
- `combin`: alias for `ncr()`, like the *Excel* function.
Expand Down Expand Up @@ -81,6 +83,8 @@ The following are changes from the original TinyExpr C library:
- `>=` greater than or equal to.
- `<<` left shift operator.
- `>>` right shift operator.
- `<<<` left (`uint64_t`) rotation operator.
- `>>>` right (`uint64_t`) rotation operator.
- `**` exponentiation (alias for `^`).
- `round` now supports negative number of digit arguments, similar to *Excel*.
For example, `ROUND(-50.55,-2)` will yield `-100`.
Expand Down
15 changes: 15 additions & 0 deletions docs/manual/compile-time-options.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,18 @@ That will match how many scripting languages do it (e.g., Python, Ruby).

Note that symbols can be defined by passing them to your compiler's
command line (or in a Cmake configuration) as such: `-DTE_POW_FROM_RIGHT`

## C++20 Features

If compiling as C++20, then the following functions and operators will be available:

- `BITLROTATE8`
- `BITLROTATE16`
- `BITLROTATE32`
- `BITLROTATE64`
- `BITRROTATE8`
- `BITRROTATE16`
- `BITRROTATE32`
- `BITRROTATE64`
- `<<<` (unsigned 64-bit left rotation)
- `>>>` (unsigned 64-bit right rotation)
10 changes: 9 additions & 1 deletion docs/manual/functions.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@ The following built-in functions are available:
| 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.) |
| BITLROTATE8(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 8-bit integer.<br>\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.<br>\linebreak *Number* is rotated as an unsigned 16-bit integer.<br>\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.<br>\linebreak *Number* is rotated as an unsigned 32-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITLROTATE64(Number, RotateAmount) | Returns *Number* left rotated left to the most significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 64-bit integer.<br>\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.<br>\linebreak (Only available if compiled as C++20.) |
| 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. |
| BITOR(Number1, Number2) | Returns a bitwise 'OR' of two (integral) numbers. (Both numbers must be positive.) |
| BITRROTATE8(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 8-bit integer.<br>\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.<br>\linebreak *Number* is rotated as an unsigned 16-bit integer.<br>\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.<br>\linebreak *Number* is rotated as an unsigned 32-bit integer.<br>\linebreak (Only available if compiled as C++20.) |
| BITRROTATE64(Number, RotateAmount) | Returns *Number* right rotated right to the least significant bit by the specified number (*RotateAmount*) of bits.<br>\linebreak *Number* is rotated as an unsigned 64-bit integer.<br>\linebreak Note, however, that values beyond the range of `double` should not be used as they will wrap around.<br>\linebreak (Only available if compiled as C++20.) |
| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits. |
| BITXOR(Number1, Number2) | Returns a bitwise 'XOR' of two (integral) numbers. (Both numbers must be positive.) |
| CEIL(Number) | Smallest integer not less than *Number*.<br>\linebreak `CEIL(-3.2)` = -3<br>\linebreak `CEIL(3.2)` = 4 |
| CLAMP(Number, Start, End) | Constrains *Number* within the range of *Start* and *End*. |
Expand Down
4 changes: 3 additions & 1 deletion docs/manual/operators.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ The following operators\index{operators} are supported within math expressions:
| ( ) | Groups sub-expressions, overriding the order of operations. |
| << | 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
:::
Expand All @@ -41,7 +43,7 @@ For operators, the order of precedence is:
| ^ | Exponentiation. |
| \*, /, and % | Multiplication, division, and modulus. |
| \+ and - | Addition and subtraction. |
| << and >> | Bitwise left and right shift. |
| <<, >>, <<<, >>> | Bitwise left and right shift and rotation. |
| <, \>, \>=, <= | Relational comparisons. |
| \= and \!\= | Equality comparisons. |
| && | Logical conjunction (AND). |
Expand Down
8 changes: 4 additions & 4 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
#############################################################################

cmake_minimum_required(VERSION 3.12)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

# Options:
# - USE_ADDRESS_SANITIZE to use ASAN
# - USE_CLANG_TIDY to run clang-tidy
# - TE_FLOAT and TE_POW_FROM_RIGHT to enable the respective preprocessors
# when compiling
# - TE_BITWISE_OPERATIONS to parse ^, &, and | as bitwise operators

project(TETestRunner)

Expand Down Expand Up @@ -68,10 +68,10 @@ if(MSVC)
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC _DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION
$<$<CONFIG:Release>:NDEBUG>)
if(USE_ADDRESS_SANITIZE)
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /MP /W3 /WX /wd4554
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /Zc:__cplusplus /MP /W3 /WX /wd4554
$<$<CONFIG:Debug>:/Od /fsanitize=address> $<$<CONFIG:Release>:/O2>)
else()
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /MP /W3 /WX /wd4554
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /Zc:__cplusplus /MP /W3 /WX /wd4554
$<$<CONFIG:Debug>:/Od> $<$<CONFIG:Release>:/O2>)
endif()
endif()
Expand Down
116 changes: 116 additions & 0 deletions tests/tetests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2176,6 +2176,122 @@ TEST_CASE("Bitwise operators", "[bitwise]")
}
}

#if __cplusplus >= 202002L

TEST_CASE("Rotate operators", "[rotate]")
{
te_parser tep;

SECTION("BITLROTATE8")
{
constexpr uint8_t i = std::numeric_limits<uint8_t>::max();
CHECK(tep.evaluate("BITLROTATE8(0, 0)") == std::rotl((uint8_t)0, 0));
CHECK(tep.evaluate("BITLROTATE8(255, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE8(255, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE8(255, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE8(255, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE8(255, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE16")
{
constexpr uint16_t i = std::numeric_limits<uint16_t>::max();
CHECK(tep.evaluate("BITLROTATE16(0, 0)") == std::rotl((uint16_t)0, 0));
CHECK(tep.evaluate("BITLROTATE16(65535, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE16(65535, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE16(65535, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE16(65535, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE16(65535, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE32")
{
constexpr uint32_t i = std::numeric_limits<uint32_t>::max();
CHECK(tep.evaluate("BITLROTATE32(0, 0)") == std::rotl((uint32_t)0, 0));
CHECK(tep.evaluate("BITLROTATE32(4294967295, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE32(4294967295, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE32(4294967295, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE32(4294967295, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE32(4294967295, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE64")
{
// malformed
CHECK(std::isnan(tep.evaluate("5 <")));
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("BITLROTATE64(0, 0)") == std::rotl((uint64_t)0, 0));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 0)") == std::rotl(i, 0));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 1)") == std::rotl(i, 1));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 4)") == std::rotl(i, 4));
CHECK(tep.evaluate("BITLROTATE64(4294967295, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE64(4294967295, -1)") == std::rotl(i, -1));
}

SECTION("BITRROTATE8")
{
constexpr std::uint8_t i = std::numeric_limits<uint8_t>::max();
CHECK(tep.evaluate("BITRROTATE8(0, 0)") == std::rotr((uint8_t)0, 0));
CHECK(tep.evaluate("BITRROTATE8(255, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE8(255, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE8(255, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE8(255, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE8(255, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE16")
{
constexpr std::uint16_t i = std::numeric_limits<uint16_t>::max();
CHECK(tep.evaluate("BITRROTATE16(0, 0)") == std::rotr((uint16_t)0, 0));
CHECK(tep.evaluate("BITRROTATE16(65535, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE16(65535, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE16(65535, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE16(65535, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE16(65535, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE32")
{
constexpr std::uint32_t i = std::numeric_limits<uint32_t>::max();
CHECK(tep.evaluate("BITRROTATE32(0, 0)") == std::rotr((uint32_t)0, 0));
CHECK(tep.evaluate("BITRROTATE32(4294967295, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE32(4294967295, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE32(4294967295, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE32(4294967295, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE32(4294967295, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE64")
{
// malformed
CHECK(std::isnan(tep.evaluate("5 >")));
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<uint32_t>::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("BITRROTATE64(0, 0)") == std::rotr((uint64_t)0, 0));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 0)") == std::rotr(i, 0));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 1)") == std::rotr(i, 1));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 4)") == std::rotr(i, 4));
CHECK(tep.evaluate("BITRROTATE64(4294967295, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE64(4294967295, -1)") == std::rotr(i, -1));
}
}
#endif

TEST_CASE("Shift operators", "[shift]")
{
te_parser tep;
Expand Down
Loading

0 comments on commit eb4cfe7

Please sign in to comment.