Page Menu
Home
FreeBSD
Search
Configure Global Search
Log In
Files
F175730978
D60642.id.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
13 KB
Referenced Files
None
Subscribers
None
D60642.id.diff
View Options
diff --git a/lib/libc/gen/isgreater.3 b/lib/libc/gen/isgreater.3
--- a/lib/libc/gen/isgreater.3
+++ b/lib/libc/gen/isgreater.3
@@ -82,6 +82,7 @@
of the relationships (less, greater, equal, unordered) holds.
.Sh SEE ALSO
.Xr fpclassify 3 ,
+.Xr iseqsig 3 ,
.Xr math 3 ,
.Xr signbit 3
.Sh STANDARDS
diff --git a/lib/msun/Makefile b/lib/msun/Makefile
--- a/lib/msun/Makefile
+++ b/lib/msun/Makefile
@@ -88,7 +88,8 @@
s_fminimum_mag.c s_fminimum_magf.c s_fminimum_mag_num.c \
s_fminimum_mag_numf.c s_fminimum_num.c s_fminimum_numf.c \
s_frexp.c s_frexpf.c s_ilogb.c s_ilogbf.c \
- s_ilogbl.c s_isfinite.c s_isnan.c s_isnormal.c \
+ s_ilogbl.c s_iseqsig.c s_iseqsigf.c s_isfinite.c s_isnan.c \
+ s_isnormal.c \
s_llrint.c s_llrintf.c s_llround.c s_llroundf.c s_llroundl.c \
s_log1p.c s_log1pf.c s_logb.c s_logbf.c s_lrint.c s_lrintf.c \
s_lround.c s_lroundf.c s_lroundl.c s_modff.c \
@@ -143,7 +144,7 @@
s_fmaximum_numl.c s_fmaximum_mag_numl.c \
s_fminl.c s_fminimuml.c s_fminimum_magl.c \
s_fminimum_numl.c s_fminimum_mag_numl.c \
- s_frexpl.c s_logbl.c s_logl.c s_nanl.c \
+ s_frexpl.c s_iseqsigl.c s_logbl.c s_logl.c s_nanl.c \
s_nextafterl.c s_nexttoward.c s_remquol.c s_rintl.c s_roundl.c \
s_scalbnl.c s_sinl.c s_sincosl.c s_sinpil.c s_rsqrtl.c \
s_tanhl.c s_tanl.c s_tanpil.c s_truncl.c w_cabsl.c
@@ -191,7 +192,7 @@
fegetmode.3 fegetround.3 fenv.3 floor.3 fma.3 \
fmax.3 fmaximum.3 fmaximum_mag.3 \
fmaximum_mag_num.3 fmaximum_num.3 fmod.3 \
- hypot.3 ieee.3 ieee_test.3 ilogb.3 j0.3 \
+ hypot.3 ieee.3 ieee_test.3 ilogb.3 iseqsig.3 j0.3 \
lgamma.3 log.3 lrint.3 lround.3 math.3 nan.3 \
nextafter.3 remainder.3 rint.3 \
round.3 scalbn.3 signbit.3 sin.3 sincos.3 \
diff --git a/lib/msun/Symbol.map b/lib/msun/Symbol.map
--- a/lib/msun/Symbol.map
+++ b/lib/msun/Symbol.map
@@ -356,6 +356,9 @@
fminimum_mag_num;
fminimum_mag_numf;
fminimum_mag_numl;
+ __iseqsig;
+ __iseqsigf;
+ __iseqsigl;
rsqrt;
rsqrtf;
rsqrtl;
diff --git a/lib/msun/man/iseqsig.3 b/lib/msun/man/iseqsig.3
new file mode 100644
--- /dev/null
+++ b/lib/msun/man/iseqsig.3
@@ -0,0 +1,56 @@
+.\" Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd October 12, 2026
+.Dt ISEQSIG 3
+.Os
+.Sh NAME
+.Nm iseqsig
+.Nd "signaling floating-point equality comparison"
+.Sh LIBRARY
+.Lb libm
+.Sh SYNOPSIS
+.In math.h
+.Ft int
+.Fn iseqsig "real-floating x" "real-floating y"
+.Sh DESCRIPTION
+The
+.Fn iseqsig
+macro determines whether its arguments are equal.
+Arguments of different types are compared as by the
+.Li ==
+operator, after the usual arithmetic conversions.
+Positive and negative zero compare equal.
+.Pp
+Unlike the equality operator, which is a quiet comparison,
+.Fn iseqsig
+raises the
+.Dv FE_INVALID
+floating-point exception if either argument is a NaN, quiet or
+signaling, considered a domain error.
+As
+.Va math_errhandling
+is
+.Dv MATH_ERREXCEPT ,
+.Va errno
+is not set.
+.Sh RETURN VALUES
+The
+.Fn iseqsig
+macro returns 1 if its arguments are equal and 0 otherwise.
+If either argument is a NaN, it returns 0.
+.Sh SEE ALSO
+.Xr fenv 3 ,
+.Xr isgreater 3 ,
+.Xr math 3
+.Sh STANDARDS
+The
+.Fn iseqsig
+macro conforms to
+.St -isoC-2023 .
+.Sh HISTORY
+The
+.Fn iseqsig
+macro first appeared in
+.Fx 16.0 .
diff --git a/lib/msun/man/math.3 b/lib/msun/man/math.3
--- a/lib/msun/man/math.3
+++ b/lib/msun/man/math.3
@@ -178,6 +178,10 @@
islessgreater less than or greater than relation
isunordered unordered relation
.El
+.Ss Signaling Order Predicates
+.Cl
+iseqsig equal to relation, signaling on NaN
+.El
.Ss Transcendental Functions
.Cl
acos inverse cosine
diff --git a/lib/msun/src/math.h b/lib/msun/src/math.h
--- a/lib/msun/src/math.h
+++ b/lib/msun/src/math.h
@@ -114,6 +114,37 @@
#define isunordered(x, y) (isnan(x) || isnan(y))
#endif /* __MATH_BUILTIN_RELOPS */
+#if __ISO_C_VISIBLE >= 2023
+/*
+ * Dispatch on the common type of the arguments widened to float_t, so that
+ * an argument evaluated in a format wider than its semantic type is not
+ * narrowed before it is compared (C23 §F.10.14.1). The selector is never
+ * evaluated.
+ */
+#if __STDC_VERSION__ >= 201112L || __has_extension(c_generic_selections)
+#define iseqsig(x, y) __extension__ _Generic((x) + (y) + (__float_t)0, \
+ float: __iseqsigf, \
+ double: __iseqsig, \
+ long double: __iseqsigl)((x), (y))
+#elif !defined(__cplusplus)
+#define iseqsig(x, y) __builtin_choose_expr( \
+ __builtin_types_compatible_p(__typeof((x) + (y) + (__float_t)0), \
+ long double), __iseqsigl((x), (y)), \
+ __builtin_choose_expr( \
+ __builtin_types_compatible_p(__typeof((x) + (y) + (__float_t)0), \
+ double), __iseqsig((x), (y)), \
+ __builtin_choose_expr( \
+ __builtin_types_compatible_p(__typeof((x) + (y) + (__float_t)0), \
+ float), __iseqsigf((x), (y)), (void)0)))
+#else
+#define iseqsig(x, y) \
+ ((sizeof((x) + (y) + (__float_t)0) == sizeof(float)) ? \
+ __iseqsigf((x), (y)) : \
+ (sizeof((x) + (y) + (__float_t)0) == sizeof(double)) ? \
+ __iseqsig((x), (y)) : __iseqsigl((x), (y)))
+#endif
+#endif /* __ISO_C_VISIBLE >= 2023 */
+
#define signbit(x) __fp_type_select(x, __signbitf, __signbit, __signbitl)
typedef __double_t double_t;
@@ -556,6 +587,11 @@
double rsqrt(double);
float rsqrtf(float);
long double rsqrtl(long double);
+
+/* Not __pure2: these raise "invalid" on a NaN argument. */
+int __iseqsig(double, double);
+int __iseqsigf(float, float);
+int __iseqsigl(long double, long double);
#endif /* __ISO_C_VISIBLE >= 2023 */
__END_DECLS
diff --git a/lib/msun/src/s_iseqsig.c b/lib/msun/src/s_iseqsig.c
new file mode 100644
--- /dev/null
+++ b/lib/msun/src/s_iseqsig.c
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fenv.h>
+#include <float.h>
+#include <math.h>
+
+int
+__iseqsig(double x, double y)
+{
+ if (isunordered(x, y)) {
+ feraiseexcept(FE_INVALID);
+ return (0);
+ }
+ return (x == y);
+}
+
+#if (LDBL_MANT_DIG == 53)
+__weak_reference(__iseqsig, __iseqsigl);
+#endif
diff --git a/lib/msun/src/s_iseqsigf.c b/lib/msun/src/s_iseqsigf.c
new file mode 100644
--- /dev/null
+++ b/lib/msun/src/s_iseqsigf.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fenv.h>
+#include <math.h>
+
+int
+__iseqsigf(float x, float y)
+{
+ if (isunordered(x, y)) {
+ feraiseexcept(FE_INVALID);
+ return (0);
+ }
+ return (x == y);
+}
diff --git a/lib/msun/src/s_iseqsigl.c b/lib/msun/src/s_iseqsigl.c
new file mode 100644
--- /dev/null
+++ b/lib/msun/src/s_iseqsigl.c
@@ -0,0 +1,18 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <fenv.h>
+#include <math.h>
+
+int
+__iseqsigl(long double x, long double y)
+{
+ if (isunordered(x, y)) {
+ feraiseexcept(FE_INVALID);
+ return (0);
+ }
+ return (x == y);
+}
diff --git a/lib/msun/tests/Makefile b/lib/msun/tests/Makefile
--- a/lib/msun/tests/Makefile
+++ b/lib/msun/tests/Makefile
@@ -63,6 +63,7 @@
ATF_TESTS_C+= ilogb2_test
ATF_TESTS_C+= invtrig_test
ATF_TESTS_C+= invctrig_test
+ATF_TESTS_C+= iseqsig_test
ATF_TESTS_C+= logarithm_test
ATF_TESTS_C+= lrint_test
ATF_TESTS_C+= lround_test
diff --git a/lib/msun/tests/iseqsig_test.c b/lib/msun/tests/iseqsig_test.c
new file mode 100644
--- /dev/null
+++ b/lib/msun/tests/iseqsig_test.c
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2026 Faraz Vahedi <kfv@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <sys/cdefs.h>
+#include <fenv.h>
+#include <float.h>
+#include <math.h>
+
+#include "test-utils.h"
+
+#pragma STDC FENV_ACCESS ON
+
+/*
+ * Test that iseqsig(x, y) returns exactly `expected' and raises exactly
+ * `excepts' out of the standard exceptions.
+ */
+#define TEST(tx, x, ty, y, expected, excepts) do { \
+ volatile tx __x = (x); \
+ volatile ty __y = (y); \
+ int __r; \
+ ATF_REQUIRE_EQ(0, feclearexcept(FE_ALL_EXCEPT)); \
+ __r = iseqsig(__x, __y); \
+ CHECK_FP_EXCEPTIONS_MSG((excepts), ALL_STD_EXCEPT, \
+ "iseqsig((" #tx ")%La, (" #ty ")%La)", \
+ (long double)__x, (long double)__y); \
+ ATF_CHECK_EQ_MSG((expected), __r, \
+ "iseqsig((" #tx ")%La, (" #ty ")%La) = %d, expected %d", \
+ (long double)__x, (long double)__y, __r, (expected)); \
+} while (0)
+
+/* Test x and y in both orders, in each of the three types. */
+#define TEST_ALL(x, y, expected, excepts) do { \
+ TEST(float, x, float, y, expected, excepts); \
+ TEST(float, y, float, x, expected, excepts); \
+ TEST(double, x, double, y, expected, excepts); \
+ TEST(double, y, double, x, expected, excepts); \
+ TEST(long double, x, long double, y, expected, excepts); \
+ TEST(long double, y, long double, x, expected, excepts); \
+} while (0)
+
+/* Edge cases that depend on the format, in both orders. */
+#define TEST_FORMAT(t, max, true_min, next1) do { \
+ TEST(t, max, t, max, 1, 0); \
+ TEST(t, -max, t, -max, 1, 0); \
+ TEST(t, max, t, INFINITY, 0, 0); \
+ TEST(t, INFINITY, t, max, 0, 0); \
+ TEST(t, true_min, t, true_min, 1, 0); \
+ TEST(t, true_min, t, 0.0, 0, 0); \
+ TEST(t, 0.0, t, true_min, 0, 0); \
+ TEST(t, -true_min, t, -0.0, 0, 0); \
+ TEST(t, true_min, t, -true_min, 0, 0); \
+ TEST(t, 1.0, t, next1, 0, 0); \
+ TEST(t, next1, t, 1.0, 0, 0); \
+ TEST(t, next1, t, next1, 1, 0); \
+} while (0)
+
+ATF_TC_WITHOUT_HEAD(equal);
+ATF_TC_BODY(equal, tc)
+{
+ TEST_ALL(0.0, 0.0, 1, 0);
+ TEST_ALL(0.0, -0.0, 1, 0);
+ TEST_ALL(-0.0, -0.0, 1, 0);
+ TEST_ALL(1.0, 1.0, 1, 0);
+ TEST_ALL(-0.5, -0.5, 1, 0);
+ TEST_ALL(0x1.fffffep+100, 0x1.fffffep+100, 1, 0);
+ TEST_ALL(INFINITY, INFINITY, 1, 0);
+ TEST_ALL(-INFINITY, -INFINITY, 1, 0);
+}
+
+ATF_TC_WITHOUT_HEAD(unequal);
+ATF_TC_BODY(unequal, tc)
+{
+ TEST_ALL(0.0, 1.0, 0, 0);
+ TEST_ALL(1.0, -1.0, 0, 0);
+ TEST_ALL(1.0, 2.0, 0, 0);
+ TEST_ALL(0.0, INFINITY, 0, 0);
+ TEST_ALL(-0.0, -INFINITY, 0, 0);
+ TEST_ALL(INFINITY, -INFINITY, 0, 0);
+}
+
+ATF_TC_WITHOUT_HEAD(format_edges);
+ATF_TC_BODY(format_edges, tc)
+{
+ TEST_FORMAT(float, FLT_MAX, FLT_TRUE_MIN, nextafterf(1, 2));
+ TEST_FORMAT(double, DBL_MAX, DBL_TRUE_MIN, nextafter(1, 2));
+ TEST_FORMAT(long double, LDBL_MAX, LDBL_TRUE_MIN, nextafterl(1, 2));
+}
+
+/*
+ * A quiet NaN in either position, or both, is a domain error: the result
+ * is 0 and "invalid" is raised, and nothing else is.
+ */
+ATF_TC_WITHOUT_HEAD(quiet_nan);
+ATF_TC_BODY(quiet_nan, tc)
+{
+ TEST_ALL(NAN, 0.0, 0, FE_INVALID);
+ TEST_ALL(NAN, -0.0, 0, FE_INVALID);
+ TEST_ALL(NAN, 1.0, 0, FE_INVALID);
+ TEST_ALL(NAN, INFINITY, 0, FE_INVALID);
+ TEST_ALL(NAN, -INFINITY, 0, FE_INVALID);
+ TEST_ALL(NAN, NAN, 0, FE_INVALID);
+ TEST_ALL(-NAN, 1.0, 0, FE_INVALID);
+ TEST_ALL(-NAN, NAN, 0, FE_INVALID);
+}
+
+ATF_TC_WITHOUT_HEAD(signaling_nan);
+ATF_TC_BODY(signaling_nan, tc)
+{
+ TEST(float, __builtin_nansf(""), float, 1.0f, 0, FE_INVALID);
+ TEST(float, 1.0f, float, __builtin_nansf(""), 0, FE_INVALID);
+ TEST(float, __builtin_nansf(""), float, __builtin_nansf(""), 0,
+ FE_INVALID);
+ TEST(double, __builtin_nans(""), double, 1.0, 0, FE_INVALID);
+ TEST(double, 1.0, double, __builtin_nans(""), 0, FE_INVALID);
+ TEST(double, __builtin_nans(""), double, __builtin_nans(""), 0,
+ FE_INVALID);
+ TEST(long double, __builtin_nansl(""), long double, 1.0L, 0,
+ FE_INVALID);
+ TEST(long double, 1.0L, long double, __builtin_nansl(""), 0,
+ FE_INVALID);
+ TEST(long double, __builtin_nansl(""), long double,
+ __builtin_nansl(""), 0, FE_INVALID);
+}
+
+/*
+ * Arguments of different types are compared as == would compare them,
+ * after the usual arithmetic conversions.
+ */
+ATF_TC_WITHOUT_HEAD(mixed_types);
+ATF_TC_BODY(mixed_types, tc)
+{
+ TEST(float, 0.5f, double, 0.5, 1, 0);
+ TEST(double, 0.5, float, 0.5f, 1, 0);
+ TEST(float, 0.5f, long double, 0.5L, 1, 0);
+ TEST(double, 0.5, long double, 0.5L, 1, 0);
+ TEST(long double, 0.5L, double, 0.5, 1, 0);
+
+ /* 0.1 is not representable; its roundings differ per format. */
+ TEST(float, 0.1f, double, 0.1, 0, 0);
+ TEST(double, 0.1, float, 0.1f, 0, 0);
+ TEST(float, 0.1f, long double, 0.1L, 0, 0);
+ TEST(double, 0.1, long double, 0.1L,
+ LDBL_MANT_DIG == DBL_MANT_DIG, 0);
+ TEST(long double, 0.1L, double, 0.1,
+ LDBL_MANT_DIG == DBL_MANT_DIG, 0);
+
+ /* The wider argument must not be narrowed to the narrower type. */
+ TEST(float, 1.0f, double, nextafter(1, 2), 0, 0);
+ TEST(double, 1.0, long double, nextafterl(1, 2), 0, 0);
+
+ TEST(float, NAN, double, 1.0, 0, FE_INVALID);
+ TEST(double, 1.0, long double, NAN, 0, FE_INVALID);
+ TEST(float, 1.0f, long double, NAN, 0, FE_INVALID);
+}
+
+/* iseqsig() is a macro; each argument must be evaluated exactly once. */
+ATF_TC_WITHOUT_HEAD(single_evaluation);
+ATF_TC_BODY(single_evaluation, tc)
+{
+ double a[] = { 1.0, 1.0, 2.0 };
+ float b[] = { 1.0f, 2.0f };
+ int i, j, r;
+
+ i = j = 0;
+ r = iseqsig(a[i++], a[++j]);
+ ATF_CHECK_EQ(1, r);
+ ATF_CHECK_EQ(1, i);
+ ATF_CHECK_EQ(1, j);
+
+ i = j = 0;
+ r = iseqsig(b[i++], a[j += 2]);
+ ATF_CHECK_EQ(0, r);
+ ATF_CHECK_EQ(1, i);
+ ATF_CHECK_EQ(2, j);
+}
+
+ATF_TP_ADD_TCS(tp)
+{
+ ATF_TP_ADD_TC(tp, equal);
+ ATF_TP_ADD_TC(tp, unequal);
+ ATF_TP_ADD_TC(tp, format_edges);
+ ATF_TP_ADD_TC(tp, quiet_nan);
+ ATF_TP_ADD_TC(tp, signaling_nan);
+ ATF_TP_ADD_TC(tp, mixed_types);
+ ATF_TP_ADD_TC(tp, single_evaluation);
+
+ return (atf_no_error());
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Oct 13, 8:58 PM (15 h, 26 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
40755556
Default Alt Text
D60642.id.diff (13 KB)
Attached To
Mode
D60642: libm: Implement iseqsig()
Attached
Detach File
Event Timeline
Log In to Comment