Skip to content

Commit eb4cfe7

Browse files
committed
Add bitwise rotation functions
Provides 8, 16, 32, and 64-bit functions. #13
1 parent 87fb541 commit eb4cfe7

File tree

8 files changed

+328
-11
lines changed

8 files changed

+328
-11
lines changed

TinyExprChanges.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,13 @@ The following are changes from the original TinyExpr C library:
3434
- `and`: returns true (i.e., non-zero) if all conditions are true (accepts 1-7 arguments).
3535
- `average`: returns the mean for a range of values (accepts 1-7 arguments).
3636
- `bitand`: bitwise AND.
37-
- `bitlshift`: left shift operator.
38-
Negative shift amount arguments (similar to *Excel*) is supported.
37+
- `bitlrotate64`: bitwise (`uint64_t`) left rotate. (Only available if compiled as C++20.)
38+
- `bitlshift`: left shift.
39+
Negative shift amount arguments (similar to *Excel*) are supported.
3940
- `bitor`: bitwise OR.
40-
- `bitrshift`: right shift operator.
41-
Negative shift amount arguments (similar to *Excel*) is supported.
41+
- `bitrrotate64`: bitwise (`uint64_t`) right rotate. (Only available if compiled as C++20.)
42+
- `bitrshift`: right shift.
43+
Negative shift amount arguments (similar to *Excel*) are supported.
4244
- `bitxor`: bitwise XOR.
4345
- `cot`: returns the cotangent of an angle.
4446
- `combin`: alias for `ncr()`, like the *Excel* function.
@@ -81,6 +83,8 @@ The following are changes from the original TinyExpr C library:
8183
- `>=` greater than or equal to.
8284
- `<<` left shift operator.
8385
- `>>` right shift operator.
86+
- `<<<` left (`uint64_t`) rotation operator.
87+
- `>>>` right (`uint64_t`) rotation operator.
8488
- `**` exponentiation (alias for `^`).
8589
- `round` now supports negative number of digit arguments, similar to *Excel*.
8690
For example, `ROUND(-50.55,-2)` will yield `-100`.

docs/manual/compile-time-options.qmd

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,18 @@ That will match how many scripting languages do it (e.g., Python, Ruby).
4040

4141
Note that symbols can be defined by passing them to your compiler's
4242
command line (or in a Cmake configuration) as such: `-DTE_POW_FROM_RIGHT`
43+
44+
## C++20 Features
45+
46+
If compiling as C++20, then the following functions and operators will be available:
47+
48+
- `BITLROTATE8`
49+
- `BITLROTATE16`
50+
- `BITLROTATE32`
51+
- `BITLROTATE64`
52+
- `BITRROTATE8`
53+
- `BITRROTATE16`
54+
- `BITRROTATE32`
55+
- `BITRROTATE64`
56+
- `<<<` (unsigned 64-bit left rotation)
57+
- `>>>` (unsigned 64-bit right rotation)

docs/manual/functions.qmd

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,17 @@ The following built-in functions are available:
1111
| ATAN(x) | Returns the principal value of the arc tangent of *x*, expressed in radians.. |
1212
| ATAN2(y, x) | Returns the principal value of the arc tangent of *y*,*x*, expressed in radians. |
1313
| BITAND(Number1, Number2) | Returns a bitwise 'AND' of two (integral) numbers. (Both numbers must be positive.) |
14+
| 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.) |
15+
| 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.) |
16+
| 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.) |
17+
| 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.) |
1418
| BITLSHIFT(Number, ShiftAmount) | Returns *Number* left shifted by the specified number (*ShiftAmount*) of bits. |
15-
| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits. |
1619
| BITOR(Number1, Number2) | Returns a bitwise 'OR' of two (integral) numbers. (Both numbers must be positive.) |
20+
| 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.) |
21+
| 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.) |
22+
| 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.) |
23+
| 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.) |
24+
| BITRSHIFT(Number, ShiftAmount) | Returns *Number* right shifted by the specified number (*ShiftAmount*) of bits. |
1725
| BITXOR(Number1, Number2) | Returns a bitwise 'XOR' of two (integral) numbers. (Both numbers must be positive.) |
1826
| CEIL(Number) | Smallest integer not less than *Number*.<br>\linebreak `CEIL(-3.2)` = -3<br>\linebreak `CEIL(3.2)` = 4 |
1927
| CLAMP(Number, Start, End) | Constrains *Number* within the range of *Start* and *End*. |

docs/manual/operators.qmd

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ The following operators\index{operators} are supported within math expressions:
2727
| ( ) | Groups sub-expressions, overriding the order of operations. |
2828
| << | Bitwise left shift. |
2929
| >> | Bitwise right shift. |
30+
| <<< | Bitwise left rotate. (Only available if compiled as C++20.) |
31+
| >>> | Bitwise right rotate. (Only available if compiled as C++20.) |
3032

3133
Table: Operators
3234
:::
@@ -41,7 +43,7 @@ For operators, the order of precedence is:
4143
| ^ | Exponentiation. |
4244
| \*, /, and % | Multiplication, division, and modulus. |
4345
| \+ and - | Addition and subtraction. |
44-
| << and >> | Bitwise left and right shift. |
46+
| <<, >>, <<<, >>> | Bitwise left and right shift and rotation. |
4547
| <, \>, \>=, <= | Relational comparisons. |
4648
| \= and \!\= | Equality comparisons. |
4749
| && | Logical conjunction (AND). |

tests/CMakeLists.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,14 @@
88
#############################################################################
99

1010
cmake_minimum_required(VERSION 3.12)
11-
set(CMAKE_CXX_STANDARD 17)
11+
set(CMAKE_CXX_STANDARD 20)
1212
set(CMAKE_CXX_STANDARD_REQUIRED True)
1313

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

2020
project(TETestRunner)
2121

@@ -68,10 +68,10 @@ if(MSVC)
6868
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC _DISABLE_VECTOR_ANNOTATION _DISABLE_STRING_ANNOTATION
6969
$<$<CONFIG:Release>:NDEBUG>)
7070
if(USE_ADDRESS_SANITIZE)
71-
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /MP /W3 /WX /wd4554
71+
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /Zc:__cplusplus /MP /W3 /WX /wd4554
7272
$<$<CONFIG:Debug>:/Od /fsanitize=address> $<$<CONFIG:Release>:/O2>)
7373
else()
74-
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /MP /W3 /WX /wd4554
74+
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC /Zc:__cplusplus /MP /W3 /WX /wd4554
7575
$<$<CONFIG:Debug>:/Od> $<$<CONFIG:Release>:/O2>)
7676
endif()
7777
endif()

tests/tetests.cpp

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2176,6 +2176,122 @@ TEST_CASE("Bitwise operators", "[bitwise]")
21762176
}
21772177
}
21782178

2179+
#if __cplusplus >= 202002L
2180+
2181+
TEST_CASE("Rotate operators", "[rotate]")
2182+
{
2183+
te_parser tep;
2184+
2185+
SECTION("BITLROTATE8")
2186+
{
2187+
constexpr uint8_t i = std::numeric_limits<uint8_t>::max();
2188+
CHECK(tep.evaluate("BITLROTATE8(0, 0)") == std::rotl((uint8_t)0, 0));
2189+
CHECK(tep.evaluate("BITLROTATE8(255, 0)") == std::rotl(i, 0));
2190+
CHECK(tep.evaluate("BITLROTATE8(255, 1)") == std::rotl(i, 1));
2191+
CHECK(tep.evaluate("BITLROTATE8(255, 4)") == std::rotl(i, 4));
2192+
CHECK(tep.evaluate("BITLROTATE8(255, 9)") == std::rotl(i, 9));
2193+
CHECK(tep.evaluate("BITLROTATE8(255, -1)") == std::rotl(i, -1));
2194+
}
2195+
SECTION("BITLROTATE16")
2196+
{
2197+
constexpr uint16_t i = std::numeric_limits<uint16_t>::max();
2198+
CHECK(tep.evaluate("BITLROTATE16(0, 0)") == std::rotl((uint16_t)0, 0));
2199+
CHECK(tep.evaluate("BITLROTATE16(65535, 0)") == std::rotl(i, 0));
2200+
CHECK(tep.evaluate("BITLROTATE16(65535, 1)") == std::rotl(i, 1));
2201+
CHECK(tep.evaluate("BITLROTATE16(65535, 4)") == std::rotl(i, 4));
2202+
CHECK(tep.evaluate("BITLROTATE16(65535, 9)") == std::rotl(i, 9));
2203+
CHECK(tep.evaluate("BITLROTATE16(65535, -1)") == std::rotl(i, -1));
2204+
}
2205+
SECTION("BITLROTATE32")
2206+
{
2207+
constexpr uint32_t i = std::numeric_limits<uint32_t>::max();
2208+
CHECK(tep.evaluate("BITLROTATE32(0, 0)") == std::rotl((uint32_t)0, 0));
2209+
CHECK(tep.evaluate("BITLROTATE32(4294967295, 0)") == std::rotl(i, 0));
2210+
CHECK(tep.evaluate("BITLROTATE32(4294967295, 1)") == std::rotl(i, 1));
2211+
CHECK(tep.evaluate("BITLROTATE32(4294967295, 4)") == std::rotl(i, 4));
2212+
CHECK(tep.evaluate("BITLROTATE32(4294967295, 9)") == std::rotl(i, 9));
2213+
CHECK(tep.evaluate("BITLROTATE32(4294967295, -1)") == std::rotl(i, -1));
2214+
}
2215+
SECTION("BITLROTATE64")
2216+
{
2217+
// malformed
2218+
CHECK(std::isnan(tep.evaluate("5 <")));
2219+
CHECK(std::isnan(tep.evaluate("5 <<")));
2220+
CHECK(std::isnan(tep.evaluate("5 <<<")));
2221+
2222+
std::uint64_t i = 4294967295;
2223+
CHECK(tep.evaluate("0 <<< 0") == std::rotl((uint64_t)0, 0));
2224+
CHECK(tep.evaluate("4294967295 <<< 0") == std::rotl(i, 0));
2225+
CHECK(tep.evaluate("4294967295 <<< 1") == std::rotl(i, 1));
2226+
CHECK(tep.evaluate("4294967295 <<< 4") == std::rotl(i, 4));
2227+
CHECK(tep.evaluate("4294967295 <<< 9") == std::rotl(i, 9));
2228+
CHECK(tep.evaluate("4294967295 <<< -1") == std::rotl(i, -1));
2229+
2230+
CHECK(tep.evaluate("BITLROTATE64(0, 0)") == std::rotl((uint64_t)0, 0));
2231+
CHECK(tep.evaluate("BITLROTATE64(4294967295, 0)") == std::rotl(i, 0));
2232+
CHECK(tep.evaluate("BITLROTATE64(4294967295, 1)") == std::rotl(i, 1));
2233+
CHECK(tep.evaluate("BITLROTATE64(4294967295, 4)") == std::rotl(i, 4));
2234+
CHECK(tep.evaluate("BITLROTATE64(4294967295, 9)") == std::rotl(i, 9));
2235+
CHECK(tep.evaluate("BITLROTATE64(4294967295, -1)") == std::rotl(i, -1));
2236+
}
2237+
2238+
SECTION("BITRROTATE8")
2239+
{
2240+
constexpr std::uint8_t i = std::numeric_limits<uint8_t>::max();
2241+
CHECK(tep.evaluate("BITRROTATE8(0, 0)") == std::rotr((uint8_t)0, 0));
2242+
CHECK(tep.evaluate("BITRROTATE8(255, 0)") == std::rotr(i, 0));
2243+
CHECK(tep.evaluate("BITRROTATE8(255, 1)") == std::rotr(i, 1));
2244+
CHECK(tep.evaluate("BITRROTATE8(255, 4)") == std::rotr(i, 4));
2245+
CHECK(tep.evaluate("BITRROTATE8(255, 9)") == std::rotr(i, 9));
2246+
CHECK(tep.evaluate("BITRROTATE8(255, -1)") == std::rotr(i, -1));
2247+
}
2248+
SECTION("BITRROTATE16")
2249+
{
2250+
constexpr std::uint16_t i = std::numeric_limits<uint16_t>::max();
2251+
CHECK(tep.evaluate("BITRROTATE16(0, 0)") == std::rotr((uint16_t)0, 0));
2252+
CHECK(tep.evaluate("BITRROTATE16(65535, 0)") == std::rotr(i, 0));
2253+
CHECK(tep.evaluate("BITRROTATE16(65535, 1)") == std::rotr(i, 1));
2254+
CHECK(tep.evaluate("BITRROTATE16(65535, 4)") == std::rotr(i, 4));
2255+
CHECK(tep.evaluate("BITRROTATE16(65535, 9)") == std::rotr(i, 9));
2256+
CHECK(tep.evaluate("BITRROTATE16(65535, -1)") == std::rotr(i, -1));
2257+
}
2258+
SECTION("BITRROTATE32")
2259+
{
2260+
constexpr std::uint32_t i = std::numeric_limits<uint32_t>::max();
2261+
CHECK(tep.evaluate("BITRROTATE32(0, 0)") == std::rotr((uint32_t)0, 0));
2262+
CHECK(tep.evaluate("BITRROTATE32(4294967295, 0)") == std::rotr(i, 0));
2263+
CHECK(tep.evaluate("BITRROTATE32(4294967295, 1)") == std::rotr(i, 1));
2264+
CHECK(tep.evaluate("BITRROTATE32(4294967295, 4)") == std::rotr(i, 4));
2265+
CHECK(tep.evaluate("BITRROTATE32(4294967295, 9)") == std::rotr(i, 9));
2266+
CHECK(tep.evaluate("BITRROTATE32(4294967295, -1)") == std::rotr(i, -1));
2267+
}
2268+
SECTION("BITRROTATE64")
2269+
{
2270+
// malformed
2271+
CHECK(std::isnan(tep.evaluate("5 >")));
2272+
CHECK(std::isnan(tep.evaluate("5 >>")));
2273+
CHECK(std::isnan(tep.evaluate("5 >>>")));
2274+
2275+
// TODO limited to 32-bit int until possible long double support
2276+
// can be added (at least for some compilers)
2277+
constexpr std::uint64_t i = std::numeric_limits<uint32_t>::max();;
2278+
CHECK(tep.evaluate("0 >>> 0") == std::rotr((uint64_t)0, 0));
2279+
CHECK(tep.evaluate("4294967295 >>> 0") == std::rotr(i, 0));
2280+
CHECK(tep.evaluate("4294967295 >>> 1") == std::rotr(i, 1));
2281+
CHECK(tep.evaluate("4294967295 >>> 4") == std::rotr(i, 4));
2282+
CHECK(tep.evaluate("4294967295 >>> 9") == std::rotr(i, 9));
2283+
CHECK(tep.evaluate("4294967295 >>> -1") == std::rotr(i, -1));
2284+
2285+
CHECK(tep.evaluate("BITRROTATE64(0, 0)") == std::rotr((uint64_t)0, 0));
2286+
CHECK(tep.evaluate("BITRROTATE64(4294967295, 0)") == std::rotr(i, 0));
2287+
CHECK(tep.evaluate("BITRROTATE64(4294967295, 1)") == std::rotr(i, 1));
2288+
CHECK(tep.evaluate("BITRROTATE64(4294967295, 4)") == std::rotr(i, 4));
2289+
CHECK(tep.evaluate("BITRROTATE64(4294967295, 9)") == std::rotr(i, 9));
2290+
CHECK(tep.evaluate("BITRROTATE64(4294967295, -1)") == std::rotr(i, -1));
2291+
}
2292+
}
2293+
#endif
2294+
21792295
TEST_CASE("Shift operators", "[shift]")
21802296
{
21812297
te_parser tep;

0 commit comments

Comments
 (0)