Skip to content

Commit cc4e973

Browse files
l46kokcopybara-github
authored andcommitted
No public description
PiperOrigin-RevId: 1008426293
1 parent ab84c6e commit cc4e973

14 files changed

Lines changed: 380 additions & 169 deletions

File tree

‎common/src/main/java/dev/cel/common/internal/BUILD.bazel‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,6 @@ java_library(
8888
tags = [
8989
],
9090
deps = [
91-
"//:auto_value",
9291
"//common/annotations",
9392
"@maven//:com_google_errorprone_error_prone_annotations",
9493
"@maven//:com_google_guava_guava",

‎common/src/main/java/dev/cel/common/internal/ComparisonFunctions.java‎

Lines changed: 16 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -30,32 +30,17 @@
3030
public final class ComparisonFunctions {
3131

3232
private static final UnsignedLong UINT_TO_INT_MAX = UnsignedLong.valueOf(Long.MAX_VALUE);
33-
private static final double DOUBLE_TO_INT_MAX = (double) Long.MAX_VALUE;
34-
private static final double DOUBLE_TO_INT_MIN = (double) Long.MIN_VALUE;
35-
private static final double DOUBLE_TO_UINT_MAX = UnsignedLong.MAX_VALUE.doubleValue();
3633

3734
public static int compareDoubleInt(double d, long l) {
38-
if (d < DOUBLE_TO_INT_MIN) {
39-
return -1;
40-
}
41-
if (d > DOUBLE_TO_INT_MAX) {
42-
return 1;
43-
}
44-
return Double.compare(d, (double) l);
35+
return compareDouble(d, (double) l);
4536
}
4637

4738
public static int compareIntDouble(long l, double d) {
4839
return -compareDoubleInt(d, l);
4940
}
5041

5142
public static int compareDoubleUint(double d, UnsignedLong ul) {
52-
if (d < 0.0) {
53-
return -1;
54-
}
55-
if (d > DOUBLE_TO_UINT_MAX) {
56-
return 1;
57-
}
58-
return Double.compare(d, ul.doubleValue());
43+
return compareDouble(d, ul.doubleValue());
5944
}
6045

6146
public static int compareUintDouble(UnsignedLong ul, double d) {
@@ -76,51 +61,17 @@ public static int compareUintInt(UnsignedLong ul, long l) {
7661
/**
7762
* Compare two numeric values of any type (double, int, uint) for equality.
7863
*
79-
* <p>Floating point values are follow IEEE 754 standard for NaN comparisons.
64+
* <p>Floating point values follow IEEE 754 standard for NaN and negative zero comparisons.
8065
*/
8166
public static boolean numericEquals(Number x, Number y) {
82-
if (x instanceof Double) {
83-
if (y instanceof Double) {
84-
return !(Double.isNaN((Double) x) || Double.isNaN((Double) y))
85-
&& x.doubleValue() == y.doubleValue();
86-
}
87-
if (y instanceof Long) {
88-
return compareDoubleInt((Double) x, (Long) y) == 0;
89-
}
90-
if (y instanceof UnsignedLong) {
91-
return compareDoubleUint((Double) x, (UnsignedLong) y) == 0;
92-
}
93-
}
94-
if (x instanceof Long) {
95-
if (y instanceof Long) {
96-
return x.equals(y);
97-
}
98-
if (y instanceof Double) {
99-
return compareIntDouble((Long) x, (Double) y) == 0;
100-
}
101-
if (y instanceof UnsignedLong) {
102-
return compareIntUint((Long) x, (UnsignedLong) y) == 0;
103-
}
104-
}
105-
if (x instanceof UnsignedLong) {
106-
if (y instanceof UnsignedLong) {
107-
return x.equals(y);
108-
}
109-
if (y instanceof Double) {
110-
return compareUintDouble((UnsignedLong) x, (Double) y) == 0;
111-
}
112-
if (y instanceof Long) {
113-
return compareUintInt((UnsignedLong) x, (Long) y) == 0;
114-
}
115-
}
116-
return false;
67+
return !(x instanceof Double && ((Double) x).isNaN()) && numericCompare(x, y) == 0;
11768
}
11869

11970
/** Compare two numeric values of any type (double, int, uint). */
12071
public static int numericCompare(Number x, Number y) {
12172
if (x instanceof Double) {
12273
if (y instanceof Double) {
123-
return ((Double) x).compareTo((Double) y);
74+
return compareDouble((Double) x, (Double) y);
12475
}
12576
if (y instanceof Long) {
12677
return compareDoubleInt((Double) x, (Long) y);
@@ -155,5 +106,16 @@ public static int numericCompare(Number x, Number y) {
155106
"Unsupported argument types: " + x.getClass() + ", " + y.getClass());
156107
}
157108

109+
private static int compareDouble(double a, double b) {
110+
if (a < b) {
111+
return -1;
112+
}
113+
if (a > b) {
114+
return 1;
115+
}
116+
// Treat -0.0 and 0.0 as equal, and sort NaN after all non-NaN numbers.
117+
return Boolean.compare(Double.isNaN(a), Double.isNaN(b));
118+
}
119+
158120
private ComparisonFunctions() {}
159121
}

‎common/src/main/java/dev/cel/common/internal/ProtoEquality.java‎

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import com.google.auto.value.AutoValue;
1818
import com.google.common.collect.ImmutableMap;
19+
import com.google.common.collect.Lists;
20+
import com.google.common.collect.Maps;
1921
import com.google.errorprone.annotations.CheckReturnValue;
2022
import com.google.errorprone.annotations.Immutable;
2123
import com.google.protobuf.Any;
@@ -26,6 +28,7 @@
2628
import dev.cel.common.annotations.Internal;
2729
import java.util.List;
2830
import java.util.Map;
31+
import java.util.Objects;
2932
import java.util.Optional;
3033

3134
/**
@@ -36,6 +39,7 @@
3639
*
3740
* <ul>
3841
* <li/>NaN is not equal to itself.
42+
* <li/>Positive and negative zero (+0.0 and -0.0) compare as equal.
3943
* <li/>Any values are unpacked before comparison.
4044
* <li/>If two Any values cannot be unpacked, they are compared by bytes.
4145
* </ul>
@@ -56,9 +60,6 @@ public ProtoEquality(DynamicProto dynamicProto) {
5660
}
5761

5862
public boolean equals(Message message1, Message message2) {
59-
if (message1 == message2) {
60-
return true;
61-
}
6263
if (!message1.getDescriptorForType().equals(message2.getDescriptorForType())) {
6364
return false;
6465
}
@@ -124,22 +125,48 @@ public boolean equals(Message message1, Message message2) {
124125
return true;
125126
}
126127

128+
public int hashCode(Message message) {
129+
if (isAny(message)) {
130+
return ANY_FULL_NAME.hashCode();
131+
}
132+
return Objects.hash(
133+
message.getDescriptorForType(),
134+
message.getUnknownFields(),
135+
Maps.transformEntries(message.getAllFields(), this::hashFieldEntry));
136+
}
137+
138+
private int hashFieldEntry(FieldDescriptor field, Object value) {
139+
if (field.isMapField()) {
140+
ProtoMap protoMap = protoMap((List<?>) value);
141+
return Maps.transformValues(protoMap.map(), v -> hashFieldValue(protoMap.valueField(), v))
142+
.hashCode();
143+
}
144+
if (field.isRepeated()) {
145+
return Lists.transform((List<?>) value, elem -> hashFieldValue(field, elem)).hashCode();
146+
}
147+
return hashFieldValue(field, value);
148+
}
149+
150+
private int hashFieldValue(FieldDescriptor field, Object value) {
151+
switch (field.getJavaType()) {
152+
case MESSAGE:
153+
return hashCode((Message) value);
154+
case DOUBLE:
155+
case FLOAT:
156+
// Adding 0.0d normalizes -0.0d to +0.0d per IEEE 754 so equal zeros hash alike.
157+
return Double.hashCode(((Number) value).doubleValue() + 0.0d);
158+
default:
159+
return value.hashCode();
160+
}
161+
}
162+
127163
private boolean equalsFieldValues(FieldDescriptor field, Object value1, Object value2) {
128164
switch (field.getJavaType()) {
129165
case MESSAGE:
130166
return equals((Message) value1, (Message) value2);
131167
case DOUBLE:
132-
// fallthrough
133168
case FLOAT:
134-
Number num1 = (Number) value1;
135-
Number num2 = (Number) value2;
136-
boolean num1IsNaN = Double.isNaN(num1.doubleValue());
137-
boolean num2IsNaN = Double.isNaN(num2.doubleValue());
138-
// handle the NaN case
139-
if (num1IsNaN && num2IsNaN) {
140-
return false;
141-
}
142-
return num1.equals(num2);
169+
return ((Number) value1).doubleValue() == ((Number) value2).doubleValue();
143170
default:
144171
return value1.equals(value2);
145172
}

‎common/src/test/java/dev/cel/common/internal/ComparisonFunctionsTest.java‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import static com.google.common.truth.Truth.assertThat;
1818

1919
import com.google.common.primitives.UnsignedLong;
20+
import com.google.testing.junit.testparameterinjector.TestParameter;
2021
import com.google.testing.junit.testparameterinjector.TestParameterInjector;
2122
import com.google.testing.junit.testparameterinjector.TestParameters;
2223
import java.math.BigInteger;
@@ -28,20 +29,24 @@ public final class ComparisonFunctionsTest {
2829

2930
@Test
3031
@TestParameters("{x: 1.0, y: 1, expect: 0}")
32+
@TestParameters("{x: -0.0, y: 0, expect: 0}")
3133
@TestParameters("{x: -2.1, y: -2, expect: -1}")
3234
@TestParameters("{x: 2.1, y: -3, expect: 1}")
3335
@TestParameters("{x: -9223372036854778809.0, y: 1, expect: -1}")
3436
@TestParameters("{x: 9223372036854778809.0, y: 1, expect: 1}")
37+
@TestParameters("{x: NaN, y: 0, expect: 1}")
3538
public void compareDoubleInt(double x, long y, int expect) {
3639
assertThat(ComparisonFunctions.compareDoubleInt(x, y)).isEqualTo(expect);
3740
assertThat(ComparisonFunctions.compareIntDouble(y, x)).isEqualTo(-1 * expect);
3841
}
3942

4043
@Test
4144
@TestParameters("{x: 1.0, y: 1, expect: 0}")
45+
@TestParameters("{x: -0.0, y: 0, expect: 0}")
4246
@TestParameters("{x: -2.1, y: 0, expect: -1}")
4347
@TestParameters("{x: 2.1, y: 1, expect: 1}")
4448
@TestParameters("{x: 18446744073709653620.1, y: 1, expect: 1}")
49+
@TestParameters("{x: NaN, y: 0, expect: 1}")
4550
public void compareDoubleUint(double x, long y, int expect) {
4651
UnsignedLong uy = UnsignedLong.valueOf(y);
4752
assertThat(ComparisonFunctions.compareDoubleUint(x, uy)).isEqualTo(expect);
@@ -93,4 +98,46 @@ public void numericCompareUintInt(long x, long y, int expect) {
9398
assertThat(ComparisonFunctions.numericCompare(ux, y)).isEqualTo(expect);
9499
assertThat(ComparisonFunctions.numericCompare(y, ux)).isEqualTo(-1 * expect);
95100
}
101+
102+
@Test
103+
@TestParameters("{x: -0.0, y: 0.0, expect: 0}")
104+
@TestParameters("{x: 1.0, y: 2.0, expect: -1}")
105+
@TestParameters("{x: NaN, y: 1.0, expect: 1}")
106+
@TestParameters("{x: NaN, y: NaN, expect: 0}")
107+
public void numericCompare_doubleDouble(double x, double y, int expect) {
108+
assertThat(ComparisonFunctions.numericCompare(x, y)).isEqualTo(expect);
109+
assertThat(ComparisonFunctions.numericCompare(y, x)).isEqualTo(-1 * expect);
110+
}
111+
112+
@SuppressWarnings("ImmutableEnumChecker") // Test only
113+
private enum NumericEqualsTestCase {
114+
NEGATIVE_ZERO_AND_POSITIVE_ZERO(-0.0, 0.0, true),
115+
NEGATIVE_ZERO_AND_LONG_ZERO(-0.0, 0L, true),
116+
NEGATIVE_ZERO_AND_ULONG_ZERO(-0.0, UnsignedLong.ZERO, true),
117+
DOUBLE_ONE_AND_LONG_ONE(1.0, 1L, true),
118+
DOUBLE_ONE_AND_ULONG_ONE(1.0, UnsignedLong.ONE, true),
119+
DISTINCT_DOUBLES(1.0, 2.0, false),
120+
DISTINCT_DOUBLE_AND_LONG(1.0, 2L, false),
121+
NAN_AND_NAN(Double.NaN, Double.NaN, false),
122+
NAN_AND_LONG_ZERO(Double.NaN, 0L, false),
123+
NAN_AND_ULONG_ZERO(Double.NaN, UnsignedLong.ZERO, false);
124+
125+
final Number x;
126+
final Number y;
127+
final boolean expected;
128+
129+
NumericEqualsTestCase(Number x, Number y, boolean expected) {
130+
this.x = x;
131+
this.y = y;
132+
this.expected = expected;
133+
}
134+
}
135+
136+
@Test
137+
public void numericEquals(@TestParameter NumericEqualsTestCase testCase) {
138+
assertThat(ComparisonFunctions.numericEquals(testCase.x, testCase.y))
139+
.isEqualTo(testCase.expected);
140+
assertThat(ComparisonFunctions.numericEquals(testCase.y, testCase.x))
141+
.isEqualTo(testCase.expected);
142+
}
96143
}

0 commit comments

Comments
 (0)