Skip to content

Commit

Permalink
Add EVEN, ODD, ISEVEN, and ISODD functions
Browse files Browse the repository at this point in the history
These are the same as the Excel functions.
  • Loading branch information
Blake-Madden committed Mar 20, 2024
1 parent d8e24d9 commit 424a7e9
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 24 deletions.
4 changes: 4 additions & 0 deletions TinyExprChanges.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,19 @@ The following are changes from the original TinyExpr C library:
- `cot`: returns the cotangent of an angle.
- `combin`: alias for `ncr()`, like the *Excel* function.
- `clamp`: constrains a value to a range.
- `even`: returns a value rounded up to the nearest even integer.
- `fact`: alias for `fac()`, like the *Excel* function.
- `false`: returns `false` (i.e., `0`) in a boolean expression.
- `iseven`: returns true if a number is even, false if odd.
- `isodd`: returns true if a number is odd, false if even.
- `if`: if a value is true (i.e., non-zero), then returns the second argument; otherwise, returns the third argument.
- `ifs`: checks up to three conditions, returning the value corresponding to the first met condition.
- `max`: returns the maximum of a range of values (accepts 1-7 arguments).
- `maxint`: returns the largest integer value that the parser can store.
- `min`: returns the minimum of a range of values (accepts 1-7 arguments).
- `mod`: returns remainder from a division.
- `nan`: returns `NaN` (i.e., Not-a-Number) in a boolean expression.
- `odd`: returns a value rounded up to the nearest odd integer.
- `or`: returns true (i.e., non-zero) if any condition is true (accepts 1-7 arguments).
- `not`: returns logical negation of value.
- `permut`: alias for `npr()`, like the *Excel* function.
Expand Down
4 changes: 4 additions & 0 deletions docs/manual/functions.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ The following built-in functions are available:
| COSH(Number) | Hyperbolic cosine of *Number*. |
| COT(Number) | Cotangent of *Number*. |
| EXP(Number) | Euler to the power of *Number*. |
| EVEN(Number) | Returns *Number* rounded up to the nearest even integer.<br>\linebreak *Number* is always rounded away from zero (e.g., `EVEN(-3)` = -4). |
| FAC(Number) | Returns the factorial of *Number*. The factorial of *Number* is equal to 1\*2\*3\*...\* *Number* |
| FACT(Number) | Alias for `FAC()` |
| FLOOR(Number) | Returns the largest integer not greater than *Number*.<br>\linebreak `FLOOR(-3.2)` = -4<br>\linebreak `FLOOR(3.2)` = 3 |
| ISEVEN(Number) | Returns true if *Number* is even, false if odd. |
| ISODD(Number) | Returns true if *Number* is odd, false if even. |
| LN(Number) | Natural logarithm of *Number* (base Euler). |
| LOG10(Number) | Common logarithm of *Number* (base 10). |
| MIN(Number1, Number2, ...) | Returns the smallest value from a specified range of values. |
Expand All @@ -29,6 +32,7 @@ The following built-in functions are available:
| NAN | Returns an invalid value (i.e., Not-a-number). |
| NCR(Number, NumberChosen) | Alias for `COMBIN()`. |
| NPR(Number, NumberChosen) | Alias for `PERMUT()`. |
| ODD(Number) | Returns *Number* rounded up to the nearest odd integer.<br>\linebreak *Number* is always rounded away from zero (e.g., `ODD(-4)` = -5). |
| PERMUT(Number, NumberChosen) | Returns the number of permutations for a given number (*NumberChosen*) of items that can be selected *Number* of items. A permutation is any set of items where order is important. (This differs from combinations, where order is not important). |
| POW(Base, Exponent) | Raises *Base* to any power. For fractional exponents, *Base* must be greater than 0. |
| POWER(Base, Exponent) | Alias for `POW()`. |
Expand Down
42 changes: 42 additions & 0 deletions tests/tetests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,48 @@ TEST_CASE("NaN", "[nan]")
CHECK(tep.success());
}

TEST_CASE("Even", "[even]")
{
te_parser tep;

CHECK(tep.evaluate("=EVEN(1.5)") == 2);
CHECK(tep.evaluate("=EVEN(3)") == 4);
CHECK(tep.evaluate("=EVEN(2)") == 2);
CHECK(tep.evaluate("=EVEN(-1)") == -2);
CHECK(tep.evaluate("=EVEN(-3)") == -4);
CHECK(tep.evaluate("=EVEN(-3.7)") == -4);
CHECK(tep.evaluate("=EVEN(-3.1)") == -4);
CHECK(tep.evaluate("=EVEN(0)") == 0);
CHECK(std::isnan(tep.evaluate("=EVEN(NAN)")));

CHECK_FALSE(tep.evaluate("ISEVEN(-1)"));
CHECK(tep.evaluate("=ISEVEN(2.5)"));
CHECK_FALSE(tep.evaluate("=ISEVEN(5)"));
CHECK(tep.evaluate("=ISEVEN(0)"));
CHECK(tep.evaluate("=ISEVEN(40900)"));
CHECK(std::isnan(tep.evaluate("=ISEVEN(NAN)")));
}

TEST_CASE("Odd", "[odd]")
{
te_parser tep;

CHECK(tep.evaluate("=ODD(1.5)") == 3);
CHECK(tep.evaluate("=ODD(3)") == 3);
CHECK(tep.evaluate("=ODD(2)") == 3);
CHECK(tep.evaluate("=ODD(-1)") == -1);
CHECK(tep.evaluate("=ODD(-2)") == -3);
CHECK(tep.evaluate("=ODD(-4)") == -5);
CHECK(tep.evaluate("=ODD(0)") == 1);
CHECK(std::isnan(tep.evaluate("=ODD(NAN)")));

CHECK(tep.evaluate("ISODD(-1)"));
CHECK_FALSE(tep.evaluate("=ISODD(2.5)"));
CHECK_FALSE(tep.evaluate("=ISODD(0)"));
CHECK(tep.evaluate("=ISODD(5)"));
CHECK(std::isnan(tep.evaluate("=ISODD(NAN)")));
}

TEST_CASE("Zeros", "[zeros]")
{
te_parser tep;
Expand Down
104 changes: 80 additions & 24 deletions tinyexpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,82 @@
// builtin functions
namespace te_builtins
{
[[nodiscard]]
constexpr static te_type te_false_value() noexcept
{
return 0;
}

[[nodiscard]]
constexpr static te_type te_true_value() noexcept
{
return 1;
}

[[nodiscard]]
constexpr static te_type te_nan_value() noexcept
{
return te_parser::te_nan;
}

[[nodiscard]]
static te_type te_max_integer() noexcept
{
return te_parser::get_max_integer();
}

[[nodiscard]]
static te_type te_even(te_type val)
{
if (!std::isfinite(val))
{
return te_parser::te_nan;
}
int64_t rounded{ static_cast<int64_t>(std::ceil(std::abs(val))) };
if ((rounded % 2) != 0)
{
++rounded;
}
return (val < 0 ? -(static_cast<te_type>(rounded)) : static_cast<te_type>(rounded));
}

[[nodiscard]]
static te_type te_odd(te_type val)
{
if (!std::isfinite(val))
{
return te_parser::te_nan;
}
int64_t rounded{ static_cast<int64_t>(std::ceil(std::abs(val))) };
if ((rounded % 2) == 0)
{
++rounded;
}
return (val < 0 ? -(static_cast<te_type>(rounded)) : static_cast<te_type>(rounded));
}

[[nodiscard]]
static te_type te_is_even(te_type val)
{
if (!std::isfinite(val))
{
return te_parser::te_nan;
}
const int64_t floored{ static_cast<int64_t>(std::floor(val)) };
return ((floored % 2) == 0 ? te_true_value() : te_false_value());
}

[[nodiscard]]
static te_type te_is_odd(te_type val)
{
if (!std::isfinite(val))
{
return te_parser::te_nan;
}
const int64_t floored{ static_cast<int64_t>(std::floor(val)) };
return ((floored % 2) != 0 ? te_true_value() : te_false_value());
}

[[nodiscard]]
constexpr static te_type te_equal(te_type val1, te_type val2) noexcept
{
Expand Down Expand Up @@ -1043,30 +1119,6 @@ namespace te_builtins
te_parser::te_nan;
}

[[nodiscard]]
constexpr static te_type te_false_value() noexcept
{
return 0;
}

[[nodiscard]]
constexpr static te_type te_true_value() noexcept
{
return 1;
}

[[nodiscard]]
constexpr static te_type te_nan_value() noexcept
{
return te_parser::te_nan;
}

[[nodiscard]]
static te_type te_max_integer() noexcept
{
return te_parser::get_max_integer();
}

[[nodiscard]]
constexpr static te_type te_supports_32bit() noexcept
{
Expand Down Expand Up @@ -1187,11 +1239,14 @@ const std::set<te_variable> te_parser::m_functions = { // NOLINT
{ "cosh", static_cast<te_fun1>(te_builtins::te_cosh), TE_PURE },
{ "cot", static_cast<te_fun1>(te_builtins::te_cot), TE_PURE },
{ "e", static_cast<te_fun0>(te_builtins::te_e), TE_PURE },
{ "even", static_cast<te_fun1>(te_builtins::te_even), TE_PURE },
{ "exp", static_cast<te_fun1>(te_builtins::te_exp), TE_PURE },
{ "fac", static_cast<te_fun1>(te_builtins::te_fac), TE_PURE },
{ "fact", static_cast<te_fun1>(te_builtins::te_fac), TE_PURE },
{ "false", static_cast<te_fun0>(te_builtins::te_false_value), TE_PURE },
{ "floor", static_cast<te_fun1>(te_builtins::te_floor), TE_PURE },
{ "iseven", static_cast<te_fun1>(te_builtins::te_is_even), TE_PURE },
{ "isodd", static_cast<te_fun1>(te_builtins::te_is_odd), TE_PURE },
{ "if", static_cast<te_fun3>(te_builtins::te_if), TE_PURE },
{ "ifs", static_cast<te_fun6>(te_builtins::te_ifs),
static_cast<te_variable_flags>(TE_PURE | TE_VARIADIC) },
Expand All @@ -1207,6 +1262,7 @@ const std::set<te_variable> te_parser::m_functions = { // NOLINT
{ "ncr", static_cast<te_fun2>(te_builtins::te_ncr), TE_PURE },
{ "not", static_cast<te_fun1>(te_builtins::te_not), TE_PURE },
{ "npr", static_cast<te_fun2>(te_builtins::te_npr), TE_PURE },
{ "odd", static_cast<te_fun1>(te_builtins::te_odd), TE_PURE },
{ "or", static_cast<te_fun7>(te_builtins::te_or_variadic),
static_cast<te_variable_flags>(TE_PURE | TE_VARIADIC) },
{ "permut", static_cast<te_fun2>(te_builtins::te_npr), TE_PURE },
Expand Down

0 comments on commit 424a7e9

Please sign in to comment.