Tests: Compare infinities and zero signs exactly in ULP checks

ulp_distance folded -0 onto +0 and put an infinity one step from the largest
finite value, so every ULP budget accepted +0 for sqrt(-0), and any budget of
one or more accepted overflow. An infinity is now as far as possible from any
other value, and so is a zero from the zero of the other sign; the pmadd
check inherits the exact zero sign.

Validation: C++14, Release, seed 1. GPU parts 1, 3 and 7 pass with GCC 13.3 /
NVCC 12.8 and with Clang 20 / CUDA 12.8 (-ffp-contract=fast) on an NVIDIA
GeForce RTX 4070 Laptop GPU (sm_89) under WSL2. GCC 13.3 passes all 15
packetmath parts, the four special_packetmath parts and packetmath_fastmath.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
diff --git a/test/packetmath_gpu.cu b/test/packetmath_gpu.cu
index 277465f..b4691c6 100644
--- a/test/packetmath_gpu.cu
+++ b/test/packetmath_gpu.cu
@@ -533,6 +533,12 @@
       in, [](Scalar x) { return std::round(x); }, bits);
   check_unary<Packet, op_preinterpret_self>(
       in, [](Scalar x) { return x; }, bits_and_payload);
+  // No ULP budget admits a lost zero sign or an overflow.
+  const uint64_t kFar = (std::numeric_limits<uint64_t>::max)();
+  VERIFY_IS_EQUAL(test::ulp_distance(Scalar(0), Scalar(-0.0)), kFar);
+  VERIFY_IS_EQUAL(test::ulp_distance(std::numeric_limits<Scalar>::infinity(), (std::numeric_limits<Scalar>::max)()),
+                  kFar);
+  VERIFY_IS_EQUAL(test::ulp_distance(Scalar(0), std::numeric_limits<Scalar>::denorm_min()), uint64_t(1));
   if (std::is_same<Scalar, float>::value) {
     check_unary<Packet, op_psqrt>(
         in, [](Scalar x) { return std::sqrt(x); }, compare_ulps<Scalar>{kSqrtFloatUlps});
diff --git a/test/packetmath_test_shared.h b/test/packetmath_test_shared.h
index 5a3bd8e..db5db6d 100644
--- a/test/packetmath_test_shared.h
+++ b/test/packetmath_test_shared.h
@@ -268,13 +268,19 @@
 }
 
 // Distance in units in the last place between two values of the same floating-point type. Two NaNs are zero apart,
-// a NaN and a number as far apart as possible. (test/ulp_accuracy measures signed errors with its own fold, which
-// maps -0 below +0 and treats infinities as incomparable; the budgets it reports are not this distance.)
+// a NaN and a number as far apart as possible. Infinities and the sign of zero are exact: an infinity is as far as
+// possible from any other value, and +0 from -0, so no budget admits an overflow or a lost sign. (test/ulp_accuracy
+// measures signed errors with its own fold, which maps -0 below +0 and treats infinities as incomparable; the budgets
+// it reports are not this distance.)
 template <typename Scalar>
 uint64_t ulp_distance(Scalar a, Scalar b) {
+  constexpr uint64_t kFar = (std::numeric_limits<uint64_t>::max)();
   const bool a_nan = (numext::isnan)(a);
   const bool b_nan = (numext::isnan)(b);
-  if (a_nan || b_nan) return (a_nan && b_nan) ? 0 : (std::numeric_limits<uint64_t>::max)();
+  if (a_nan || b_nan) return (a_nan && b_nan) ? 0 : kFar;
+  if ((numext::isinf)(a) || (numext::isinf)(b) || (numext::is_exactly_zero(a) && numext::is_exactly_zero(b))) {
+    return biteq(a, b) ? 0 : kFar;
+  }
   const int64_t pa = static_cast<int64_t>(ordered_position(a));
   const int64_t pb = static_cast<int64_t>(ordered_position(b));
   return pa > pb ? uint64_t(pa) - uint64_t(pb) : uint64_t(pb) - uint64_t(pa);