tint: Have Number equality consider sign

Sign is important for equality when it comes to backend generation
of floating point numbers.

Change-Id: I1e2610fe9bae98a5c5f756a55385e092919b5aa3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/95080
Commit-Queue: Ben Clayton <bclayton@chromium.org>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/number.h b/src/tint/number.h
index 4e3e6fd..e130019 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -16,6 +16,7 @@
 #define SRC_TINT_NUMBER_H_
 
 #include <stdint.h>
+#include <cmath>
 #include <functional>
 #include <limits>
 #include <optional>
@@ -135,61 +136,6 @@
     return out << num.value;
 }
 
-/// Equality operator.
-/// @param a the LHS number
-/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly equal.
-template <typename A, typename B>
-bool operator==(Number<A> a, Number<B> b) {
-    using T = decltype(a.value + b.value);
-    return std::equal_to<T>()(static_cast<T>(a.value), static_cast<T>(b.value));
-}
-
-/// Inequality operator.
-/// @param a the LHS number
-/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly unequal.
-template <typename A, typename B>
-bool operator!=(Number<A> a, Number<B> b) {
-    return !(a == b);
-}
-
-/// Equality operator.
-/// @param a the LHS number
-/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly equal.
-template <typename A, typename B>
-std::enable_if_t<IsNumeric<B>, bool> operator==(Number<A> a, B b) {
-    return a == Number<B>(b);
-}
-
-/// Inequality operator.
-/// @param a the LHS number
-/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly unequal.
-template <typename A, typename B>
-std::enable_if_t<IsNumeric<B>, bool> operator!=(Number<A> a, B b) {
-    return !(a == b);
-}
-
-/// Equality operator.
-/// @param a the LHS number
-/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly equal.
-template <typename A, typename B>
-std::enable_if_t<IsNumeric<A>, bool> operator==(A a, Number<B> b) {
-    return Number<A>(a) == b;
-}
-
-/// Inequality operator.
-/// @param a the LHS number
-/// @param b the RHS number
-/// @returns true if the numbers `a` and `b` are exactly unequal.
-template <typename A, typename B>
-std::enable_if_t<IsNumeric<A>, bool> operator!=(A a, Number<B> b) {
-    return !(a == b);
-}
-
 /// The partial specification of Number for f16 type, storing the f16 value as float,
 /// and enforcing proper explicit casting.
 template <>
@@ -295,6 +241,70 @@
     return TO(value);  // Success
 }
 
+/// Equality operator.
+/// @param a the LHS number
+/// @param b the RHS number
+/// @returns true if the numbers `a` and `b` are exactly equal. Also considers sign bit.
+template <typename A, typename B>
+bool operator==(Number<A> a, Number<B> b) {
+    // Use the highest-precision integer or floating-point type to perform the comparisons.
+    using T =
+        std::conditional_t<IsFloatingPoint<A> || IsFloatingPoint<B>, AFloat::type, AInt::type>;
+    auto va = static_cast<T>(a.value);
+    auto vb = static_cast<T>(b.value);
+    if constexpr (IsFloatingPoint<T>) {
+        if (std::signbit(va) != std::signbit(vb)) {
+            return false;
+        }
+    }
+    return std::equal_to<T>()(va, vb);
+}
+
+/// Inequality operator.
+/// @param a the LHS number
+/// @param b the RHS number
+/// @returns true if the numbers `a` and `b` are exactly unequal. Also considers sign bit.
+template <typename A, typename B>
+bool operator!=(Number<A> a, Number<B> b) {
+    return !(a == b);
+}
+
+/// Equality operator.
+/// @param a the LHS number
+/// @param b the RHS number
+/// @returns true if the numbers `a` and `b` are exactly equal.
+template <typename A, typename B>
+std::enable_if_t<IsNumeric<B>, bool> operator==(Number<A> a, B b) {
+    return a == Number<B>(b);
+}
+
+/// Inequality operator.
+/// @param a the LHS number
+/// @param b the RHS number
+/// @returns true if the numbers `a` and `b` are exactly unequal.
+template <typename A, typename B>
+std::enable_if_t<IsNumeric<B>, bool> operator!=(Number<A> a, B b) {
+    return !(a == b);
+}
+
+/// Equality operator.
+/// @param a the LHS number
+/// @param b the RHS number
+/// @returns true if the numbers `a` and `b` are exactly equal.
+template <typename A, typename B>
+std::enable_if_t<IsNumeric<A>, bool> operator==(A a, Number<B> b) {
+    return Number<A>(a) == b;
+}
+
+/// Inequality operator.
+/// @param a the LHS number
+/// @param b the RHS number
+/// @returns true if the numbers `a` and `b` are exactly unequal.
+template <typename A, typename B>
+std::enable_if_t<IsNumeric<A>, bool> operator!=(A a, Number<B> b) {
+    return !(a == b);
+}
+
 /// Define 'TINT_HAS_OVERFLOW_BUILTINS' if the compiler provide overflow checking builtins.
 /// If the compiler does not support these builtins, then these are emulated with algorithms
 /// described in:
diff --git a/src/tint/number_test.cc b/src/tint/number_test.cc
index 6a48f9d..81acc04 100644
--- a/src/tint/number_test.cc
+++ b/src/tint/number_test.cc
@@ -65,6 +65,79 @@
 // warning.
 TINT_BEGIN_DISABLE_WARNING(CONSTANT_OVERFLOW);
 
+TEST(NumberTest, Equality) {
+    EXPECT_TRUE(0_a == 0_a);
+    EXPECT_TRUE(10_a == 10_a);
+    EXPECT_TRUE(-10_a == -10_a);
+
+    EXPECT_TRUE(0_i == 0_i);
+    EXPECT_TRUE(10_i == 10_i);
+    EXPECT_TRUE(-10_i == -10_i);
+
+    EXPECT_TRUE(0_u == 0_u);
+    EXPECT_TRUE(10_u == 10_u);
+
+    EXPECT_TRUE(0._a == 0._a);
+    EXPECT_TRUE(-0._a == -0._a);
+    EXPECT_TRUE(10._a == 10._a);
+    EXPECT_TRUE(-10._a == -10._a);
+
+    EXPECT_TRUE(0_f == 0_f);
+    EXPECT_TRUE(-0_f == -0_f);
+    EXPECT_TRUE(10_f == 10_f);
+    EXPECT_TRUE(-10_f == -10_f);
+
+    EXPECT_TRUE(0_h == 0_h);
+    EXPECT_TRUE(-0_h == -0_h);
+    EXPECT_TRUE(10_h == 10_h);
+    EXPECT_TRUE(-10_h == -10_h);
+}
+
+TEST(NumberTest, Inequality) {
+    EXPECT_TRUE(0_a != 1_a);
+    EXPECT_TRUE(10_a != 11_a);
+    EXPECT_TRUE(11_a != 10_a);
+    EXPECT_TRUE(-10_a != -11_a);
+    EXPECT_TRUE(-11_a != -10_a);
+
+    EXPECT_TRUE(0_i != 1_i);
+    EXPECT_TRUE(1_i != 0_i);
+    EXPECT_TRUE(10_i != 11_i);
+    EXPECT_TRUE(11_i != 10_i);
+    EXPECT_TRUE(-10_i != -11_i);
+    EXPECT_TRUE(-11_i != -10_i);
+
+    EXPECT_TRUE(0_u != 1_u);
+    EXPECT_TRUE(1_u != 0_u);
+    EXPECT_TRUE(10_u != 11_u);
+    EXPECT_TRUE(11_u != 10_u);
+
+    EXPECT_TRUE(0._a != -0._a);
+    EXPECT_TRUE(-0._a != 0._a);
+    EXPECT_TRUE(10._a != 11._a);
+    EXPECT_TRUE(11._a != 10._a);
+    EXPECT_TRUE(-10._a != -11._a);
+    EXPECT_TRUE(-11._a != -10._a);
+
+    EXPECT_TRUE(0_f != -0_f);
+    EXPECT_TRUE(-0_f != 0_f);
+    EXPECT_TRUE(-0_f != -1_f);
+    EXPECT_TRUE(-1_f != -0_f);
+    EXPECT_TRUE(10_f != -10_f);
+    EXPECT_TRUE(-10_f != 10_f);
+    EXPECT_TRUE(10_f != 11_f);
+    EXPECT_TRUE(-10_f != -11_f);
+
+    EXPECT_TRUE(0_h != -0_h);
+    EXPECT_TRUE(-0_h != 0_h);
+    EXPECT_TRUE(-0_h != -1_h);
+    EXPECT_TRUE(-1_h != -0_h);
+    EXPECT_TRUE(10_h != -10_h);
+    EXPECT_TRUE(-10_h != 10_h);
+    EXPECT_TRUE(10_h != 11_h);
+    EXPECT_TRUE(-10_h != -11_h);
+}
+
 TEST(NumberTest, CheckedConvertIdentity) {
     EXPECT_EQ(CheckedConvert<AInt>(0_a), 0_a);
     EXPECT_EQ(CheckedConvert<AFloat>(0_a), 0.0_a);
@@ -211,7 +284,7 @@
     EXPECT_EQ(f16(lowestNegativeSubnormalF16PlusULP), -0x0.ff8p-14);
     EXPECT_EQ(f16(highestNegativeSubnormalF16MinusULP), highestNegativeSubnormalF16);
     EXPECT_EQ(f16(highestNegativeSubnormalF16), highestNegativeSubnormalF16);
-    EXPECT_EQ(f16(highestNegativeSubnormalF16PlusULP), 0.0);
+    EXPECT_EQ(f16(highestNegativeSubnormalF16PlusULP), -0.0);
     // Test the mantissa discarding.
     EXPECT_EQ(f16(-0x0.064p-14), -0x0.064p-14);
     EXPECT_EQ(f16(-0x0.067fecp-14), -0x0.064p-14);