diff --git a/g3doc/quick_reference.md b/g3doc/quick_reference.md
index 8220e9b718..059acef4d8 100644
--- a/g3doc/quick_reference.md
+++ b/g3doc/quick_reference.md
@@ -678,6 +678,24 @@ is qNaN, and NaN if both are.
* V **Max**(V a, V b)
: returns `max(a[i], b[i])`.
+* V **MinMagnitude**(V a, V b)
: returns the number with the
+ smaller magnitude if `a[i]` and `b[i]` are both non-NaN values.
+
+ If `a[i]` and `b[i]` are both non-NaN, `MinMagnitude(a, b)` returns
+ `(|a[i]| < |b[i]| || (|a[i]| == |b[i]| && a[i] < b[i])) ? a[i] : b[i]`.
+
+ Otherwise, the results of `MinMagnitude(a, b)` are implementation-defined
+ if `a[i]` is NaN or `b[i]` is NaN.
+
+* V **MaxMagnitude**(V a, V b)
: returns the number with the
+ larger magnitude if `a[i]` and `b[i]` are both non-NaN values.
+
+ If `a[i]` and `b[i]` are both non-NaN, `MaxMagnitude(a, b)` returns
+ `(|a[i]| < |b[i]| || (|a[i]| == |b[i]| && a[i] < b[i])) ? b[i] : a[i]`.
+
+ Otherwise, the results of `MaxMagnitude(a, b)` are implementation-defined
+ if `a[i]` is NaN or `b[i]` is NaN.
+
All other ops in this section are only available if `HWY_TARGET != HWY_SCALAR`:
* `V`: `u64` \
diff --git a/hwy/ops/generic_ops-inl.h b/hwy/ops/generic_ops-inl.h
index 99b518d99c..7447fcb6cc 100644
--- a/hwy/ops/generic_ops-inl.h
+++ b/hwy/ops/generic_ops-inl.h
@@ -488,6 +488,63 @@ HWY_API V InterleaveEven(V a, V b) {
}
#endif
+// ------------------------------ MinMagnitude/MaxMagnitude
+
+#if (defined(HWY_NATIVE_FLOAT_MIN_MAX_MAGNITUDE) == defined(HWY_TARGET_TOGGLE))
+#ifdef HWY_NATIVE_FLOAT_MIN_MAX_MAGNITUDE
+#undef HWY_NATIVE_FLOAT_MIN_MAX_MAGNITUDE
+#else
+#define HWY_NATIVE_FLOAT_MIN_MAX_MAGNITUDE
+#endif
+
+template
+HWY_API V MinMagnitude(V a, V b) {
+ const auto abs_a = Abs(a);
+ const auto abs_b = Abs(b);
+ return IfThenElse(Lt(abs_a, abs_b), a,
+ Min(IfThenElse(Eq(abs_a, abs_b), a, b), b));
+}
+
+template
+HWY_API V MaxMagnitude(V a, V b) {
+ const auto abs_a = Abs(a);
+ const auto abs_b = Abs(b);
+ return IfThenElse(Lt(abs_a, abs_b), b,
+ Max(IfThenElse(Eq(abs_a, abs_b), b, a), a));
+}
+
+#endif // HWY_NATIVE_FLOAT_MIN_MAX_MAGNITUDE
+
+template
+HWY_API V MinMagnitude(V a, V b) {
+ const DFromV d;
+ const RebindToUnsigned du;
+ const auto abs_a = BitCast(du, Abs(a));
+ const auto abs_b = BitCast(du, Abs(b));
+ return IfThenElse(RebindMask(d, Lt(abs_a, abs_b)), a,
+ Min(IfThenElse(RebindMask(d, Eq(abs_a, abs_b)), a, b), b));
+}
+
+template
+HWY_API V MaxMagnitude(V a, V b) {
+ const DFromV d;
+ const RebindToUnsigned du;
+ const auto abs_a = BitCast(du, Abs(a));
+ const auto abs_b = BitCast(du, Abs(b));
+ return IfThenElse(RebindMask(d, Lt(abs_a, abs_b)), b,
+ Max(IfThenElse(RebindMask(d, Eq(abs_a, abs_b)), b, a), a));
+}
+
+template
+HWY_API V MinMagnitude(V a, V b) {
+ return Min(a, b);
+}
+
+template
+HWY_API V MaxMagnitude(V a, V b) {
+ return Max(a, b);
+}
+
// ------------------------------ AddSub
template , 1)>
diff --git a/hwy/tests/minmax_test.cc b/hwy/tests/minmax_test.cc
index 3ef116d30d..1a08d56aee 100644
--- a/hwy/tests/minmax_test.cc
+++ b/hwy/tests/minmax_test.cc
@@ -257,6 +257,106 @@ HWY_NOINLINE void TestAllMinMax128Upper() {
ForGEVectors<128, TestMinMax128Upper>()(uint64_t());
}
+struct TestMinMaxMagnitude {
+ template
+ static constexpr MakeSigned MaxPosIotaVal(hwy::FloatTag /*type_tag*/) {
+ return static_cast>(MantissaMask() + 1);
+ }
+ template
+ static constexpr MakeSigned MaxPosIotaVal(hwy::NonFloatTag /*type_tag*/) {
+ return static_cast>(((LimitsMax>()) >> 1) + 1);
+ }
+
+ template
+ HWY_NOINLINE static void VerifyMinMaxMagnitude(
+ D d, const TFromD* HWY_RESTRICT in1_lanes,
+ const TFromD* HWY_RESTRICT in2_lanes, const int line) {
+ using T = TFromD;
+ using TAbs = If() || IsSpecialFloat(), T, MakeUnsigned>;
+
+ const char* file = __FILE__;
+ const size_t N = Lanes(d);
+ auto expected_min_mag = AllocateAligned(N);
+ auto expected_max_mag = AllocateAligned(N);
+ HWY_ASSERT(expected_min_mag && expected_max_mag);
+
+ for (size_t i = 0; i < N; i++) {
+ const T val1 = in1_lanes[i];
+ const T val2 = in2_lanes[i];
+ const TAbs abs_val1 = static_cast(ScalarAbs(val1));
+ const TAbs abs_val2 = static_cast(ScalarAbs(val2));
+ if (abs_val1 < abs_val2 || (abs_val1 == abs_val2 && val1 < val2)) {
+ expected_min_mag[i] = val1;
+ expected_max_mag[i] = val2;
+ } else {
+ expected_min_mag[i] = val2;
+ expected_max_mag[i] = val1;
+ }
+ }
+
+ const auto in1 = Load(d, in1_lanes);
+ const auto in2 = Load(d, in2_lanes);
+ AssertVecEqual(d, expected_min_mag.get(), MinMagnitude(in1, in2), file,
+ line);
+ AssertVecEqual(d, expected_min_mag.get(), MinMagnitude(in2, in1), file,
+ line);
+ AssertVecEqual(d, expected_max_mag.get(), MaxMagnitude(in1, in2), file,
+ line);
+ AssertVecEqual(d, expected_max_mag.get(), MaxMagnitude(in2, in1), file,
+ line);
+ }
+
+ template
+ HWY_NOINLINE void operator()(T /*unused*/, D d) {
+ using TI = MakeSigned;
+ using TU = MakeUnsigned;
+ constexpr TI kMaxPosIotaVal = MaxPosIotaVal(hwy::IsFloatTag());
+ static_assert(kMaxPosIotaVal > 0, "kMaxPosIotaVal > 0 must be true");
+
+ constexpr size_t kPositiveIotaMask = static_cast(
+ static_cast(kMaxPosIotaVal - 1) & (HWY_MAX_LANES_D(D) - 1));
+
+ const size_t N = Lanes(d);
+ auto in1_lanes = AllocateAligned(N);
+ auto in2_lanes = AllocateAligned(N);
+ auto in3_lanes = AllocateAligned(N);
+ auto in4_lanes = AllocateAligned(N);
+ HWY_ASSERT(in1_lanes && in2_lanes && in3_lanes && in4_lanes);
+
+ for (size_t i = 0; i < N; i++) {
+ const TI x1 = static_cast((i & kPositiveIotaMask) + 1);
+ const TI x2 = static_cast(kMaxPosIotaVal - x1);
+ const TI x3 = static_cast(-x1);
+ const TI x4 = static_cast(-x2);
+
+ in1_lanes[i] = ConvertScalarTo(x1);
+ in2_lanes[i] = ConvertScalarTo(x2);
+ in3_lanes[i] = ConvertScalarTo(x3);
+ in4_lanes[i] = ConvertScalarTo(x4);
+ }
+
+ VerifyMinMaxMagnitude(d, in1_lanes.get(), in2_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in1_lanes.get(), in3_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in1_lanes.get(), in4_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in2_lanes.get(), in3_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in2_lanes.get(), in4_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in3_lanes.get(), in4_lanes.get(), __LINE__);
+
+ in2_lanes[0] = HighestValue();
+ in4_lanes[0] = LowestValue();
+
+ VerifyMinMaxMagnitude(d, in1_lanes.get(), in2_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in1_lanes.get(), in4_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in2_lanes.get(), in3_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in2_lanes.get(), in4_lanes.get(), __LINE__);
+ VerifyMinMaxMagnitude(d, in3_lanes.get(), in4_lanes.get(), __LINE__);
+ }
+};
+
+HWY_NOINLINE void TestAllMinMaxMagnitude() {
+ ForAllTypes(ForPartialVectors());
+}
+
// NOLINTNEXTLINE(google-readability-namespace-comments)
} // namespace HWY_NAMESPACE
} // namespace hwy
@@ -269,6 +369,7 @@ HWY_BEFORE_TEST(HwyMinMaxTest);
HWY_EXPORT_AND_TEST_P(HwyMinMaxTest, TestAllMinMax);
HWY_EXPORT_AND_TEST_P(HwyMinMaxTest, TestAllMinMax128);
HWY_EXPORT_AND_TEST_P(HwyMinMaxTest, TestAllMinMax128Upper);
+HWY_EXPORT_AND_TEST_P(HwyMinMaxTest, TestAllMinMaxMagnitude);
HWY_AFTER_TEST();
} // namespace hwy