Core: Preserve NEON bfloat16 abs bit patterns

libeigen/eigen!3041

diff --git a/Eigen/src/Core/arch/NEON/PacketMath.h b/Eigen/src/Core/arch/NEON/PacketMath.h
index 2973a85..9b154b3 100644
--- a/Eigen/src/Core/arch/NEON/PacketMath.h
+++ b/Eigen/src/Core/arch/NEON/PacketMath.h
@@ -4647,7 +4647,7 @@
 
 template <>
 EIGEN_STRONG_INLINE Packet4bf pabs(const Packet4bf& a) {
-  return F32ToBf16(pabs<Packet4f>(Bf16ToF32(a)));
+  return Packet4bf(vand_u16(a, vdup_n_u16(0x7fff)));
 }
 
 template <>
diff --git a/test/packetmath.cpp b/test/packetmath.cpp
index b160ec9..32a60c6 100644
--- a/test/packetmath.cpp
+++ b/test/packetmath.cpp
@@ -12,6 +12,7 @@
 #include <utility>
 #include "packetmath_test_shared.h"
 #include "random_without_cast_overflow.h"
+#include "fp_control.h"
 
 using internal::unpacket_traits;
 
@@ -1756,6 +1757,62 @@
 #endif
 
 template <typename Scalar, typename Packet>
+void packetmath_abs_bits() {
+  using Bits = typename numext::get_integer_by_size<sizeof(Scalar)>::unsigned_type;
+  constexpr int PacketSize = unpacket_traits<Packet>::size;
+  constexpr Bits Sign = Bits(1) << (8 * sizeof(Scalar) - 1);
+  constexpr Bits MinNormal = Bits(1) << (std::numeric_limits<Scalar>::digits - 1);
+  constexpr Bits Infinity = (Sign - 1) ^ (MinNormal - 1);
+  Scalar input[PacketSize], output[PacketSize];
+  const auto check = [&] {
+    internal::pstoreu(output, internal::pabs(internal::ploadu<Packet>(input)));
+    for (int i = 0; i < PacketSize; ++i) {
+      const Bits expected = numext::bit_cast<Bits>(input[i]) & (Sign - 1);
+      const Bits actual = numext::bit_cast<Bits>(output[i]);
+      if (std::is_same<Scalar, Packet>::value && std::is_floating_point<Scalar>::value && expected > Infinity) {
+        // Scalar floating-point loads/stores (notably x87) may quiet signaling NaNs. Preserve all other bits.
+        VERIFY(actual == expected || actual == Bits(expected | (MinNormal >> 1)));
+      } else {
+        VERIFY_IS_EQUAL(actual, expected);
+      }
+    }
+  };
+  const auto run = [&] {
+    EIGEN_IF_CONSTEXPR (sizeof(Scalar) == 2) {
+      // Exhaust half/bfloat16 encodings, including signaling NaNs and their payloads.
+      for (int first = 0; first < 65536; first += PacketSize) {
+        for (int i = 0; i < PacketSize; ++i) input[i] = numext::bit_cast<Scalar>(Bits(first + i));
+        check();
+      }
+    } else {
+      const Bits samples[] = {Bits(0),
+                              Bits(1),
+                              Bits(2),
+                              Bits(MinNormal - 1),
+                              MinNormal,
+                              Bits(MinNormal + 1),
+                              numext::bit_cast<Bits>(Scalar(1)),
+                              Bits(Infinity - 1),
+                              Infinity,
+                              Bits(Infinity | 1),
+                              Bits(Infinity | (MinNormal >> 1)),
+                              Bits(Sign - 1)};
+      const int count = sizeof(samples) / sizeof(samples[0]);
+      for (int offset = 0; offset < count; ++offset) {
+        for (Bits sign : {Bits(0), Sign}) {
+          for (int i = 0; i < PacketSize; ++i)
+            input[i] = numext::bit_cast<Scalar>(Bits(samples[(offset + i) % count] | sign));
+          check();
+        }
+      }
+    }
+  };
+  run();
+  ScopedFlushToZero flush_to_zero;
+  run();
+}
+
+template <typename Scalar, typename Packet>
 void packetmath_notcomplex() {
   packetmath_ieee_special_values<Scalar, Packet>();
 
@@ -2304,12 +2361,29 @@
   }
 }
 
+void packetmath_bfloat16_abs_array() {
+  // Keep public evaluator coverage with complete packets and a scalar tail.
+  constexpr Index count = 2 * internal::packet_traits<bfloat16>::size + 1;
+  const numext::uint16_t samples[] = {0x8000, 0x8001, 0x807f, 0x8080, 0xbf80, 0xff80, 0xff81, 0xffff};
+  Array<bfloat16, Dynamic, 1> input(count), result(count);
+  for (Index i = 0; i < count; ++i) {
+    input(i) = numext::bit_cast<bfloat16>(samples[i % 8]);
+  }
+  input(count - 1) = numext::bit_cast<bfloat16>(numext::uint16_t(0xffff));
+  result = input.abs();
+  for (Index i = 0; i < count; ++i) {
+    const auto expected = static_cast<numext::uint16_t>(numext::bit_cast<numext::uint16_t>(input(i)) & 0x7fff);
+    VERIFY_IS_EQUAL(numext::bit_cast<numext::uint16_t>(result(i)), expected);
+  }
+}
+
 namespace Eigen {
 namespace test {
 
 template <typename Scalar, typename PacketType>
 struct runall<Scalar, PacketType, false, false> {  // i.e. float or double
   static void run() {
+    if (g_first_pass) packetmath_abs_bits<Scalar, PacketType>();
     packetmath<Scalar, PacketType>();
     packetmath_scatter_gather<Scalar, PacketType>();
     packetmath_notcomplex<Scalar, PacketType>();
@@ -2365,6 +2439,12 @@
     g_first_pass = false;
   }
 
+  CALL_SUBTEST_15(packetmath_bfloat16_abs_array());
+  CALL_SUBTEST_15({
+    ScopedFlushToZero flush_to_zero;
+    packetmath_bfloat16_abs_array();
+  });
+
 #if defined(EIGEN_VECTORIZE_RVV10)
   CALL_SUBTEST_1((packetmath_redux_infinities<float, internal::Packet1Xf>()));
   CALL_SUBTEST_1((packetmath_redux_infinities<float, internal::Packet2Xf>()));