diff --git a/doc/langdef.md b/doc/langdef.md index 2e9b3d4..1223f5d 100644 --- a/doc/langdef.md +++ b/doc/langdef.md @@ -1073,9 +1073,9 @@ interpretation. #### Numbers The numeric types of `int`, `uint`, and `double` are compared as though they -exist on a continuous number line where two numbers `x` and `y` are equal if -`!(x < y || x > y)`. Since it is possible to compare numeric types without -type conversion, CEL uses this definition for `numericEquals` to support +exist on a continuous number line where two non-`NaN` numbers `x` and `y` are +equal if `!(x < y || x > y)`. Since it is possible to compare numeric types +without type conversion, CEL uses this definition for `numericEquals` to support comparison across numeric types. This property of cross-type numeric equality is essential for supporting @@ -1097,8 +1097,10 @@ json.number in [1, 2, 3] int(json.number) in [1, 2, 3] ``` -The `double` type follows the IEEE 754 standard. Not-a-number (`NaN`) values -compare as unequal, e.g. `NaN == NaN // false` and `NaN != NaN // true`. +The `double` type follows the IEEE 754 standard. Negative zero (`-0.0`) +compares equal to positive zero (`0.0`, as well as `0` and `0u`), and +not-a-number (`NaN`) values compare as unequal to all values, including +themselves, e.g. `NaN == NaN // false` and `NaN != NaN // true`. #### Lists and Maps @@ -1187,7 +1189,9 @@ strings are encoded in UTF-8, strings consequently also obey lexicographic ordering of their Unicode code points. The ordering operators obey the usual algebraic properties, i.e. `e1 <= e2` -gives the same result as `!(e1 > e2)` as well as `(e1 < e2) || (e1 == e2)`. +gives the same result as `(e1 < e2) || (e1 == e2)`, and for non-`NaN` values, +`!(e1 > e2)`. Following IEEE 754, any ordering comparison (`<`, `<=`, `>`, +`>=`) involving `NaN` evaluates to `false`. ### Overflow diff --git a/tests/simple/testdata/comparisons.textproto b/tests/simple/testdata/comparisons.textproto index e712196..6cbf729 100644 --- a/tests/simple/testdata/comparisons.textproto +++ b/tests/simple/testdata/comparisons.textproto @@ -81,16 +81,36 @@ section { expr: "0.0/0.0 == 0.0/0.0" value: { bool_value: false } } + test { + name: "not_eq_double_nan_left" + expr: "0.0/0.0 == 1.0" + value: { bool_value: false } + } + test { + name: "not_eq_double_nan_right" + expr: "1.0 == 0.0/0.0" + value: { bool_value: false } + } test { name: "not_eq_int_double_nan" expr: "dyn(1) == 0.0/0.0" value: { bool_value: false } } + test { + name: "not_eq_double_nan_int" + expr: "dyn(0.0/0.0) == 1" + value: { bool_value: false } + } test { name: "not_eq_uint_double_nan" expr: "dyn(1u) == 0.0/0.0" value: { bool_value: false } } + test { + name: "not_eq_double_nan_uint" + expr: "dyn(0.0/0.0) == 1u" + value: { bool_value: false } + } test { name: "eq_double_int" expr: "dyn(1.0) == 1" @@ -111,6 +131,36 @@ section { expr: "dyn(2.0) == 1u" value: { bool_value: false } } + test { + name: "eq_double_neg_zero" + expr: "-0.0 == 0.0" + value: { bool_value: true } + } + test { + name: "eq_double_zero_neg_zero" + expr: "0.0 == -0.0" + value: { bool_value: true } + } + test { + name: "eq_double_neg_zero_int" + expr: "dyn(-0.0) == 0" + value: { bool_value: true } + } + test { + name: "eq_int_double_neg_zero" + expr: "dyn(0) == -0.0" + value: { bool_value: true } + } + test { + name: "eq_double_neg_zero_uint" + expr: "dyn(-0.0) == 0u" + value: { bool_value: true } + } + test { + name: "eq_uint_double_neg_zero" + expr: "dyn(0u) == -0.0" + value: { bool_value: true } + } test { name: "eq_string" expr: "'' == \"\"" @@ -203,6 +253,41 @@ section { expr: "[1.0, 2.1] == [1u, 2]" value: { bool_value: false } } + test { + name: "not_eq_list_double_nan" + expr: "[0.0/0.0] == [0.0/0.0]" + value: { bool_value: false } + } + test { + name: "not_eq_nested_list_double_nan" + expr: "[[0.0/0.0]] == [[0.0/0.0]]" + value: { bool_value: false } + } + test { + name: "not_eq_nested_list_map_double_nan" + expr: "[{'k': [0.0/0.0]}] == [{'k': [0.0/0.0]}]" + value: { bool_value: false } + } + test { + name: "eq_list_double_neg_zero" + expr: "[-0.0] == [0.0]" + value: { bool_value: true } + } + test { + name: "eq_list_mixed_type_neg_zero" + expr: "[-0.0, 1] == [0, 1.0]" + value: { bool_value: true } + } + test { + name: "eq_list_mixed_type_neg_zero_uint" + expr: "[-0.0, 1u] == [0u, 1.0]" + value: { bool_value: true } + } + test { + name: "eq_nested_list_mixed_type_neg_zero" + expr: "[[-0.0, 1]] == [[0, 1u]]" + value: { bool_value: true } + } test { name: "not_eq_list_order" expr: "[1, 2, 3] == [1, 3, 2]" @@ -249,6 +334,36 @@ section { expr: "{'k':1.0} == {'k':1e+0}" value: { bool_value: true } } + test { + name: "not_eq_map_double_nan" + expr: "{'k': 0.0/0.0} == {'k': 0.0/0.0}" + value: { bool_value: false } + } + test { + name: "not_eq_nested_map_list_double_nan" + expr: "{'k': [0.0/0.0, {'a': 1.0}]} == {'k': [0.0/0.0, {'a': 1.0}]}" + value: { bool_value: false } + } + test { + name: "eq_map_double_neg_zero" + expr: "{'k': -0.0} == {'k': 0.0}" + value: { bool_value: true } + } + test { + name: "eq_map_mixed_type_neg_zero" + expr: "{1: -0.0, 2: 0} == {1: 0, 2: -0.0}" + value: { bool_value: true } + } + test { + name: "eq_map_mixed_type_neg_zero_uint" + expr: "{1: -0.0, 2: 0u} == {1: 0u, 2: -0.0}" + value: { bool_value: true } + } + test { + name: "eq_nested_map_list_neg_zero" + expr: "{'k': [-0.0, {'a': -0.0, 'b': 1}]} == {'k': [0, {'a': 0u, 'b': 1.0}]}" + value: { bool_value: true } + } test { name: "eq_map_mixed_type_numbers" expr: "{1: 1.0, 2u: 3u} == {1u: 1, 2: 3.0}" @@ -870,16 +985,36 @@ section { expr: "0.0/0.0 != 0.0/0.0" value: { bool_value: true } } + test { + name: "ne_double_nan_left" + expr: "0.0/0.0 != 1.0" + value: { bool_value: true } + } + test { + name: "ne_double_nan_right" + expr: "1.0 != 0.0/0.0" + value: { bool_value: true } + } test { name: "not_ne_int_double_nan" expr: "dyn(1) != 0.0/0.0" value: { bool_value: true } } + test { + name: "ne_double_nan_int" + expr: "dyn(0.0/0.0) != 1" + value: { bool_value: true } + } test { name: "not_ne_uint_double_nan" expr: "dyn(1u) != 0.0/0.0" value: { bool_value: true } } + test { + name: "ne_double_nan_uint" + expr: "dyn(0.0/0.0) != 1u" + value: { bool_value: true } + } test { name: "not_ne_double" expr: "1.0 != 1e+0" @@ -905,6 +1040,36 @@ section { expr: "dyn(1u) != 1e+0" value: { bool_value: false } } + test { + name: "not_ne_double_neg_zero" + expr: "-0.0 != 0.0" + value: { bool_value: false } + } + test { + name: "not_ne_double_zero_neg_zero" + expr: "0.0 != -0.0" + value: { bool_value: false } + } + test { + name: "not_ne_double_neg_zero_int" + expr: "dyn(-0.0) != 0" + value: { bool_value: false } + } + test { + name: "not_ne_int_double_neg_zero" + expr: "dyn(0) != -0.0" + value: { bool_value: false } + } + test { + name: "not_ne_double_neg_zero_uint" + expr: "dyn(-0.0) != 0u" + value: { bool_value: false } + } + test { + name: "not_ne_uint_double_neg_zero" + expr: "dyn(0u) != -0.0" + value: { bool_value: false } + } test { name: "ne_double_nan" expr: "0.0/0.0 != 0.0/0.0" @@ -981,6 +1146,41 @@ section { expr: "[[]] != [[]]" value: { bool_value: false } } + test { + name: "ne_list_double_nan" + expr: "[0.0/0.0] != [0.0/0.0]" + value: { bool_value: true } + } + test { + name: "ne_nested_list_double_nan" + expr: "[[0.0/0.0]] != [[0.0/0.0]]" + value: { bool_value: true } + } + test { + name: "ne_nested_list_map_double_nan" + expr: "[{'k': [0.0/0.0]}] != [{'k': [0.0/0.0]}]" + value: { bool_value: true } + } + test { + name: "not_ne_list_double_neg_zero" + expr: "[-0.0] != [0.0]" + value: { bool_value: false } + } + test { + name: "not_ne_list_mixed_type_neg_zero" + expr: "[-0.0, 1] != [0, 1.0]" + value: { bool_value: false } + } + test { + name: "not_ne_list_mixed_type_neg_zero_uint" + expr: "[-0.0, 1u] != [0u, 1.0]" + value: { bool_value: false } + } + test { + name: "not_ne_nested_list_mixed_type_neg_zero" + expr: "[[-0.0, 1]] != [[0, 1u]]" + value: { bool_value: false } + } test { name: "ne_map_by_value" expr: "{'k':'v'} != {'k':'v1'}" @@ -996,6 +1196,36 @@ section { expr: "{1:1.0} != {1:1.0}" value: { bool_value: false } } + test { + name: "ne_map_double_nan" + expr: "{'k': 0.0/0.0} != {'k': 0.0/0.0}" + value: { bool_value: true } + } + test { + name: "ne_nested_map_list_double_nan" + expr: "{'k': [0.0/0.0, {'a': 1.0}]} != {'k': [0.0/0.0, {'a': 1.0}]}" + value: { bool_value: true } + } + test { + name: "not_ne_map_double_neg_zero" + expr: "{'k': -0.0} != {'k': 0.0}" + value: { bool_value: false } + } + test { + name: "not_ne_map_mixed_type_neg_zero" + expr: "{1: -0.0, 2: 0} != {1: 0, 2: -0.0}" + value: { bool_value: false } + } + test { + name: "not_ne_map_mixed_type_neg_zero_uint" + expr: "{1: -0.0, 2: 0u} != {1: 0u, 2: -0.0}" + value: { bool_value: false } + } + test { + name: "not_ne_nested_map_list_neg_zero" + expr: "{'k': [-0.0, {'a': -0.0, 'b': 1}]} != {'k': [0, {'a': 0u, 'b': 1.0}]}" + value: { bool_value: false } + } test { name: "not_ne_map_key_order" expr: "{'a':'b','c':'d'} != {'c':'d','a':'b'}" @@ -1122,6 +1352,66 @@ section { expr: "-0.0 < 0.0" value: { bool_value: false } } + test { + name: "not_lt_double_zero_neg_zero" + expr: "0.0 < -0.0" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_double_neg_zero_int" + expr: "dyn(-0.0) < 0" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_int_double_neg_zero" + expr: "dyn(0) < -0.0" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_double_neg_zero_uint" + expr: "dyn(-0.0) < 0u" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_uint_double_neg_zero" + expr: "dyn(0u) < -0.0" + value: { bool_value: false } + } + test { + name: "not_lt_double_nan" + expr: "0.0/0.0 < 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_lt_double_nan_left" + expr: "0.0/0.0 < 1.0" + value: { bool_value: false } + } + test { + name: "not_lt_double_nan_right" + expr: "1.0 < 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_double_nan_int" + expr: "dyn(0.0/0.0) < 1" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_int_double_nan" + expr: "dyn(1) < 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_double_nan_uint" + expr: "dyn(0.0/0.0) < 1u" + value: { bool_value: false } + } + test { + name: "not_lt_dyn_uint_double_nan" + expr: "dyn(1u) < 0.0/0.0" + value: { bool_value: false } + } test { name: "lt_string" expr: "'a' < 'b'" @@ -1392,6 +1682,71 @@ section { expr: ".99 > 9.9e-1" value: { bool_value: false } } + test { + name: "not_gt_double_neg_zero" + expr: "-0.0 > 0.0" + value: { bool_value: false } + } + test { + name: "not_gt_double_zero_neg_zero" + expr: "0.0 > -0.0" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_double_neg_zero_int" + expr: "dyn(-0.0) > 0" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_int_double_neg_zero" + expr: "dyn(0) > -0.0" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_double_neg_zero_uint" + expr: "dyn(-0.0) > 0u" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_uint_double_neg_zero" + expr: "dyn(0u) > -0.0" + value: { bool_value: false } + } + test { + name: "not_gt_double_nan" + expr: "0.0/0.0 > 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_gt_double_nan_left" + expr: "0.0/0.0 > 1.0" + value: { bool_value: false } + } + test { + name: "not_gt_double_nan_right" + expr: "1.0 > 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_double_nan_int" + expr: "dyn(0.0/0.0) > 1" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_int_double_nan" + expr: "dyn(1) > 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_double_nan_uint" + expr: "dyn(0.0/0.0) > 1u" + value: { bool_value: false } + } + test { + name: "not_gt_dyn_uint_double_nan" + expr: "dyn(1u) > 0.0/0.0" + value: { bool_value: false } + } test { name: "gt_string_case" expr: "'abc' > 'aBc'" @@ -1644,6 +1999,71 @@ section { expr: "1.0 <= 0.99" value: { bool_value: false } } + test { + name: "lte_double_neg_zero" + expr: "-0.0 <= 0.0" + value: { bool_value: true } + } + test { + name: "lte_double_zero_neg_zero" + expr: "0.0 <= -0.0" + value: { bool_value: true } + } + test { + name: "lte_dyn_double_neg_zero_int" + expr: "dyn(-0.0) <= 0" + value: { bool_value: true } + } + test { + name: "lte_dyn_int_double_neg_zero" + expr: "dyn(0) <= -0.0" + value: { bool_value: true } + } + test { + name: "lte_dyn_double_neg_zero_uint" + expr: "dyn(-0.0) <= 0u" + value: { bool_value: true } + } + test { + name: "lte_dyn_uint_double_neg_zero" + expr: "dyn(0u) <= -0.0" + value: { bool_value: true } + } + test { + name: "not_lte_double_nan" + expr: "0.0/0.0 <= 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_lte_double_nan_left" + expr: "0.0/0.0 <= 1.0" + value: { bool_value: false } + } + test { + name: "not_lte_double_nan_right" + expr: "1.0 <= 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_lte_dyn_double_nan_int" + expr: "dyn(0.0/0.0) <= 1" + value: { bool_value: false } + } + test { + name: "not_lte_dyn_int_double_nan" + expr: "dyn(1) <= 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_lte_dyn_double_nan_uint" + expr: "dyn(0.0/0.0) <= 1u" + value: { bool_value: false } + } + test { + name: "not_lte_dyn_uint_double_nan" + expr: "dyn(1u) <= 0.0/0.0" + value: { bool_value: false } + } test { name: "lte_string_empty" expr: "'' <= ''" @@ -1906,6 +2326,71 @@ section { expr: "0.9999 >= 1.0" value: { bool_value: false } } + test { + name: "gte_double_neg_zero" + expr: "-0.0 >= 0.0" + value: { bool_value: true } + } + test { + name: "gte_double_zero_neg_zero" + expr: "0.0 >= -0.0" + value: { bool_value: true } + } + test { + name: "gte_dyn_double_neg_zero_int" + expr: "dyn(-0.0) >= 0" + value: { bool_value: true } + } + test { + name: "gte_dyn_int_double_neg_zero" + expr: "dyn(0) >= -0.0" + value: { bool_value: true } + } + test { + name: "gte_dyn_double_neg_zero_uint" + expr: "dyn(-0.0) >= 0u" + value: { bool_value: true } + } + test { + name: "gte_dyn_uint_double_neg_zero" + expr: "dyn(0u) >= -0.0" + value: { bool_value: true } + } + test { + name: "not_gte_double_nan" + expr: "0.0/0.0 >= 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_gte_double_nan_left" + expr: "0.0/0.0 >= 1.0" + value: { bool_value: false } + } + test { + name: "not_gte_double_nan_right" + expr: "1.0 >= 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_gte_dyn_double_nan_int" + expr: "dyn(0.0/0.0) >= 1" + value: { bool_value: false } + } + test { + name: "not_gte_dyn_int_double_nan" + expr: "dyn(1) >= 0.0/0.0" + value: { bool_value: false } + } + test { + name: "not_gte_dyn_double_nan_uint" + expr: "dyn(0.0/0.0) >= 1u" + value: { bool_value: false } + } + test { + name: "not_gte_dyn_uint_double_nan" + expr: "dyn(1u) >= 0.0/0.0" + value: { bool_value: false } + } test { name: "gte_string_empty" expr: "'' >= ''" @@ -2165,6 +2650,56 @@ section { expr: "'elem' in [1u, 'str', 2, b'bytes']" value: { bool_value: false } } + test { + name: "nan_not_in_list" + expr: "0.0/0.0 in [0.0/0.0]" + value: { bool_value: false } + } + test { + name: "nested_list_nan_not_in_list" + expr: "[0.0/0.0] in [[0.0/0.0]]" + value: { bool_value: false } + } + test { + name: "nested_map_nan_not_in_list" + expr: "{'a': 0.0/0.0} in [{'a': 0.0/0.0}]" + value: { bool_value: false } + } + test { + name: "neg_zero_in_list_double" + expr: "-0.0 in [0.0]" + value: { bool_value: true } + } + test { + name: "neg_zero_in_list_int" + expr: "-0.0 in [0, 1.0]" + value: { bool_value: true } + } + test { + name: "neg_zero_in_list_uint" + expr: "-0.0 in [0u, 1.0]" + value: { bool_value: true } + } + test { + name: "int_zero_in_list_neg_zero" + expr: "0 in [-0.0, 1]" + value: { bool_value: true } + } + test { + name: "uint_zero_in_list_neg_zero" + expr: "0u in [-0.0, 1u]" + value: { bool_value: true } + } + test { + name: "nested_list_neg_zero_in_list" + expr: "[-0.0] in [[0.0]]" + value: { bool_value: true } + } + test { + name: "nested_map_neg_zero_in_list" + expr: "{'a': -0.0} in [{'a': 0.0}]" + value: { bool_value: true } + } } section { name: "in_map_literal" @@ -2195,6 +2730,21 @@ section { expr: "'key' in {1u:'str', 2:b'bytes'}" value: { bool_value: false } } + test { + name: "nan_not_in_map" + expr: "dyn(0.0/0.0) in {1: 'a', 2u: 'b'}" + value: { bool_value: false } + } + test { + name: "neg_zero_in_map_int_key" + expr: "dyn(-0.0) in {0: 'a', 1u: 'b'}" + value: { bool_value: true } + } + test { + name: "neg_zero_in_map_uint_key" + expr: "dyn(-0.0) in {0u: 'a', 1: 'b'}" + value: { bool_value: true } + } } section { name: "bound" @@ -2367,4 +2917,164 @@ section { value: { value: { null_value: NULL_VALUE } } } } + test { + name: "double_nan_eq_self_false" + expr: "x == x" + value: { bool_value: false } + type_env: { + name: "x" + ident: { type: { primitive: DOUBLE } } + } + bindings: { + key: "x" + value: { value: { double_value: nan } } + } + } + test { + name: "double_nan_ne_self_true" + expr: "x != x" + value: { bool_value: true } + type_env: { + name: "x" + ident: { type: { primitive: DOUBLE } } + } + bindings: { + key: "x" + value: { value: { double_value: nan } } + } + } + test { + name: "list_of_bound_nan_eq_false" + expr: "[x] == [x]" + value: { bool_value: false } + type_env: { + name: "x" + ident: { type: { primitive: DOUBLE } } + } + bindings: { + key: "x" + value: { value: { double_value: nan } } + } + } + test { + name: "list_of_bound_nan_ne_true" + expr: "[x] != [x]" + value: { bool_value: true } + type_env: { + name: "x" + ident: { type: { primitive: DOUBLE } } + } + bindings: { + key: "x" + value: { value: { double_value: nan } } + } + } + test { + name: "bound_list_nan_eq_self_false" + expr: "x == x" + value: { bool_value: false } + type_env: { + name: "x" + ident: { + type: { + list_type: { + elem_type: { primitive: DOUBLE } + } + } + } + } + bindings: { + key: "x" + value: { + value: { + list_value { + values: { double_value: nan } + } + } + } + } + } + test { + name: "bound_list_nan_ne_self_true" + expr: "x != x" + value: { bool_value: true } + type_env: { + name: "x" + ident: { + type: { + list_type: { + elem_type: { primitive: DOUBLE } + } + } + } + } + bindings: { + key: "x" + value: { + value: { + list_value { + values: { double_value: nan } + } + } + } + } + } + test { + name: "bound_map_nan_eq_self_false" + expr: "x == x" + value: { bool_value: false } + type_env: { + name: "x" + ident: { + type: { + map_type: { + key_type: { primitive: STRING } + value_type: { primitive: DOUBLE } + } + } + } + } + bindings: { + key: "x" + value: { + value: { + map_value { + entries { + key: { string_value: "a" } + value: { double_value: nan } + } + } + } + } + } + } + test { + name: "bound_map_nan_ne_self_true" + expr: "x != x" + value: { bool_value: true } + type_env: { + name: "x" + ident: { + type: { + map_type: { + key_type: { primitive: STRING } + value_type: { primitive: DOUBLE } + } + } + } + } + bindings: { + key: "x" + value: { + value: { + map_value { + entries { + key: { string_value: "a" } + value: { double_value: nan } + } + } + } + } + } + } }