consteval: -0.0 equals 0.0

That's what IEEE 754 requires, and WGSL does not overrule or relax that.

Various tests are updated:
- Don't expect that -0.0 is not equal to 0.0
- A floating point narrowing conversion is not required to preserve
  the sign bit on -0.0
- Avoid atan2 cases that have unbounded error, particularly around +/- 0.0

Suppress VertexFormatTests for Arm Android devices when -0.0 is used.
There are similar suppressed failures on Qualcomm Android devices.

Bug: dawn:1566
Fixed: tint:2001
Change-Id: Idc6c92473a798add3f2fe3f5478e91323dd76474
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/144383
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: David Neto <dneto@google.com>
diff --git a/src/dawn/tests/end2end/VertexFormatTests.cpp b/src/dawn/tests/end2end/VertexFormatTests.cpp
index 22a85c0..a62c38f 100644
--- a/src/dawn/tests/end2end/VertexFormatTests.cpp
+++ b/src/dawn/tests/end2end/VertexFormatTests.cpp
@@ -261,7 +261,7 @@
                 return bitcast<f32>(fp32u);
             }
 
-            // NaN defination in IEEE 754-1985 is :
+            // NaN definition in IEEE 754-1985 is :
             //   - sign = either 0 or 1.
             //   - biased exponent = all 1 bits.
             //   - fraction = anything except all 0 bits (since all 0 bits represents infinity).
@@ -288,7 +288,7 @@
 
         // Declare expected values.
         vs << "var expected : array<array<" << expectedDataType << ", "
-           << std::to_string(componentCount) << ">, " << std::to_string(kVertexNum) << ">;";
+           << std::to_string(componentCount) << ">, " << std::to_string(kVertexNum) << ">;\n";
         // Assign each elements in expected values
         // e.g. expected[0][0] = u32(1u);
         //      expected[0][1] = u32(2u);
@@ -307,7 +307,7 @@
                        << ") / " << std::to_string(std::numeric_limits<T>::max())
                        << ".0 , -1.0));\n";
                 } else if (isHalf) {
-                    // Becasue Vulkan and D3D12 handle -0.0f through bitcast have different
+                    // Because Vulkan and D3D12 handle -0.0f through bitcast have different
                     // result (Vulkan take -0.0f as -0.0 but D3D12 take -0.0f as 0), add workaround
                     // for -0.0f.
                     if (static_cast<uint16_t>(expectedData[i * componentCount + j]) ==
@@ -339,12 +339,15 @@
             if (!isInputTypeFloat) {  // Integer / unsigned integer need to match exactly.
                 vs << "    success = success && (" << testVal << " == " << expectedVal << ");\n";
             } else {
+                vs << "    success = success && (isNaNCustom(" << expectedVal << ") == isNaNCustom("
+                   << testVal << "));\n";
+                vs << "    if (!isNaNCustom(" << expectedVal << ")) {\n";
+                // Fold -0.0 into 0.0.
+                vs << "        if (" << testVal << " == 0) { " << testVal << " = 0; }\n";
+                vs << "        if (" << expectedVal << " == 0) { " << expectedVal << " = 0; }\n";
                 // TODO(shaobo.yan@intel.com) : a difference of 8 ULPs is allowed in this test
                 // because it is required on MacbookPro 11.5,AMD Radeon HD 8870M(on macOS 10.13.6),
                 // but that it might be possible to tighten.
-                vs << "    if (isNaNCustom(" << expectedVal << ")) {\n";
-                vs << "       success = success && isNaNCustom(" << testVal << ");\n";
-                vs << "    } else {\n";
                 vs << "        let testValFloatToUint : u32 = bitcast<u32>(" << testVal << ");\n";
                 vs << "        let expectedValFloatToUint : u32 = bitcast<u32>(" << expectedVal
                    << ");\n";
@@ -703,6 +706,8 @@
 TEST_P(VertexFormatTest, Float16x2) {
     // Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
     DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
+    // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
 
     std::vector<uint16_t> vertexData =
         Float32ToFloat16(std::vector<float>({14.8f, -0.0f, 22.5f, 1.3f, +0.0f, -24.8f}));
@@ -713,22 +718,28 @@
 TEST_P(VertexFormatTest, Float16x4) {
     // Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
     DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
+    // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
 
     std::vector<uint16_t> vertexData = Float32ToFloat16(std::vector<float>(
         {+0.0f, -16.8f, 18.2f, -0.0f, 12.5f, 1.3f, 14.8f, -12.4f, 22.5f, -48.8f, 47.4f, -24.8f}));
 
     DoVertexFormatTest(wgpu::VertexFormat::Float16x4, vertexData, vertexData);
 }
-
-TEST_P(VertexFormatTest, Float32) {
-    // TODO(dawn:1549) Fails on Qualcomm-based Android devices.
+TEST_P(VertexFormatTest, Float32_Zeros) {
+    // TODO(dawn:1566) Fails on Qualcomm-based and Arm-base Android devices. When using -0.0.
     DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsQualcomm());
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
 
     std::vector<float> vertexData = {1.3f, +0.0f, -0.0f};
 
     DoVertexFormatTest(wgpu::VertexFormat::Float32, vertexData, vertexData);
+}
 
-    vertexData = std::vector<float>{+1.0f, -1.0f, 18.23f};
+TEST_P(VertexFormatTest, Float32_Plain) {
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsQualcomm());
+
+    std::vector<float> vertexData = {+1.0f, -1.0f, 18.23f};
 
     DoVertexFormatTest(wgpu::VertexFormat::Float32, vertexData, vertexData);
 }
@@ -737,8 +748,10 @@
     // Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
     DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
 
-    // TODO(dawn:1549) Fails on Qualcomm-based Android devices.
+    // TODO(dawn:1566) Fails on Qualcomm-based Android devices.
     DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsQualcomm());
+    // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
 
     std::vector<float> vertexData = {18.23f, -0.0f, +0.0f, +1.0f, 1.3f, -1.0f};
 
@@ -748,6 +761,8 @@
 TEST_P(VertexFormatTest, Float32x3) {
     // Fails on NVIDIA's Vulkan drivers on CQ but passes locally.
     DAWN_SUPPRESS_TEST_IF(IsVulkan() && IsNvidia());
+    // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
 
     std::vector<float> vertexData = {
         +0.0f, -1.0f, -0.0f, 1.0f, 1.3f, 99.45f, 23.6f, -81.2f, 55.0f,
@@ -757,6 +772,8 @@
 }
 
 TEST_P(VertexFormatTest, Float32x4) {
+    // TODO(dawn:1566) Fails on Arm-based Android devices, when using -0.0.
+    DAWN_SUPPRESS_TEST_IF(IsAndroid() && IsARM());
     std::vector<float> vertexData = {
         19.2f, -19.3f, +0.0f, 1.0f, -0.0f, 1.0f, 1.3f, -1.0f, 13.078f, 21.1965f, -1.1f, -1.2f,
     };
diff --git a/src/tint/lang/core/constant/composite_test.cc b/src/tint/lang/core/constant/composite_test.cc
index 2f2bb10..763f357 100644
--- a/src/tint/lang/core/constant/composite_test.cc
+++ b/src/tint/lang/core/constant/composite_test.cc
@@ -30,14 +30,17 @@
     auto* fPos0 = constants.Get(0_f);
     auto* fNeg0 = constants.Get(-0_f);
     auto* fPos1 = constants.Get(1_f);
+    auto* fNeg1 = constants.Get(-1_f);
 
-    auto* compositeAll = constants.Composite(vec3f, Vector{fPos0, fPos0});
-    auto* compositeAny = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
-    auto* compositeNone = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+    auto* compositePosZeros = constants.Composite(vec3f, Vector{fPos0, fPos0});
+    auto* compositeNegZeros = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+    auto* compositeMixed = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
+    auto* compositePosNeg = constants.Composite(vec3f, Vector{fNeg1, fPos1, fPos1});
 
-    EXPECT_TRUE(compositeAll->AllZero());
-    EXPECT_FALSE(compositeAny->AllZero());
-    EXPECT_FALSE(compositeNone->AllZero());
+    EXPECT_TRUE(compositePosZeros->AllZero());
+    EXPECT_TRUE(compositeNegZeros->AllZero());
+    EXPECT_FALSE(compositeMixed->AllZero());
+    EXPECT_FALSE(compositePosNeg->AllZero());
 }
 
 TEST_F(ConstantTest_Composite, AnyZero) {
@@ -46,14 +49,17 @@
     auto* fPos0 = constants.Get(0_f);
     auto* fNeg0 = constants.Get(-0_f);
     auto* fPos1 = constants.Get(1_f);
+    auto* fNeg1 = constants.Get(-1_f);
 
-    auto* compositeAll = constants.Composite(vec3f, Vector{fPos0, fPos0});
-    auto* compositeAny = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
-    auto* compositeNone = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+    auto* compositePosZeros = constants.Composite(vec3f, Vector{fPos0, fPos0});
+    auto* compositeNegZeros = constants.Composite(vec3f, Vector{fNeg0, fNeg0});
+    auto* compositeMixed = constants.Composite(vec3f, Vector{fNeg0, fPos1, fPos0});
+    auto* compositePosNeg = constants.Composite(vec3f, Vector{fNeg1, fPos1, fPos1});
 
-    EXPECT_TRUE(compositeAll->AnyZero());
-    EXPECT_TRUE(compositeAny->AnyZero());
-    EXPECT_FALSE(compositeNone->AnyZero());
+    EXPECT_TRUE(compositePosZeros->AnyZero());
+    EXPECT_TRUE(compositeNegZeros->AnyZero());
+    EXPECT_TRUE(compositeMixed->AnyZero());
+    EXPECT_FALSE(compositePosNeg->AllZero());
 }
 
 TEST_F(ConstantTest_Composite, Index) {
diff --git a/src/tint/lang/core/constant/eval.cc b/src/tint/lang/core/constant/eval.cc
index daac5a9..0840065 100644
--- a/src/tint/lang/core/constant/eval.cc
+++ b/src/tint/lang/core/constant/eval.cc
@@ -255,7 +255,7 @@
         using FROM = T;
         if constexpr (std::is_same_v<TO, bool>) {
             // [x -> bool]
-            return ctx.mgr.Get<Scalar<TO>>(target_ty, !scalar->IsPositiveZero());
+            return ctx.mgr.Get<Scalar<TO>>(target_ty, !scalar->IsZero());
         } else if constexpr (std::is_same_v<FROM, bool>) {
             // [bool -> x]
             return ctx.mgr.Get<Scalar<TO>>(target_ty, TO(scalar->value ? 1 : 0));
diff --git a/src/tint/lang/core/constant/eval_builtin_test.cc b/src/tint/lang/core/constant/eval_builtin_test.cc
index 22e8bf4..80f87ed 100644
--- a/src/tint/lang/core/constant/eval_builtin_test.cc
+++ b/src/tint/lang/core/constant/eval_builtin_test.cc
@@ -184,14 +184,39 @@
     }
 }
 
-INSTANTIATE_TEST_SUITE_P(  //
-    MixedAbstractArgs,
-    ConstEvalBuiltinTest,
-    testing::Combine(testing::Values(core::Function::kAtan2),
-                     testing::ValuesIn(std::vector{
-                         C({0_a, -0.0_a}, kPi<AFloat>),
-                         C({1.0_a, 0_a}, kPiOver2<AFloat>),
-                     })));
+INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs_Atan2,
+                         ConstEvalBuiltinTest,
+                         testing::Combine(testing::Values(core::Function::kAtan2),
+                                          testing::ValuesIn(std::vector{
+                                              C({0.0_a, 1_a}, AFloat{0}),
+                                              C({0_a, 1.0_a}, AFloat{0}),
+                                              C({1_a, 0.0_a}, kPiOver2<AFloat>),
+                                              C({1.0_a, 0_a}, kPiOver2<AFloat>),
+                                          })));
+
+INSTANTIATE_TEST_SUITE_P(MixedAbstractArgs_Max,
+                         ConstEvalBuiltinTest,
+                         testing::Combine(testing::Values(core::Function::kMax),
+                                          testing::ValuesIn(std::vector{
+                                              // AbstractInt first, AbstractFloat second
+                                              C({1_a, 2.0_a}, AFloat{2}),
+                                              C({-1_a, -2.0_a}, AFloat{-1}),
+                                              C({2_a, 0.0_a}, AFloat{2}),
+                                              C({-2_a, 0.0_a}, AFloat{0}),
+                                              C({0_a, 0.0_a}, AFloat{0}),
+                                              C({0_a, -0.0_a}, AFloat{0}),
+                                              C({-0_a, 0.0_a}, AFloat{0}),
+                                              C({-0_a, -0.0_a}, AFloat{0}),
+                                              // AbstractFloat first, AbstractInt second
+                                              C({1.0_a, 2_a}, AFloat{2}),
+                                              C({-1.0_a, -2_a}, AFloat{-1}),
+                                              C({2.0_a, 0_a}, AFloat{2}),
+                                              C({-2.0_a, 0_a}, AFloat{0}),
+                                              C({0.0_a, 0_a}, AFloat{0}),
+                                              C({0.0_a, -0_a}, AFloat{0}),
+                                              C({-0.0_a, 0_a}, AFloat{0}),
+                                              C({-0.0_a, -0_a}, AFloat{0}),
+                                          })));
 
 template <typename T>
 std::vector<Case> AbsCases() {
@@ -291,11 +316,14 @@
 template <typename T>
 std::vector<Case> Atan2Cases() {
     return {
-        // If y is +/-0 and x is negative or -0, +/-PI is returned
-        C({T(0.0), -T(0.0)}, kPi<T>).PosOrNeg().FloatComp(),
-
-        // If y is +/-0 and x is positive or +0, +/-0 is returned
-        C({T(0.0), T(0.0)}, T(0.0)).PosOrNeg(),
+        // Beware that y is first:  atan2(y,x)
+        //
+        // Beware of regions where the error is unbounded:
+        //  At the orgin (0,0)
+        //  When x has very small magnitude, since atan(y/x) is a
+        //    valid formulation, and floating point division can have
+        //    large error when the divisor is subnormal.
+        //  When y is subnormal or infinite.
 
         // If x is +/-0 and y is negative, -PI/2 is returned
         C({-T(1.0), T(0.0)}, -kPiOver2<T>).FloatComp(),  //
@@ -305,11 +333,14 @@
         C({T(1.0), T(0.0)}, kPiOver2<T>).FloatComp(),  //
         C({T(1.0), -T(0.0)}, kPiOver2<T>).FloatComp(),
 
+        // If x is positive and y is 0, 0 is returned
+        C({T(0.0), T(1.0)}, T(0.0)).FloatComp(),  //
+        C({-T(0.0), T(1.0)}, T(0.0)).FloatComp(),
+
         // Vector tests
-        C({Vec(T(0.0), T(0.0)), Vec(-T(0.0), T(0.0))}, Vec(kPi<T>, T(0.0))).PosOrNeg().FloatComp(),
-        C({Vec(-T(1.0), -T(1.0)), Vec(T(0.0), -T(0.0))}, Vec(-kPiOver2<T>, -kPiOver2<T>))
-            .FloatComp(),
-        C({Vec(T(1.0), T(1.0)), Vec(T(0.0), -T(0.0))}, Vec(kPiOver2<T>, kPiOver2<T>)).FloatComp(),
+        C({Vec(-T(1.0), T(1.0)), Vec(T(0.0), -T(0.0))}, Vec(-kPiOver2<T>, kPiOver2<T>)).FloatComp(),
+        C({Vec(T(1.0), -T(1.0)), Vec(-T(0.0), T(0.0))}, Vec(kPiOver2<T>, -kPiOver2<T>)).FloatComp(),
+        C({Vec(T(0.0), -T(0.0)), Vec(T(1.0), T(1.0))}, Vec(T(0.0), T(0.0))).FloatComp(),
     };
 }
 INSTANTIATE_TEST_SUITE_P(  //
diff --git a/src/tint/lang/core/constant/eval_construction_test.cc b/src/tint/lang/core/constant/eval_construction_test.cc
index 4e9ae4ea..5d9cdc2 100644
--- a/src/tint/lang/core/constant/eval_construction_test.cc
+++ b/src/tint/lang/core/constant/eval_construction_test.cc
@@ -868,19 +868,19 @@
     EXPECT_TRUE(vec->type()->Is<core::type::F32>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), -0_f);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), -0_f);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f32>(), -0_f);
 }
 
@@ -898,14 +898,14 @@
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f32>(), 0_f);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f32>(), -0_f);
 
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
@@ -1021,19 +1021,19 @@
     EXPECT_TRUE(vec->type()->Is<core::type::F16>());
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
-    EXPECT_FALSE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(0)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), -0_h);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), -0_h);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(2)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<f16>(), -0_h);
 }
 
@@ -1053,14 +1053,14 @@
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<f16>(), 0_h);
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<f16>(), -0_h);
 
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
diff --git a/src/tint/lang/core/constant/eval_conversion_test.cc b/src/tint/lang/core/constant/eval_conversion_test.cc
index 23279d4..179f046 100644
--- a/src/tint/lang/core/constant/eval_conversion_test.cc
+++ b/src/tint/lang/core/constant/eval_conversion_test.cc
@@ -416,6 +416,10 @@
 TEST_F(ConstEvalTest, Vec3_Convert_Small_f32_to_f16) {
     Enable(core::Extension::kF16);
 
+    // These values are chosen to force underflow when converting to f16.
+    // When the result is zero, the sign bit is *not* guaranteed to be preserved.
+    // - IEEE 754 does not require it.
+    // - C++14 does not require it.
     auto* expr = Call<vec3<f16>>(Call<vec3<f32>>(1e-20_f, -2e-30_f, 3e-40_f));
     WrapInFunction(expr);
 
@@ -429,22 +433,19 @@
     EXPECT_EQ(vec->Width(), 3u);
     EXPECT_TYPE(sem->ConstantValue()->Type(), sem->Type());
     EXPECT_TRUE(sem->ConstantValue()->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->AllZero());
 
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(0)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(0)->ValueAs<AFloat>(), 0.0);
-    EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(0)->ValueAs<AFloat>().value));
 
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AnyZero());
-    EXPECT_FALSE(sem->ConstantValue()->Index(1)->AllZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AnyZero());
+    EXPECT_TRUE(sem->ConstantValue()->Index(1)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(1)->ValueAs<AFloat>(), -0.0);
-    EXPECT_TRUE(std::signbit(sem->ConstantValue()->Index(1)->ValueAs<AFloat>().value));
 
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AnyZero());
     EXPECT_TRUE(sem->ConstantValue()->Index(2)->AllZero());
     EXPECT_EQ(sem->ConstantValue()->Index(2)->ValueAs<AFloat>(), 0.0);
-    EXPECT_FALSE(std::signbit(sem->ConstantValue()->Index(2)->ValueAs<AFloat>().value));
 }
 
 TEST_F(ConstEvalTest, StructAbstractSplat_to_StructDifferentTypes) {
diff --git a/src/tint/lang/core/constant/scalar.h b/src/tint/lang/core/constant/scalar.h
index 2389d44..8fd51ad 100644
--- a/src/tint/lang/core/constant/scalar.h
+++ b/src/tint/lang/core/constant/scalar.h
@@ -58,10 +58,10 @@
     size_t NumElements() const override { return 1; }
 
     /// @copydoc Value::AllZero()
-    bool AllZero() const override { return IsPositiveZero(); }
+    bool AllZero() const override { return IsZero(); }
 
     /// @copydoc Value::AnyZero()
-    bool AnyZero() const override { return IsPositiveZero(); }
+    bool AnyZero() const override { return IsZero(); }
 
     /// @copydoc Value::Hash()
     size_t Hash() const override { return tint::Hash(type, ValueOf()); }
@@ -84,10 +84,11 @@
         }
     }
 
-    /// @returns true if `value` is a positive zero.
-    inline bool IsPositiveZero() const {
+    /// @returns true if `value` is zero.
+    /// For floating point -0.0 equals 0.0, according to IEEE 754.
+    inline bool IsZero() const {
         using N = UnwrapNumber<T>;
-        return Number<N>(value) == Number<N>(0);  // Considers sign bit
+        return Number<N>(value) == Number<N>(0);
     }
 
     /// The scalar type
diff --git a/src/tint/lang/core/constant/scalar_test.cc b/src/tint/lang/core/constant/scalar_test.cc
index e4651de..999da12 100644
--- a/src/tint/lang/core/constant/scalar_test.cc
+++ b/src/tint/lang/core/constant/scalar_test.cc
@@ -61,12 +61,12 @@
     EXPECT_FALSE(u1->AllZero());
 
     EXPECT_TRUE(fPos0->AllZero());
-    EXPECT_FALSE(fNeg0->AllZero());
+    EXPECT_TRUE(fNeg0->AllZero());
     EXPECT_FALSE(fPos1->AllZero());
     EXPECT_FALSE(fNeg1->AllZero());
 
     EXPECT_TRUE(f16Pos0->AllZero());
-    EXPECT_FALSE(f16Neg0->AllZero());
+    EXPECT_TRUE(f16Neg0->AllZero());
     EXPECT_FALSE(f16Pos1->AllZero());
     EXPECT_FALSE(f16Neg1->AllZero());
 
@@ -74,7 +74,7 @@
     EXPECT_FALSE(bt->AllZero());
 
     EXPECT_TRUE(afPos0->AllZero());
-    EXPECT_FALSE(afNeg0->AllZero());
+    EXPECT_TRUE(afNeg0->AllZero());
     EXPECT_FALSE(afPos1->AllZero());
     EXPECT_FALSE(afNeg1->AllZero());
 
@@ -121,12 +121,12 @@
     EXPECT_FALSE(u1->AnyZero());
 
     EXPECT_TRUE(fPos0->AnyZero());
-    EXPECT_FALSE(fNeg0->AnyZero());
+    EXPECT_TRUE(fNeg0->AnyZero());
     EXPECT_FALSE(fPos1->AnyZero());
     EXPECT_FALSE(fNeg1->AnyZero());
 
     EXPECT_TRUE(f16Pos0->AnyZero());
-    EXPECT_FALSE(f16Neg0->AnyZero());
+    EXPECT_TRUE(f16Neg0->AnyZero());
     EXPECT_FALSE(f16Pos1->AnyZero());
     EXPECT_FALSE(f16Neg1->AnyZero());
 
@@ -134,7 +134,7 @@
     EXPECT_FALSE(bt->AnyZero());
 
     EXPECT_TRUE(afPos0->AnyZero());
-    EXPECT_FALSE(afNeg0->AnyZero());
+    EXPECT_TRUE(afNeg0->AnyZero());
     EXPECT_FALSE(afPos1->AnyZero());
     EXPECT_FALSE(afNeg1->AnyZero());
 
diff --git a/src/tint/lang/core/constant/splat_test.cc b/src/tint/lang/core/constant/splat_test.cc
index 8e3fa47..1e7cdc3 100644
--- a/src/tint/lang/core/constant/splat_test.cc
+++ b/src/tint/lang/core/constant/splat_test.cc
@@ -36,7 +36,7 @@
     auto* SpfPos1 = constants.Splat(vec3f, fPos1, 3);
 
     EXPECT_TRUE(SpfPos0->AllZero());
-    EXPECT_FALSE(SpfNeg0->AllZero());
+    EXPECT_TRUE(SpfNeg0->AllZero());
     EXPECT_FALSE(SpfPos1->AllZero());
 }
 
@@ -52,7 +52,7 @@
     auto* SpfPos1 = constants.Splat(vec3f, fPos1, 3);
 
     EXPECT_TRUE(SpfPos0->AnyZero());
-    EXPECT_FALSE(SpfNeg0->AnyZero());
+    EXPECT_TRUE(SpfNeg0->AnyZero());
     EXPECT_FALSE(SpfPos1->AnyZero());
 }
 
diff --git a/src/tint/lang/core/number.h b/src/tint/lang/core/number.h
index ea2f94a..463bcb4 100644
--- a/src/tint/lang/core/number.h
+++ b/src/tint/lang/core/number.h
@@ -356,7 +356,9 @@
 /// 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.
+/// @returns true if the numbers `a` and `b` are exactly equal.
+/// For floating point types, negative zero equals zero.
+/// IEEE 754 says "Comparison shall ignore the sign of zero (so +0 = -0)."
 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.
@@ -364,11 +366,6 @@
         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);
 }
 
@@ -577,7 +574,7 @@
 template <typename FloatingPointT,
           typename = tint::traits::EnableIf<IsFloatingPoint<FloatingPointT>>>
 inline std::optional<FloatingPointT> CheckedDiv(FloatingPointT a, FloatingPointT b) {
-    if (b == FloatingPointT{0.0} || b == FloatingPointT{-0.0}) {
+    if (b == FloatingPointT{0.0}) {
         return {};
     }
     auto result = FloatingPointT{a.value / b.value};
@@ -624,7 +621,7 @@
 template <typename FloatingPointT,
           typename = tint::traits::EnableIf<IsFloatingPoint<FloatingPointT>>>
 inline std::optional<FloatingPointT> CheckedMod(FloatingPointT a, FloatingPointT b) {
-    if (b == FloatingPointT{0.0} || b == FloatingPointT{-0.0}) {
+    if (b == FloatingPointT{0.0}) {
         return {};
     }
     auto result = FloatingPointT{tint::detail::Mod(a.value, b.value)};
diff --git a/src/tint/lang/core/number_test.cc b/src/tint/lang/core/number_test.cc
index be5cab5..9039841 100644
--- a/src/tint/lang/core/number_test.cc
+++ b/src/tint/lang/core/number_test.cc
@@ -70,16 +70,22 @@
     EXPECT_TRUE(10_u == 10_u);
 
     EXPECT_TRUE(0._a == 0._a);
+    EXPECT_TRUE(0._a == -0._a);
+    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(-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(-0._h == 0._h);
     EXPECT_TRUE(-0_h == -0_h);
     EXPECT_TRUE(10_h == 10_h);
     EXPECT_TRUE(-10_h == -10_h);
@@ -104,15 +110,15 @@
     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_FALSE(0._a != -0._a);
+    EXPECT_FALSE(-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_FALSE(0_f != -0_f);
+    EXPECT_FALSE(-0_f != 0_f);
     EXPECT_TRUE(-0_f != -1_f);
     EXPECT_TRUE(-1_f != -0_f);
     EXPECT_TRUE(10_f != -10_f);
@@ -120,8 +126,8 @@
     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_FALSE(0_h != -0_h);
+    EXPECT_FALSE(-0_h != 0_h);
     EXPECT_TRUE(-0_h != -1_h);
     EXPECT_TRUE(-1_h != -0_h);
     EXPECT_TRUE(10_h != -10_h);
@@ -389,8 +395,10 @@
 #define OVERFLOW \
     {}
 
+// An error value.  IEEE 754 exceptions map to this, including overflow,
+// invalid operation, and division by zero.
 template <typename T>
-auto Overflow = std::optional<T>{};
+auto Error = std::optional<T>{};
 
 using BinaryCheckedCase_AInt = std::tuple<std::optional<AInt>, AInt, AInt>;
 using CheckedAddTest_AInt = testing::TestWithParam<BinaryCheckedCase_AInt>;
@@ -483,8 +491,8 @@
         {T(0x100), T(-0x100), T(0x200)},
         {T::Highest(), T::Highest(), T(0)},
         {T::Lowest(), T::Lowest(), T(0)},
-        {Overflow<T>, T::Highest(), T::Highest()},
-        {Overflow<T>, T::Lowest(), T::Lowest()},
+        {Error<T>, T::Highest(), T::Highest()},
+        {Error<T>, T::Lowest(), T::Lowest()},
     };
 }
 INSTANTIATE_TEST_SUITE_P(CheckedAddTest_Float,
@@ -552,7 +560,7 @@
         {T(-0x300), T(-0x100), T(0x200)},
         {T::Highest(), T::Highest(), T(0)},
         {T::Lowest(), T::Lowest(), T(0)},
-        {Overflow<T>, T::Lowest(), T::Highest()},
+        {Error<T>, T::Lowest(), T::Highest()},
     };
 }
 INSTANTIATE_TEST_SUITE_P(CheckedSubTest_Float,
@@ -631,8 +639,8 @@
         {T(-2), T(-2), T(1)},
         {T(0), T::Highest(), T(0)},
         {T(0), T::Lowest(), -T(0)},
-        {Overflow<T>, T::Highest(), T::Highest()},
-        {Overflow<T>, T::Lowest(), T::Lowest()},
+        {Error<T>, T::Highest(), T::Highest()},
+        {Error<T>, T::Lowest(), T::Lowest()},
     };
 }
 INSTANTIATE_TEST_SUITE_P(CheckedMulTest_Float,
@@ -692,10 +700,10 @@
         {T(1), T::Highest(), T::Highest()},
         {T(0), T(0), T::Highest()},
         {-T(0), T(0), T::Lowest()},
-        {Overflow<T>, T(123), T(0)},
-        {Overflow<T>, T(123), T(-0)},
-        {Overflow<T>, T(-123), T(0)},
-        {Overflow<T>, T(-123), T(-0)},
+        {Error<T>, T(123), T(0.0)},
+        {Error<T>, T(123), T(-0.0)},
+        {Error<T>, T(-123), T(0.0)},
+        {Error<T>, T(-123), T(-0.0)},
     };
 }
 INSTANTIATE_TEST_SUITE_P(CheckedDivTest_Float,
@@ -763,10 +771,10 @@
         {T{2}, T{10}, -T{4}},                //
         {-T{1}, -T{10}, -T{3}},              //
         {-T{2}, -T{10}, -T{4}},              //
-        {Overflow<T>, T(123), T(0)},         //
-        {Overflow<T>, T(123), T(-0)},        //
-        {Overflow<T>, T(-123), T(0)},        //
-        {Overflow<T>, T(-123), T(-0)},
+        {Error<T>, T(123), T(0.0)},          //
+        {Error<T>, T(123), T(-0.0)},         //
+        {Error<T>, T(-123), T(0.0)},         //
+        {Error<T>, T(-123), T(-0.0)},
     };
 }
 INSTANTIATE_TEST_SUITE_P(CheckedModTest_Float,
@@ -790,28 +798,40 @@
 template <typename T>
 std::vector<BinaryCheckedCase_Float> CheckedPowTest_FloatCases() {
     return {
-        {T(0), T(0), T(1)},                        //
-        {T(0), T(0), T::Highest()},                //
-        {T(1), T(1), T(1)},                        //
-        {T(1), T(1), T::Lowest()},                 //
-        {T(4), T(2), T(2)},                        //
-        {T(8), T(2), T(3)},                        //
-        {T(1), T(1), T::Highest()},                //
-        {T(1), T(1), -T(1)},                       //
-        {T(0.25), T(2), -T(2)},                    //
-        {T(0.125), T(2), -T(3)},                   //
-        {T(15.625), T(2.5), T(3)},                 //
-        {T(11.313708498), T(2), T(3.5)},           //
-        {T(24.705294220), T(2.5), T(3.5)},         //
-        {T(0.0883883476), T(2), -T(3.5)},          //
-        {Overflow<T>, -T(1), T(1)},                //
-        {Overflow<T>, -T(1), T::Highest()},        //
-        {Overflow<T>, T::Lowest(), T(1)},          //
-        {Overflow<T>, T::Lowest(), T::Highest()},  //
-        {Overflow<T>, T::Lowest(), T::Lowest()},   //
-        {Overflow<T>, T(0), T(0)},                 //
-        {Overflow<T>, T(0), -T(1)},                //
-        {Overflow<T>, T(0), T::Lowest()},          //
+        {T(0), T(0), T(1)},                     //
+        {T(0), T(0), T::Highest()},             //
+        {T(1), T(1), T(1)},                     //
+        {T(1), T(1), T::Lowest()},              //
+        {T(4), T(2), T(2)},                     //
+        {T(8), T(2), T(3)},                     //
+        {T(1), T(1), T::Highest()},             //
+        {T(1), T(1), -T(1)},                    //
+        {T(0.25), T(2), -T(2)},                 //
+        {T(0.125), T(2), -T(3)},                //
+        {T(15.625), T(2.5), T(3)},              //
+        {T(11.313708498), T(2), T(3.5)},        //
+        {T(24.705294220), T(2.5), T(3.5)},      //
+        {T(0.0883883476), T(2), -T(3.5)},       //
+        {Error<T>, -T(1), T(1)},                //
+        {Error<T>, -T(1), T::Highest()},        //
+        {Error<T>, T::Lowest(), T(1)},          //
+        {Error<T>, T::Lowest(), T::Highest()},  //
+        {Error<T>, T::Lowest(), T::Lowest()},   //
+        {Error<T>, T(0), T(0)},                 //
+        {Error<T>, T(0), -T(1)},                //
+        {Error<T>, T(0), T::Lowest()},          //
+        // Check exceptional cases from IEEE 754 `powr` function
+        // which has its exceptions derived from the formulation
+        // powr(x,y) = exp(y * log(x))
+        {Error<T>, T(0.0f), T(-2)},     // (0, less-than-zero)
+        {Error<T>, T(-0.0f), T(-2)},    // (0, less-than-zero)
+        {Error<T>, T(-2), T(5)},        // (less-than-zero, finite)
+        {Error<T>, T(-2), T(0)},        // (less-than-zero, finite)
+        {Error<T>, T(-2), T(-5)},       // (less-than-zero, finite)
+        {Error<T>, T(0.0f), T(0.0f)},   // (0,0)
+        {Error<T>, T(-0.0f), T(0.0f)},  // (0,0)
+        {Error<T>, T(0.0f), T(-0.0f)},  // (0,0)
+        {Error<T>, T(-0.0), T(-0.0f)},  // (0,0)
     };
 }
 INSTANTIATE_TEST_SUITE_P(CheckedPowTest_Float,