Proposal - Arithmetic Operators #21
Replies: 7 comments 40 replies
-
|
Self Happy to try to account for community feedback. |
Beta Was this translation helpful? Give feedback.
-
|
Should we consider covering test cases for null and other extreme cases [
{
"description": "Addition with null values",
"rule": { "+": [null, 1, null] },
"result": 1,
"data": null
},
{
"description": "Addition with empty string",
"rule": { "+": ["", 1] },
"result": 1,
"data": null
},
{
"description": "Addition with non-numeric strings",
"rule": { "+": ["abc", 1] },
"result": 1,
"data": null
},
{
"description": "Addition with undefined/missing values",
"rule": { "+": [{"var": "missing"}, 1] },
"result": 1,
"data": {}
},
{
"description": "Addition with arrays",
"rule": { "+": [[1,2], 3] },
"result": 3,
"data": null
},
{
"description": "Addition with objects",
"rule": { "+": [{"foo": "bar"}, 1] },
"result": 1,
"data": null
}
] |
Beta Was this translation helpful? Give feedback.
-
|
It's my impression that a lot/most of the JSON Logic operators are “implied |
Beta Was this translation helpful? Give feedback.
-
|
Could you please highlight any differences between this proposal and what's currently defined in JSON Logic? The proposal makes sense, so I'll give it a |
Beta Was this translation helpful? Give feedback.
-
|
Perhaps I've missed it, but I feel like type coercion should be a discussion on its own. Especially when considering future equality operators. I personally would like to avoid coercion as much as possible, since I feel like their use case is rare, easily solved with explicit conversions and that implicit coercion is error prone for users. Additionally, when defining string parsing (preferably explicitly), I think it would be better to accept any valid json number as string (e.g. including scientific notation). |
Beta Was this translation helpful? Give feedback.
-
|
@json-logic/tc Calling for a revote, I've amended this to include error boundaries to answer https://github.com/orgs/json-logic/discussions/21#discussioncomment-11871756 I'm giving this a self +1. (For non-TC members, I'm going to assume the error boundaries did not change the vote unless otherwise announced... based on convos I've had) Current Votes
|
Beta Was this translation helpful? Give feedback.
-
I take this to mean that you're recommending division with a single argument means an implied divisor of 1? I think that there's a stronger argument to be made that this can just as easily be interpreted as having an implicit dividend of 1. That is
This also has functional symmetry with
The identity for Moreover, with this setup,
|
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Background
JSON Logic includes various arithmetic operators, namely:
+,-,/,*and%.This proposal seeks to evaluate these operators and tests to recognize them in JSON Logic Core.
Proposal
As decided in a previous proposal, these operators are being defined with agnostic precision and from a pure mathematical perspective.
These operators are expected to coerce booleans and strings into numeric values.
{ operator: 2 })+0 + X, (ex. 2)*1 * X, (ex. 2)-0 - X, (ex. -2)/1 / X, (ex. 0.5)%The coercion behavior being defined at this time is the following:
stringis parsed as a JSON Number (e.g.1,1.23,1e5should be valid). Empty string becomes 0.truebecomes 1falsebecomes 0nullbecomes 0There are a few changes from mainline json-logic:
%will emit an error when given < 2 arguments./with one argument will perform1 / X, so{ "/": 2 }will emit0.5/and%also support variadic functionality, for consistency.For the purposes of tests, I am not distinguishing between
-0and0to try to remain precision / implementation agnostic.-0does not seem purely mathematical.These operators are variadic, with an implied foldLeft behavior.
{ "+": [1, 2, 3] }is1 + 2 + 3.Controversial: I am marking the tests that use fractional numbers with a
"decimal": trueproperty. The original JSON Logictests.jsondid have a division test that resulted in a fractional number. Normally I'd omit it, but I'll carry the test for it forward as it is relatively precision agnostic, and we are aiming to honor the originaltests.json.If someone chose to implement an integer-based version of JSON Logic Core, they can decide how they want to handle those tests (like truncating).
For our purposes, it changes nothing; it's just an annotation.
Tests
+Operator[ "# Collection of Plus Operator Tests", { "description": "Addition", "rule": { "+": [1, 2] }, "result": 3, "data": null }, { "description": "Addition (2)", "rule": { "+": [5, 12] }, "result": 17, "data": null }, { "description": "Addition with Multiple Operands", "rule": { "+": [1, 2, 3, 4] }, "result": 10, "data": null }, { "description": "Addition with Negative Numbers", "rule": { "+": [-1, 0, 5] }, "result": 4, "data": null }, { "description": "Addition with Strings", "rule": { "+": ["1", "2", "3"] }, "result": 6, "data": null }, { "description": "Addition with Booleans", "rule": { "+": [true, false, true] }, "result": 2, "data": null }, { "description": "Addition with Multiple Value Types", "rule": { "+": [1, "2", 3, "4", "", true, false, null] }, "result": 11, "data": null }, { "description": "Plus Operator with Single Operand (Number)", "rule": { "+": [1] }, "result": 1, "data": null }, { "description": "Plus Operator with Single Operand (Negative Number)", "rule": { "+": [-1] }, "result": -1, "data": null }, { "description": "Plus Operator with zero operands is zero", "rule": { "+": [] }, "result": 0, "data": null }, { "description": "Plus Operator with Single Operand, Direct (Number)", "rule": { "+": 1 }, "result": 1, "data": null }, { "description": "Plus Operator with Single Operand, Direct (0)", "rule": { "+": 0 }, "result": 0, "data": null }, { "description": "Plus Operator with Single Operand (String)", "rule": { "+": ["1"] }, "result": 1, "data": null }, { "description": "Plus Operator with Single Operand, Direct (Negative Number String)", "rule": { "+": "-1" }, "result": -1, "data": null }, { "description": "Plus Operator with Single Operand, Direct (String Decimal)", "rule": { "+": "1.5" }, "result": 1.5, "data": null, "decimal": true }, { "description": "Plus Operator with Single Operand, Direct (String Negative Decimal)", "rule": { "+": "-1.5" }, "result": -1.5, "data": null, "decimal": true }, { "description": "Plus Operator with Single Operand, Direct (String 0.5)", "rule": { "+": "0.5" }, "result": 0.5, "data": null, "decimal": true }, { "description": "Plus Operator with Single Operand, Direct (String 1e2)", "rule": { "+": "1e2" }, "result": 100, "data": null }, { "description": "Plus Operator with Single Operand, Direct (String 0)", "rule": { "+": "0" }, "result": 0, "data": null }, { "description": "Plus Operator with Single Operand, Direct (true)", "rule": { "+": true }, "result": 1, "data": null }, { "description": "Plus Operator with Single Operand, Direct (false)", "rule": { "+": false }, "result": 0, "data": null }, { "description": "Plus Operator with Single Operand, Direct (Empty String)", "rule": { "+": "" }, "result": 0, "data": null }, { "description": "Plus Operator with a Single Operand, Direct (null)", "rule": { "+": null }, "result": 0, "data": null }, { "description": "Addition with val", "rule": { "+": [{ "val": "x" }, { "val": "y" }] }, "result": 3, "data": { "x": 1, "y": 2 } }, { "description": "Addition with string produces NaN", "rule": { "+": ["Hey", 1] }, "error": { "type": "NaN" }, "data": null }, { "description": "Addition with Array produces NaN", "rule": { "+": [[1], 1] }, "error": { "type": "NaN" }, "data": null }, { "description": "Addition with Array from context produces NaN", "rule": { "+": [{ "val": "x" }, 1] }, "error": { "type": "NaN" }, "data": { "x": [1] } }, { "description": "Addition with Object produces NaN", "rule": { "+": [{ "val": "x" }, 1] }, "error": { "type": "NaN" }, "data": { "x": {} } }, { "description": "Plus Operator with Single Operand, Invalid String Produces NaN", "rule": { "+": "Hello" }, "error": { "type": "NaN" }, "data": null }, { "description": "Plus Operator with Single Operand, Array Input Produces NaN", "rule": { "+": [[1]] }, "error": { "type": "NaN" }, "data": null }, { "description": "Plus Operator with Single Operand, Object Input Produces NaN", "rule": { "+": [{}] }, "error": { "type": "NaN" }, "data": null }, { "description": "Plus Operator with Single Operand, Direct Object Input Produces NaN", "rule": { "+": {} }, "error": { "type": "NaN" }, "data": null } ]-Operator[ "# Collection of Minus Operator Tests", { "description": "Subtraction", "rule": { "-": [1, 2] }, "result": -1, "data": null }, { "description": "Subtraction (2)", "rule": { "-": [5, 12] }, "result": -7, "data": null }, { "description": "Subtraction with Multiple Operands", "rule": { "-": [1, 2, 3, 4] }, "result": -8, "data": null }, { "description": "Subtraction with Negative Numbers", "rule": { "-": [-1, 0, 5] }, "result": -6, "data": null }, { "description": "Subtraction with Strings", "rule": { "-": ["1", "2", "3"] }, "result": -4, "data": null }, { "description": "Subtraction with Booleans", "rule": { "-": [true, false, true] }, "result": 0, "data": null }, { "description": "Subtraction with Multiple Value Types", "rule": { "-": [1, "2", 3, "4", "", true, false, null] }, "result": -9, "data": null }, { "description": "Minus Operator with Single Operand (Number)", "rule": { "-": [1] }, "result": -1, "data": null }, { "description": "Minus Operator with Single Operand (Negative Number)", "rule": { "-": [-1] }, "result": 1, "data": null }, { "description": "Minus with zero operands is an error", "rule": { "-": [] }, "error": { "type": "Invalid Arguments" }, "data": null }, { "description": "Minus Operator with Single Operand, Direct (Number)", "rule": { "-": 1 }, "result": -1, "data": null }, { "description": "Minus Operator with Single Operand, Direct (0)", "rule": { "-": 0 }, "result": 0, "data": null }, { "description": "Minus Operator with Single Operand (String)", "rule": { "-": ["1"] }, "result": -1, "data": null }, { "description": "Minus Operator with Single Operand, Direct (Negative Number String)", "rule": { "-": "-1" }, "result": 1, "data": null }, { "description": "Minus Operator with Single Operand, Direct (String 0)", "rule": { "-": "0" }, "result": 0, "data": null }, { "description": "Minus Operator with Single Operand, Direct (true)", "rule": { "-": true }, "result": -1, "data": null }, { "description": "Minus Operator with Single Operand, Direct (false)", "rule": { "-": false }, "result": 0, "data": null }, { "description": "Minus Operator with Single Operand, Direct (Empty String)", "rule": { "-": "" }, "result": 0, "data": null }, { "description": "Minus Operator with a Single Operand, Direct (null)", "rule": { "-": null }, "result": 0, "data": null }, { "description": "Subtraction with val", "rule": { "-": [{ "val": "x" }, { "val": "y" }] }, "data": { "x": 1, "y": 2 }, "result": -1 }, { "description": "Subtraction with string produces NaN", "rule": { "-": ["Hey", 1] }, "error": { "type": "NaN" }, "data": null }, { "description": "Subtraction with Array produces NaN", "rule": { "-": [[1], 1] }, "error": { "type": "NaN" }, "data": null } ]*Operator[ "# Collection of Multiply Operator Tests", { "description": "Multiply", "rule": { "*": [3, 2] }, "result": 6, "data": null }, { "description": "Multiply with Multiple Operands", "rule": { "*": [5, 10, 2] }, "result": 100, "data": null }, { "description": "Multiply with Multiple Operands (2)", "rule": { "*": [2, 2, 2] }, "result": 8, "data": null }, { "description": "Multiply with Negative Numbers", "rule": { "*": [-1, 2, 5] }, "result": -10, "data": null }, { "description": "Multiply with Strings", "rule": { "*": ["1", "2", "3"] }, "result": 6, "data": null }, { "description": "Multiply with Booleans", "rule": { "*": [true, false, true] }, "result": 0, "data": null }, { "description": "Multiply with Multiple Value Types", "rule": { "*": [1, "2", 3, "4", true] }, "result": 24, "data": null }, { "description": "Multiply with Multiple Value Types (2)", "rule": { "*": [1, "1"] }, "result": 1, "data": null }, { "description": "Multiply with Multiple Value Types (3)", "rule": { "*": ["1", null] }, "result": 0, "data": null }, { "description": "Multiply with Single Operand (Number)", "rule": { "*": [1] }, "result": 1, "data": null }, { "description": "Multiply with Single Operand, Direct (Number)", "rule": { "*": 1 }, "result": 1, "data": null }, { "description": "Multiply with Single Operand, Direct (0)", "rule": { "*": 0 }, "result": 0, "data": null }, { "description": "Multiply Operator with Single Operand (Number)", "rule": { "*": [1] }, "result": 1, "data": null }, { "description": "Multiply Operator with Single Operand (Negative Number)", "rule": { "*": [-1] }, "result": -1, "data": null }, { "description": "Multiply with zero operands is 1 (Product)", "rule": { "*": [] }, "result": 1, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (Number)", "rule": { "*": 1 }, "result": 1, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (0)", "rule": { "*": 0 }, "result": 0, "data": null }, { "description": "Multiply Operator with Single Operand (String)", "rule": { "*": ["1"] }, "result": 1, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (Negative Number String)", "rule": { "*": "-1" }, "result": -1, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (String 0)", "rule": { "*": "0" }, "result": 0, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (true)", "rule": { "*": true }, "result": 1, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (false)", "rule": { "*": false }, "result": 0, "data": null }, { "description": "Multiply Operator with Single Operand, Direct (Empty String)", "rule": { "*": "" }, "result": 0, "data": null }, { "description": "Multiply Operator with a Single Operand, Direct (null)", "rule": { "*": null }, "result": 0, "data": null }, { "description": "Multiply with val", "rule": { "*": [{ "val": "x" }, { "val": "y" }] }, "data": { "x": 8, "y": 2 }, "result": 16 }, { "description": "Multiply with string produces NaN", "rule": { "*": ["Hey", 1] }, "error": { "type": "NaN" }, "data": null }, { "description": "Multiply with a single string produces NaN", "rule": { "*": ["Hey"] }, "error": { "type": "NaN" }, "data": null }, { "description": "Multiply with Array produces NaN", "rule": { "*": [[1], 1] }, "error": { "type": "NaN" }, "data": null } ]/Operator[ "# Collection of Divide Operator Tests", { "description": "Divide", "rule": { "/": [4, 2] }, "result": 2, "data": null }, { "description": "Divide to Decimal", "rule": { "/": [2, 4] }, "result": 0.5, "data": null, "decimal": true }, { "description": "Divide with Multiple Operands", "rule": { "/": [8, 2, 2] }, "result": 2, "data": null }, { "description": "Divide with Multiple Operands (2)", "rule": { "/": [2, 2, 1] }, "result": 1, "data": null }, { "description": "Divide with Negative Numbers", "rule": { "/": [-1, 2] }, "result": -0.5, "data": null, "decimal": true }, { "description": "Divide with Strings", "rule": { "/": ["8", "2", "2"] }, "result": 2, "data": null }, { "description": "Divide with Booleans", "rule": { "/": [false, true] }, "result": 0, "data": null }, { "description": "Divide with Multiple Value Types", "rule": { "/": ["8", true, 2] }, "result": 4, "data": null }, { "description": "Divide with Multiple Value Types (2)", "rule": { "/": ["1", 1] }, "result": 1, "data": null }, { "description": "Divide with Single Operand (Number)", "rule": { "/": [1] }, "result": 1, "data": null }, { "description": "Divide with zero operands is an error", "rule": { "/": [] }, "error": { "type": "Invalid Arguments" }, "data": null }, { "description": "Divide with Single Operand, Direct (Number)", "rule": { "/": 1 }, "result": 1, "data": null }, { "description": "Divide with Single Operand, Direct (0)", "rule": { "/": 0 }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide Operator with Single Operand (Number)", "rule": { "/": [1] }, "result": 1, "data": null }, { "description": "Divide Operator with Single Operand (Negative Number)", "rule": { "/": [-1] }, "result": -1, "data": null }, { "description": "Divide Operator with Single Operand, Direct (Number)", "rule": { "/": 1 }, "result": 1, "data": null }, { "description": "Divide Operator with Single Operand, Direct (2)", "rule": { "/": 2 }, "result": 0.5, "decimal": true, "data": null }, { "description": "Divide Operator with Single Operand, Direct (0)", "rule": { "/": 0 }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide Operator with Single Operand (String)", "rule": { "/": ["1"] }, "result": 1, "data": null }, { "description": "Divide Operator with Single Operand, Direct (Negative Number String)", "rule": { "/": "-1" }, "result": -1, "data": null }, { "description": "Divide Operator with Single Operand, Direct (String 0)", "rule": { "/": "0" }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide Operator with Single Operand, Direct (true)", "rule": { "/": true }, "result": 1, "data": null }, { "description": "Divide Operator with Single Operand, Direct (false)", "rule": { "/": false }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide Operator with Single Operand, Direct (Empty String)", "rule": { "/": "" }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide Operator with a Single Operand, Direct (null)", "rule": { "/": null }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide with val", "rule": { "/": [{ "val": "x" }, { "val": "y" }] }, "data": { "x": 8, "y": 2 }, "result": 4 }, { "description": "Divide by Zero", "rule": { "/": [0, 0] }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide with String produces NaN", "rule": { "/": [1, "a"] }, "error": { "type": "NaN" }, "data": null }, { "description": "Divide with Array produces NaN", "rule": { "/": [1, [1]] }, "error": { "type": "NaN" }, "data": null }, { "description": "Any division by zero should return NaN", "rule": { "/": [1, 0] }, "error": { "type": "NaN" }, "data": null }, { "description": "Any division by zero should return NaN (2)", "rule": { "/": [8, 2, 0] }, "error": { "type": "NaN" }, "data": null } ]%Operator[ "# Collection of Modulo Operator Tests", { "description": "Modulo", "rule": { "%": [4, 2] }, "result": 0, "data": null }, { "description": "Modulo (2)", "rule": { "%": [2, 2] }, "result": 0, "data": null }, { "description": "Modulo (3)", "rule": { "%": [3, 2] }, "result": 1, "data": null }, { "description": "Modulo with a Decimal Operand", "rule": { "%": [1, 0.5] }, "result": 0, "data": null, "decimal": true }, { "description": "Modulo with Multiple Operands", "rule": { "%": [8, 6, 3] }, "result": 2, "data": null }, { "description": "Modulo with Multiple Operands (2)", "rule": { "%": [2, 2, 1] }, "result": 0, "data": null }, { "description": "Modulo with Negative Numbers", "rule": { "%": [-1, 2] }, "result": -1, "data": null }, { "description": "Modulo with Strings", "rule": { "%": ["8", "2"] }, "result": 0, "data": null }, { "description": "Modulo with Booleans", "rule": { "%": [false, true] }, "result": 0, "data": null }, { "description": "Modulo with Multiple Value Types", "rule": { "%": ["8", 3, true] }, "result": 0, "data": null }, { "description": "Modulo with Multiple Value Types (2)", "rule": { "%": ["1", 1] }, "result": 0, "data": null }, { "description": "Modulo with Single Operand (Number)", "rule": { "%": [1] }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo with Single Operand, Direct (Number)", "rule": { "%": 1 }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo with Single Operand, Direct (0)", "rule": { "%": 0 }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand (Number)", "rule": { "%": [1] }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand (Negative Number)", "rule": { "%": [-1] }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo with zero operands is an error", "rule": { "%": [] }, "error": { "type": "Invalid Arguments" }, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (Number)", "rule": { "%": 1 }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (0)", "rule": { "%": 0 }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand (String)", "rule": { "%": ["1"] }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (Negative Number String)", "rule": { "%": "-1" }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (String 0)", "rule": { "%": "0" }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (true)", "rule": { "%": true }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (false)", "rule": { "%": false }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with Single Operand, Direct (Empty String)", "rule": { "%": "" }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo Operator with a Single Operand, Direct (null)", "rule": { "%": null }, "error": { "type": "Invalid Arguments"}, "data": null }, { "description": "Modulo with val", "rule": { "%": [{ "val": "x" }, { "val": "y" }] }, "data": { "x": 11, "y": 6 }, "result": 5 }, { "description": "Modulo with string produces NaN", "rule": { "%": ["Hey", 1] }, "error": { "type": "NaN" }, "data": null }, { "description": "Modulo with array produces NaN", "rule": { "%": [[1], 1] }, "error": { "type": "NaN" }, "data": null }, { "description": "Modulo with a negative dividend", "rule": { "%": [-8, 3] }, "result": -2, "data": null }, { "description": "Modulo with a negative divisor", "rule": { "%": [8, -3] }, "result": 2, "data": null } ]Beta Was this translation helpful? Give feedback.
All reactions