[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