[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