Skip to content

Commit

Permalink
Add value validation to bitwise functions
Browse files Browse the repository at this point in the history
Large integer values will now be limited to 53-bit if that is what the platform (e.g., MSVC) supports.
  • Loading branch information
Blake-Madden committed Mar 6, 2024
1 parent 482da0b commit 7e041d0
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 61 deletions.
37 changes: 22 additions & 15 deletions docs/manual/functions.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,6 @@ 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.) |
| 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. |
| 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*. |
| 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. |
Expand Down Expand Up @@ -51,15 +38,35 @@ The following built-in functions are available:
| SIN(Number) | Sine of the angle *Number* in radians. |
| SINH(Number) | Hyperbolic sine of *Number*. |
| SQRT(Number) | Square root of *Number*. |
| 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. |
| TAN(Number) | Tangent of *Number*. |
| TGAMMA(Number) | Returns the gamma function of *Number*. |
| TRUNC(Number) | Discards the fractional part of *Number*.<br>\linebreak `TRUNC(-3.2)` = -3<br>\linebreak `TRUNC(3.2)` = 3 |

Table: Math Functions\index{functions!math}
:::

::: {.minipage data-latex="{\textwidth}"}
| Function | Description |
| :-- | :-- |
| BITAND(Number1, Number2) | Returns a bitwise 'AND' of two (integral) numbers. (Both numbers must be positive and cannot exceed `(2^48)-1`.) |
| 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.) |
| BITLROTATE(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 53-bit (or 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.<br>\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `64`, if 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.<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.) |
| BITRROTATE(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 53-bit (or 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.<br>\linebreak *Number* cannot exceed `(2^48)-1` and *ShiftAmount* cannot exceed `53` (or `64`, if 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}
:::

::: {.notesection data-latex=""}
Defining `TE_FLOAT` will disable all bitwise functions and operators.
:::
Expand Down
55 changes: 31 additions & 24 deletions tests/tetests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2179,6 +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(23, 10)") == 31);
CHECK(tep.evaluate("BITOR(23, 0)") == (23 | 0));
CHECK(tep.evaluate("BITOR(0, 10)") == (0 | 10));
Expand Down Expand Up @@ -2210,6 +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(5,3)") == 6);
CHECK(tep.evaluate("BITXOR(5,9)") == 12);
CHECK(tep.evaluate("BITXOR(23, 0)") == (23 ^ 0));
Expand All @@ -2233,6 +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(1, 5)") == 1);
CHECK(tep.evaluate("BITAND(13, 25)") == 9);
CHECK(tep.evaluate("BITAND(23, 0)") == (23 & 0));
Expand Down Expand Up @@ -2262,7 +2273,7 @@ TEST_CASE("Rotate operators", "[rotate]")
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(std::isnan(tep.evaluate("BITLROTATE8(255, 9)")));
CHECK(tep.evaluate("BITLROTATE8(255, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE16")
Expand All @@ -2285,7 +2296,7 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("BITLROTATE32(4294967295, 9)") == std::rotl(i, 9));
CHECK(tep.evaluate("BITLROTATE32(4294967295, -1)") == std::rotl(i, -1));
}
SECTION("BITLROTATE64")
SECTION("BITLROTATE")
{
// malformed
CHECK(std::isnan(tep.evaluate("5 <")));
Expand All @@ -2300,12 +2311,12 @@ TEST_CASE("Rotate operators", "[rotate]")
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));
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));
}

SECTION("BITRROTATE8")
Expand All @@ -2315,7 +2326,7 @@ TEST_CASE("Rotate operators", "[rotate]")
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(std::isnan(tep.evaluate("BITRROTATE8(255, 9)")));
CHECK(tep.evaluate("BITRROTATE8(255, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE16")
Expand All @@ -2338,7 +2349,7 @@ TEST_CASE("Rotate operators", "[rotate]")
CHECK(tep.evaluate("BITRROTATE32(4294967295, 9)") == std::rotr(i, 9));
CHECK(tep.evaluate("BITRROTATE32(4294967295, -1)") == std::rotr(i, -1));
}
SECTION("BITRROTATE64")
SECTION("BITRROTATE")
{
// malformed
CHECK(std::isnan(tep.evaluate("5 >")));
Expand All @@ -2355,12 +2366,12 @@ TEST_CASE("Rotate operators", "[rotate]")
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));
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));
}
}
#endif
Expand All @@ -2370,12 +2381,12 @@ TEST_CASE("Shift operators", "[shift]")
{
te_parser tep;

for (uint64_t i = 0; i < 63; ++i)
for (uint64_t i = 0; i < te_parser::get_max_integer_bitness()+1; ++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 < 62; ++i)
for (uint64_t i = 0; i < te_parser::get_max_integer_bitness()+1; ++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));
Expand All @@ -2401,17 +2412,14 @@ TEST_CASE("Shift operators", "[shift]")
}
SECTION("Left")
{
CHECK_FALSE(tep.compile("1 << 64"));
CHECK_FALSE(tep.compile("1 << 54"));
CHECK(std::isnan(tep.evaluate()));
CHECK(tep.get_last_error_message() == "Additive expression of left shift (<<) operation must be between 0-63.");
CHECK(tep.evaluate("0 << 4") == ((uint64_t)0 << 4));
CHECK(std::isnan(tep.evaluate("1 << 64")));
CHECK(std::isnan(tep.evaluate("1 << -5")));
CHECK(tep.get_last_error_message() == "Additive expression of left shift (<<) operation must be between 0-63.");
CHECK(tep.evaluate("31 << 59") == ((uint64_t)31 << 59));
CHECK(tep.evaluate("31 << 53") == ((uint64_t)31 << 53));
// overflow
CHECK(std::isnan(tep.evaluate("32 << 59")));
CHECK(tep.get_last_error_message() == "Overflow in left shift (<<) operation; base number is too large.");
CHECK(std::isnan(tep.evaluate("2 << 63")));
CHECK(std::isnan(tep.evaluate("-1 << 2")));
CHECK(tep.evaluate("1.0 << 4.0") == ((uint64_t)1 << 4));
Expand All @@ -2436,7 +2444,6 @@ TEST_CASE("Shift operators", "[shift]")
CHECK(std::isnan(tep.evaluate("1 >> 64")));
CHECK(std::isnan(tep.get_result()));
CHECK(std::isnan(tep.evaluate("1 >> -5")));
CHECK(tep.get_last_error_message() == "Additive expression of right shift (>>) operation must be between 0-63.");
CHECK(std::isnan(tep.get_result()));
CHECK(tep.evaluate("32 >> 4") == ((uint64_t)32 >> 4));
CHECK(tep.evaluate("32 >> 5") == ((uint64_t)32 >> 5));
Expand Down
Loading

0 comments on commit 7e041d0

Please sign in to comment.