[PATCH] math: Order signed zeros in fmin and fmax.

James Y Knight jyknight@google.com
Fri Dec 19 16:45:54 GMT 2025


The C standard (at least from C99 till 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. Many other libc
implementations have done so for a long time, such as freebsd,
openbsd, android, musl, llvm-libc, and Windows.
---
 math/libm-test-fmax.inc         | 4 ++--
 math/libm-test-fmin.inc         | 4 ++--
 math/s_fmax_template.c          | 6 ++++--
 math/s_fmin_template.c          | 6 ++++--
 sysdeps/i386/fpu/s_fmax.S       | 6 +++++-
 sysdeps/i386/fpu/s_fmaxf.S      | 6 +++++-
 sysdeps/i386/fpu/s_fmaxl.S      | 5 ++++-
 sysdeps/i386/fpu/s_fmin.S       | 6 +++++-
 sysdeps/i386/fpu/s_fminf.S      | 6 +++++-
 sysdeps/i386/fpu/s_fminl.S      | 5 ++++-
 sysdeps/i386/i686/fpu/s_fmax.S  | 7 ++++++-
 sysdeps/i386/i686/fpu/s_fmaxf.S | 7 ++++++-
 sysdeps/i386/i686/fpu/s_fmaxl.S | 6 +++++-
 sysdeps/i386/i686/fpu/s_fmin.S  | 7 ++++++-
 sysdeps/i386/i686/fpu/s_fminf.S | 7 ++++++-
 sysdeps/i386/i686/fpu/s_fminl.S | 6 +++++-
 sysdeps/x86_64/fpu/s_fmax.S     | 4 ++++
 sysdeps/x86_64/fpu/s_fmaxf.S    | 4 ++++
 sysdeps/x86_64/fpu/s_fmaxl.S    | 7 ++++++-
 sysdeps/x86_64/fpu/s_fmin.S     | 4 ++++
 sysdeps/x86_64/fpu/s_fminf.S    | 4 ++++
 sysdeps/x86_64/fpu/s_fminl.S    | 7 ++++++-
 22 files changed, 102 insertions(+), 22 deletions(-)

diff --git a/math/libm-test-fmax.inc b/math/libm-test-fmax.inc
index 2716bcf70e..4276e774f4 100644
--- a/math/libm-test-fmax.inc
+++ b/math/libm-test-fmax.inc
@@ -22,8 +22,8 @@ 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),
+    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 d0f5bf6b7e..577f54705b 100644
--- a/math/libm-test-fmin.inc
+++ b/math/libm-test-fmin.inc
@@ -22,8 +22,8 @@ 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),
+    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 87e6ee67df..2bd331c7dd 100644
--- a/math/s_fmax_template.c
+++ b/math/s_fmax_template.c
@@ -24,9 +24,11 @@ M_DECL_FUNC (__fmax) (FLOAT x, FLOAT y)
 #if M_USE_BUILTIN (FMAX)
   return M_SUF (__builtin_fmax) (x, y);
 #else
-  if (isgreaterequal (x, y))
+  if (isgreaterequal (x, y)) {
+    if (signbit(x) && !signbit(y))
+      return y;
     return x;
-  else if (isless (x, y))
+  } else if (isless (x, y))
     return y;
   else if (issignaling (x) || issignaling (y))
     return x + y;
diff --git a/math/s_fmin_template.c b/math/s_fmin_template.c
index 9ff330969f..4ac2f2fca1 100644
--- a/math/s_fmin_template.c
+++ b/math/s_fmin_template.c
@@ -24,9 +24,11 @@ M_DECL_FUNC (__fmin) (FLOAT x, FLOAT y)
 #if M_USE_BUILTIN (FMIN)
   return M_SUF (__builtin_fmin) (x, y);
 #else
-  if (islessequal (x, y))
+  if (islessequal (x, y)) {
+    if (!signbit(x) && signbit(y))
+      return y;
     return x;
-  else if (isgreater (x, y))
+  } else if (isgreater (x, y))
     return y;
   else if (issignaling (x) || issignaling (y))
     return x + y;
diff --git a/sysdeps/i386/fpu/s_fmax.S b/sysdeps/i386/fpu/s_fmax.S
index 8fa48ae013..7171b048ec 100644
--- a/sysdeps/i386/fpu/s_fmax.S
+++ b/sysdeps/i386/fpu/s_fmax.S
@@ -33,8 +33,12 @@ ENTRY(__fmax)
 	fucom	%st(1)
 	fnstsw
 	sahf
-	jnc	1f
+	jnz	2f
+	jp	2f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 19(%esp)
 
+2:	jnc	1f
 	fxch	%st(1)
 1:	fstp	%st(1)
 
diff --git a/sysdeps/i386/fpu/s_fmaxf.S b/sysdeps/i386/fpu/s_fmaxf.S
index 5c881a849d..c8d0fb0412 100644
--- a/sysdeps/i386/fpu/s_fmaxf.S
+++ b/sysdeps/i386/fpu/s_fmaxf.S
@@ -33,8 +33,12 @@ ENTRY(__fmaxf)
 	fucom	%st(1)
 	fnstsw
 	sahf
-	jnc	1f
+	jnz	2f
+	jp	2f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 11(%esp)
 
+2:	jnc	1f
 	fxch	%st(1)
 1:	fstp	%st(1)
 
diff --git a/sysdeps/i386/fpu/s_fmaxl.S b/sysdeps/i386/fpu/s_fmaxl.S
index 77526e3b99..3655231a2e 100644
--- a/sysdeps/i386/fpu/s_fmaxl.S
+++ b/sysdeps/i386/fpu/s_fmaxl.S
@@ -39,7 +39,10 @@ ENTRY(__fmaxl)
 	fucom	%st(1)
 	fnstsw
 	sahf
-	jnc	1f
+	jnz	5f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 25(%esp)
+5:	jnc	1f
 
 	fxch	%st(1)
 1:	fstp	%st(1)
diff --git a/sysdeps/i386/fpu/s_fmin.S b/sysdeps/i386/fpu/s_fmin.S
index e15e668a61..5d77b5e77c 100644
--- a/sysdeps/i386/fpu/s_fmin.S
+++ b/sysdeps/i386/fpu/s_fmin.S
@@ -33,8 +33,12 @@ ENTRY(__fmin)
 	fucom	%st(1)
 	fnstsw
 	sahf
-	jc	2f
+	jnz	3f
+	jp	3f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 11(%esp)
 
+3:	jc	2f
 1:	fxch	%st(1)
 2:	fstp	%st(1)
 
diff --git a/sysdeps/i386/fpu/s_fminf.S b/sysdeps/i386/fpu/s_fminf.S
index a5cc4e28e5..30b2b80ed2 100644
--- a/sysdeps/i386/fpu/s_fminf.S
+++ b/sysdeps/i386/fpu/s_fminf.S
@@ -33,8 +33,12 @@ ENTRY(__fminf)
 	fucom	%st(1)
 	fnstsw
 	sahf
-	jc	2f
+	jnz	3f
+	jp	3f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 7(%esp)
 
+3:	jc	2f
 1:	fxch	%st(1)
 2:	fstp	%st(1)
 
diff --git a/sysdeps/i386/fpu/s_fminl.S b/sysdeps/i386/fpu/s_fminl.S
index f23a5008da..e0cd4669d7 100644
--- a/sysdeps/i386/fpu/s_fminl.S
+++ b/sysdeps/i386/fpu/s_fminl.S
@@ -39,7 +39,10 @@ ENTRY(__fminl)
 	fucom	%st(1)
 	fnstsw
 	sahf
-	jc	1f
+	jnz	5f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 25(%esp)
+5:	jc	1f
 
 	fxch	%st(1)
 1:	fstp	%st(1)
diff --git a/sysdeps/i386/i686/fpu/s_fmax.S b/sysdeps/i386/i686/fpu/s_fmax.S
index 3b5b2fee4f..7158c8ae95 100644
--- a/sysdeps/i386/i686/fpu/s_fmax.S
+++ b/sysdeps/i386/i686/fpu/s_fmax.S
@@ -30,7 +30,12 @@ ENTRY(__fmax)
 	fxch
 
 	fucomi	%st(1), %st
-	fcmovb	%st(1), %st
+	jnz 1f
+	jp 1f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 19(%esp)
+
+1:	fcmovb	%st(1), %st
 
 	fstp	%st(1)
 
diff --git a/sysdeps/i386/i686/fpu/s_fmaxf.S b/sysdeps/i386/i686/fpu/s_fmaxf.S
index 65a42ddffc..da033ea95b 100644
--- a/sysdeps/i386/i686/fpu/s_fmaxf.S
+++ b/sysdeps/i386/i686/fpu/s_fmaxf.S
@@ -30,7 +30,12 @@ ENTRY(__fmaxf)
 	fxch
 
 	fucomi	%st(1), %st
-	fcmovb	%st(1), %st
+	jnz 1f
+	jp 1f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 11(%esp)
+
+1:	fcmovb	%st(1), %st
 
 	fstp	%st(1)
 
diff --git a/sysdeps/i386/i686/fpu/s_fmaxl.S b/sysdeps/i386/i686/fpu/s_fmaxl.S
index dd1d6a3f00..41c755d91e 100644
--- a/sysdeps/i386/i686/fpu/s_fmaxl.S
+++ b/sysdeps/i386/i686/fpu/s_fmaxl.S
@@ -25,9 +25,13 @@ ENTRY(__fmaxl)
 	fldt	16(%esp)	// x : y
 
 	fucomi	%st(1), %st
+	jnz	1f
 	jp	2f
-	fcmovb	%st(1), %st
 
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 13(%esp)
+
+1:	fcmovb	%st(1), %st
 	fstp	%st(1)
 
 	ret
diff --git a/sysdeps/i386/i686/fpu/s_fmin.S b/sysdeps/i386/i686/fpu/s_fmin.S
index 8df9fa32cf..11bbafb6b5 100644
--- a/sysdeps/i386/i686/fpu/s_fmin.S
+++ b/sysdeps/i386/i686/fpu/s_fmin.S
@@ -28,7 +28,12 @@ ENTRY(__fmin)
 	fcmovu	%st(1), %st	// now %st contains y if not NaN, x otherwise
 
 	fucomi	%st(1), %st
-	fcmovnb	%st(1), %st
+	jnz 1f
+	jp 1f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 11(%esp)
+
+1:	fcmovnb	%st(1), %st
 
 	fstp	%st(1)
 
diff --git a/sysdeps/i386/i686/fpu/s_fminf.S b/sysdeps/i386/i686/fpu/s_fminf.S
index a4623c40e1..9f088ffe01 100644
--- a/sysdeps/i386/i686/fpu/s_fminf.S
+++ b/sysdeps/i386/i686/fpu/s_fminf.S
@@ -28,7 +28,12 @@ ENTRY(__fminf)
 	fcmovu	%st(1), %st	// now %st contains y if not NaN, x otherwise
 
 	fucomi	%st(1), %st
-	fcmovnb	%st(1), %st
+	jnz 1f
+	jp 1f
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 7(%esp)
+
+1:	fcmovnb	%st(1), %st
 
 	fstp	%st(1)
 
diff --git a/sysdeps/i386/i686/fpu/s_fminl.S b/sysdeps/i386/i686/fpu/s_fminl.S
index 90629bda2f..b1922d1cd4 100644
--- a/sysdeps/i386/i686/fpu/s_fminl.S
+++ b/sysdeps/i386/i686/fpu/s_fminl.S
@@ -25,9 +25,13 @@ ENTRY(__fminl)
 	fldt	16(%esp)	// x : y
 
 	fucomi	%st(1), %st
+	jnz	1f
 	jp	2f
-	fcmovnb	%st(1), %st
 
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 13(%esp)
+
+1:	fcmovnb	%st(1), %st
 	fstp	%st(1)
 
 	ret
diff --git a/sysdeps/x86_64/fpu/s_fmax.S b/sysdeps/x86_64/fpu/s_fmax.S
index 141539380a..73bc79d974 100644
--- a/sysdeps/x86_64/fpu/s_fmax.S
+++ b/sysdeps/x86_64/fpu/s_fmax.S
@@ -23,9 +23,13 @@
 ENTRY(__fmax)
 	ucomisd	%xmm0, %xmm1
 	jp	1f		// jump if unordered
+	je	5f		// jump if equal
 	maxsd	%xmm1, %xmm0
 	jmp	2f
 
+5:	pand	%xmm1, %xmm0	// and sign bits
+	jmp	2f
+
 1:	ucomisd	%xmm1, %xmm1	// Is xmm1 a NaN?
 	jp	3f
 	// xmm0 is a NaN; xmm1 is not.  Test if xmm0 is signaling.
diff --git a/sysdeps/x86_64/fpu/s_fmaxf.S b/sysdeps/x86_64/fpu/s_fmaxf.S
index 6607a3a566..24c5817f57 100644
--- a/sysdeps/x86_64/fpu/s_fmaxf.S
+++ b/sysdeps/x86_64/fpu/s_fmaxf.S
@@ -23,9 +23,13 @@
 ENTRY(__fmaxf)
 	ucomiss	%xmm0, %xmm1
 	jp	1f		// jump if unordered
+	je	5f		// jump if equal
 	maxss	%xmm1, %xmm0
 	jmp	2f
 
+5:	pand	%xmm1, %xmm0	// and sign bits
+	jmp	2f
+
 1:	ucomiss	%xmm1, %xmm1	// Is xmm1 a NaN?
 	jp	3f
 	// xmm0 is a NaN; xmm1 is not.  Test if xmm0 is signaling.
diff --git a/sysdeps/x86_64/fpu/s_fmaxl.S b/sysdeps/x86_64/fpu/s_fmaxl.S
index f2cc11bb49..c9c2df0e8e 100644
--- a/sysdeps/x86_64/fpu/s_fmaxl.S
+++ b/sysdeps/x86_64/fpu/s_fmaxl.S
@@ -25,8 +25,13 @@ ENTRY(__fmaxl)
 	fldt	24(%rsp)	// x : y
 
 	fucomi	%st(1), %st
+	jnz	1f
 	jp	2f
-	fcmovb	%st(1), %st
+
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 17(%rsp)
+
+1:	fcmovb	%st(1), %st
 
 	fstp	%st(1)
 
diff --git a/sysdeps/x86_64/fpu/s_fmin.S b/sysdeps/x86_64/fpu/s_fmin.S
index 0cbcb6d115..394672b34a 100644
--- a/sysdeps/x86_64/fpu/s_fmin.S
+++ b/sysdeps/x86_64/fpu/s_fmin.S
@@ -23,9 +23,13 @@
 ENTRY(__fmin)
 	ucomisd	%xmm0, %xmm1
 	jp	1f		// jump if unordered
+	je	5f		// jump if equal
 	minsd	%xmm1, %xmm0
 	jmp	2f
 
+5:	por	%xmm1, %xmm0 	// or sign bits
+	jmp	2f
+
 1:	ucomisd	%xmm1, %xmm1	// Is xmm1 a NaN?
 	jp	3f
 	// xmm0 is a NaN; xmm1 is not.  Test if xmm0 is signaling.
diff --git a/sysdeps/x86_64/fpu/s_fminf.S b/sysdeps/x86_64/fpu/s_fminf.S
index 2aa5fb58a7..5a69ae7665 100644
--- a/sysdeps/x86_64/fpu/s_fminf.S
+++ b/sysdeps/x86_64/fpu/s_fminf.S
@@ -23,9 +23,13 @@
 ENTRY(__fminf)
 	ucomiss	%xmm0, %xmm1
 	jp	1f		// jump if unordered
+	je	5f		// jump if equal
 	minss	%xmm1, %xmm0
 	jmp	2f
 
+5:	por	%xmm1, %xmm0	// or sign bits
+	jmp	2f
+
 1:	ucomiss	%xmm1, %xmm1	// Is xmm1 a NaN?
 	jp	3f
 	// xmm0 is a NaN; xmm1 is not.  Test if xmm0 is signaling.
diff --git a/sysdeps/x86_64/fpu/s_fminl.S b/sysdeps/x86_64/fpu/s_fminl.S
index 7f9f17066d..1cdc6b0e44 100644
--- a/sysdeps/x86_64/fpu/s_fminl.S
+++ b/sysdeps/x86_64/fpu/s_fminl.S
@@ -25,8 +25,13 @@ ENTRY(__fminl)
 	fldt	24(%rsp)	// x : y
 
 	fucomi	%st(1), %st
+	jnz	1f
 	jp	2f
-	fcmovnb	%st(1), %st
+
+	// Equal; check sign, so -0.0 < 0.0
+	cmpb $0x80, 17(%rsp)
+
+1:	fcmovnb	%st(1), %st
 
 	fstp	%st(1)
 
-- 
2.52.0.322.g1dd061c0dc-goog



More information about the Libc-alpha mailing list