[PATCH 06/12] math: Order signed zeros in f{max,min}{f,l,f128}

Adhemerval Zanella adhemerval.zanella@linaro.org
Fri Jan 16 17:07:52 GMT 2026


The C standard (at least from C99 until C23) does not require
fmin/fmax to order zeros by their sign, so glibc's previous behavior
was entirely standards-conforming.  However, the standard does
recommend that zeros be ordered in a footnote, saying:

"If possible, fmax is sensitive to the sign of zero, for example
fmax(−0.0, +0.0) ideally returns +0."

As this is indeed possible (and not too complicated), implement it as
a quality-of-implementation improvement.  It also remove possible
deviations between architectures, where for some architectures that
has direct mapping instruction (USE_FMA*_BUILTIN) they already do
the ordering.

Checked on x86_64-linux-gnu, aarch64-linux-gnu, i686-linux-gnu,
arm-linux-gnueabihf, powerpc64le-linux-gnu,
riscv64-linux-gnu-rv64imafdc-lp64d, and loongarch64-linux-gnuf64.

Co-authored-by: James Y Knight <jyknight@google.com>
---
 manual/arith.texi          | 7 +++++--
 math/libm-test-fmax.inc    | 6 ++++--
 math/libm-test-fmin.inc    | 6 ++++--
 math/s_fmax_template.c     | 6 +++++-
 math/s_fmin_template.c     | 6 +++++-
 sysdeps/i386/fpu/s_fmax.c  | 6 +++++-
 sysdeps/i386/fpu/s_fmaxf.c | 6 +++++-
 sysdeps/i386/fpu/s_fmin.c  | 6 +++++-
 sysdeps/i386/fpu/s_fminf.c | 6 +++++-
 9 files changed, 43 insertions(+), 12 deletions(-)

diff --git a/manual/arith.texi b/manual/arith.texi
index a344153d24..7846b6903e 100644
--- a/manual/arith.texi
+++ b/manual/arith.texi
@@ -2110,9 +2110,12 @@ perform these operations faster than the equivalent C code.
 @standardsx{fminfNx, TS 18661-3:2015, math.h}
 @safety{@prelim{}@mtsafe{}@assafe{}@acsafe{}}
 The @code{fmin} function returns the lesser of the two values @var{x}
-and @var{y}.  It is similar to the expression
+and @var{y} respecting the order signed zeros.  It is similar to the
+expression
 @smallexample
-((x) < (y) ? (x) : (y))
+if (x == y)
+  return signbit (x) ? x : y;
+return ((x) < (y) ? (x) : (y))
 @end smallexample
 except that @var{x} and @var{y} are only evaluated once.
 
diff --git a/math/libm-test-fmax.inc b/math/libm-test-fmax.inc
index 3d306004a7..ecebdaf4ca 100644
--- a/math/libm-test-fmax.inc
+++ b/math/libm-test-fmax.inc
@@ -22,8 +22,10 @@ static const struct test_ff_f_data fmax_test_data[] =
   {
     TEST_ff_f (fmax, 0, 0, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmax, minus_zero, minus_zero, minus_zero, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
-    TEST_ff_f (fmax, 0, minus_zero, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED|IGNORE_ZERO_INF_SIGN),
-    TEST_ff_f (fmax, minus_zero, 0, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED|IGNORE_ZERO_INF_SIGN),
+    /* The order signed zeros are implemented as a QoI in the generic implementation
+       and expected for USE_F{MIN,MAX}{F,L,F128}_BUILTIN.  */
+    TEST_ff_f (fmax, 0, minus_zero, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
+    TEST_ff_f (fmax, minus_zero, 0, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmax, min_subnorm_value, -min_subnorm_value, min_subnorm_value, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmax, -min_subnorm_value, min_subnorm_value, min_subnorm_value, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmax, min_value, -min_value, min_value, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
diff --git a/math/libm-test-fmin.inc b/math/libm-test-fmin.inc
index 9c8d1209ea..24de456f85 100644
--- a/math/libm-test-fmin.inc
+++ b/math/libm-test-fmin.inc
@@ -22,8 +22,10 @@ static const struct test_ff_f_data fmin_test_data[] =
   {
     TEST_ff_f (fmin, 0, 0, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmin, minus_zero, minus_zero, minus_zero, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
-    TEST_ff_f (fmin, 0, minus_zero, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED|IGNORE_ZERO_INF_SIGN),
-    TEST_ff_f (fmin, minus_zero, 0, 0, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED|IGNORE_ZERO_INF_SIGN),
+    /* The order signed zeros are implemented as a QoI in the generic implementation
+       and expected for USE_F{MIN,MAX}{F,L,F128}_BUILTIN.  */
+    TEST_ff_f (fmin, 0, minus_zero, minus_zero, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
+    TEST_ff_f (fmin, minus_zero, 0, minus_zero, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmin, min_subnorm_value, -min_subnorm_value, -min_subnorm_value, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmin, -min_subnorm_value, min_subnorm_value, -min_subnorm_value, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
     TEST_ff_f (fmin, min_value, -min_value, -min_value, NO_INEXACT_EXCEPTION|ERRNO_UNCHANGED),
diff --git a/math/s_fmax_template.c b/math/s_fmax_template.c
index 5921d2b43c..8e3fde4af1 100644
--- a/math/s_fmax_template.c
+++ b/math/s_fmax_template.c
@@ -25,7 +25,11 @@ M_DECL_FUNC (__fmax) (FLOAT x, FLOAT y)
   return M_SUF (__builtin_fmax) (x, y);
 #else
   if (__glibc_likely (!isunordered (x, y)))
-    return x > y ? x : y;
+    {
+      if (__glibc_unlikely (x == y))
+	return signbit (x) ? y : x;
+      return x > y ? x : y;
+    }
   else if (issignaling (x) || issignaling (y))
     return x + y;
   else
diff --git a/math/s_fmin_template.c b/math/s_fmin_template.c
index e78358c8ed..0ed32cd06c 100644
--- a/math/s_fmin_template.c
+++ b/math/s_fmin_template.c
@@ -25,7 +25,11 @@ M_DECL_FUNC (__fmin) (FLOAT x, FLOAT y)
   return M_SUF (__builtin_fmin) (x, y);
 #else
   if (__glibc_likely (!isunordered (x, y)))
-    return x > y ? y : x;
+    {
+      if (__glibc_unlikely (x == y))
+	return signbit (x) ? x : y;
+      return x > y ? y : x;
+    }
   else if (issignaling (x) || issignaling (y))
     return x + y;
   else
diff --git a/sysdeps/i386/fpu/s_fmax.c b/sysdeps/i386/fpu/s_fmax.c
index c191bfa09b..0a058950be 100644
--- a/sysdeps/i386/fpu/s_fmax.c
+++ b/sysdeps/i386/fpu/s_fmax.c
@@ -26,7 +26,11 @@ __fmax (double x, double y)
      already raises FE_INVALID if an operand is a sNaN.  So there is no need
      to check for issignaling.  */
   if (__glibc_likely (!isunordered (x, y)))
-    return x > y ? x : y;
+    {
+      if (__glibc_unlikely (x == y))
+       return signbit (x) ? y : x;
+      return x > y ? x : y;
+    }
   return isnan (y) ? x : y;
 }
 libm_alias_double (__fmax, fmax)
diff --git a/sysdeps/i386/fpu/s_fmaxf.c b/sysdeps/i386/fpu/s_fmaxf.c
index 431f519d00..7c12880eb0 100644
--- a/sysdeps/i386/fpu/s_fmaxf.c
+++ b/sysdeps/i386/fpu/s_fmaxf.c
@@ -26,7 +26,11 @@ __fmaxf (float x, float y)
      already raises FE_INVALID if an operand is a sNaN.  So there is no need
      to check for issignaling.  */
   if (__glibc_likely (!isunordered (x, y)))
-    return x > y ? x : y;
+    {
+      if (__glibc_unlikely (x == y))
+       return signbit (x) ? y : x;
+      return x > y ? x : y;
+    }
   return isnan (y) ? x : y;
 }
 libm_alias_float (__fmax, fmax)
diff --git a/sysdeps/i386/fpu/s_fmin.c b/sysdeps/i386/fpu/s_fmin.c
index 6784dc6550..db639241ab 100644
--- a/sysdeps/i386/fpu/s_fmin.c
+++ b/sysdeps/i386/fpu/s_fmin.c
@@ -26,7 +26,11 @@ __fmin (double x, double y)
      already raises FE_INVALID if an operand is a sNaN.  So there is no need
      to check for issignaling.  */
   if (__glibc_likely (!isunordered (x, y)))
-    return x > y ? y : x;
+    {
+      if (__glibc_unlikely (x == y))
+	return signbit (x) ? x : y;
+      return x > y ? y : x;
+    }
   return isnan (y) ? x : y;
 }
 libm_alias_double (__fmin, fmin)
diff --git a/sysdeps/i386/fpu/s_fminf.c b/sysdeps/i386/fpu/s_fminf.c
index 026998286e..d60a02538c 100644
--- a/sysdeps/i386/fpu/s_fminf.c
+++ b/sysdeps/i386/fpu/s_fminf.c
@@ -26,7 +26,11 @@ __fminf (float x, float y)
      already raises FE_INVALID if an operand is a sNaN.  So there is no need
      to check for issignaling.  */
   if (__glibc_likely (!isunordered (x, y)))
-    return x > y ? y : x;
+    {
+      if (__glibc_unlikely (x == y))
+	return signbit (x) ? x : y;
+      return x > y ? y : x;
+    }
   return isnan (y) ? x : y;
 }
 libm_alias_float (__fmin, fmin)
-- 
2.43.0



More information about the Libc-alpha mailing list