Skip to content

Commit

Permalink
Add IFS function (like Excel)
Browse files Browse the repository at this point in the history
  • Loading branch information
Blake-Madden committed Oct 22, 2023
1 parent 6b379d5 commit 5c41d90
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ The following are changes from the original TinyExpr C library:
- `fact`: alias for `fac()`, like the *Excel* function.
- `false`: returns `false` (i.e., `0`) in a boolean expression.
- `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).
- `min`: returns the minimum of a range of values (accepts 1-7 arguments).
- `mod`: returns remainder from a division.
Expand Down
15 changes: 15 additions & 0 deletions docs/manual/09-EndUserUsage.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@ IF(AND(smartMeter1.power > 1,900, sensor1.temperature < 52), TRUE,
```
:::

::: {.minipage data-latex="{\textwidth}"}
The same can be accomplished using the `IFS()` function:

```cpp
IFS(AND(smartMeter1.power > 1900, sensor1.temperature < 52), TRUE,
// First logical check failed, so now check another scenarios
// and return false either meets our criteria.
AND(smartMeter1.power < 0), FALSE,
AND(smartMeter1.power < 300, sensor1.temperature > 55), FALSE)

// NaN is returned if no conditions were met and we are
// in an unknown state.
```
:::
::: {.notesection data-latex=""}
Expressions can optionally begin with an `=`, the same as spreadsheet programs. For example: \
Expand Down
1 change: 1 addition & 0 deletions docs/manual/11-Functions.Rmd
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ Table: Statistical Functions\index{functions!statistical}
| :-- | :-- |
| AND(Value1, Value2, ...) | Returns true if all conditions are true. |
| IF(Condition, ValueIfTrue, ValueIfFalse) | If *Condition* is true (non-zero), then *ValueIfTrue* is returned; otherwise, *ValueIfFalse* is returned.<br>\linebreak Note that multiple `IF` commands can be nested to create a "case" statement\index{case statements}. |
| IFS(Condition1, Value1, Condition2, Value2, ...) | Checks up to three conditions, returning the value corresponding to the first met condition.<br>\linebreak This is shorthand for multiple nested `IF` commands, providing better readability. Will accept 1–3 condition/value pairs.<br>\linebreak NaN will be returned if all conditions are false. |
| NOT(Value) | Returns the logical negation of *Value.* |
| OR(Value1, Value2, ...) | Returns true if any condition is true. |

Expand Down
20 changes: 20 additions & 0 deletions tests/tetests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,26 @@ TEST_CASE("Main tests", "[main]")
{
CHECK(tep.evaluate("if(1, 9, 7)") == 9);
CHECK(tep.evaluate("if(0, 9, 7)") == 7);
CHECK(tep.evaluate("if(0.000001 /* non-zero means true */, 9, 7)") == 9);

// no evaluation statement returns NaN
CHECK(std::isnan(tep.evaluate("IFS()")));
CHECK(std::isnan(tep.evaluate("IFS(0, 9)")));
CHECK(std::isnan(tep.evaluate("IFS(0.000, 9)")));
CHECK(std::isnan(tep.evaluate("IFS(FALSE, 9)")));
CHECK(tep.evaluate("IFS(true, 9, 0.0, 7, 1, 5)") == 9);
CHECK(tep.evaluate("IFS(FALSE, 9, TRUE, 7)") == 7);
CHECK(tep.evaluate("IFS(FALSE, 9, 0.0, 7, 1, 5)") == 5);
// complex expressions
CHECK(tep.evaluate("IFS(9 > 1, 9, 0.0, 7, 1, 5)") == 9);
CHECK(tep.evaluate("IFS(9 > 10, 9, 6 > 9, 6, AND(1 > 0, 1 > -1), 1)") == 1);
// all three expressions are false, so returns NaN
CHECK(std::isnan(tep.evaluate("IFS(false, 9, false, 7, false, 5)")));
// not enough args, so NaN is the default value for the last expression
CHECK(std::isnan(tep.evaluate("IFS(FALSE, 9, 0.0, 7, 1)")));
// NaN "condition" returns NaN
CHECK(std::isnan(tep.evaluate("IFS(NAN, 9)")));

CHECK(tep.evaluate("and(0.0, 5)") == 0);
CHECK(tep.evaluate("and(0.0, 0)") == 0);
CHECK(tep.evaluate("AND(-1, 5)") == 1);
Expand Down
11 changes: 11 additions & 0 deletions tinyexpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,16 @@ static double _or_variadic(double v1, double v2, double v3, double v4,
constexpr static double _if(double a, double b, double c) noexcept
{ return (a != 0.0) ? b : c; }
[[nodiscard]]
constexpr static double _ifs(double if1, double if1True,
double if2, double if2True,
double if3, double if3True) noexcept
{
return (!std::isnan(if1) && if1 != 0.0) ? if1True :
(!std::isnan(if2) && if2 != 0.0) ? if2True :
(!std::isnan(if3) && if3 != 0.0) ? if3True :
te_parser::te_nan;
}
[[nodiscard]]
constexpr static double _false_value() noexcept
{ return 0; }
[[nodiscard]]
Expand Down Expand Up @@ -592,6 +602,7 @@ const std::set<te_variable> te_parser::m_functions = {
{"false", static_cast<te_fun0>(_false_value), TE_PURE},
{"floor", static_cast<te_fun1>(_floor), TE_PURE},
{"if", static_cast<te_fun3>(_if), TE_PURE},
{"ifs", static_cast<te_fun6>(_ifs), static_cast<te_variable_flags>(TE_PURE|TE_VARIADIC)},
{"ln", static_cast<te_fun1>(_log), TE_PURE},
{"log10", static_cast<te_fun1>(_log10), TE_PURE},
{"max", static_cast<te_fun7>(_max), static_cast<te_variable_flags>(TE_PURE|TE_VARIADIC)},
Expand Down

0 comments on commit 5c41d90

Please sign in to comment.