'-Wno-string-plus-int', # clang
'-fdiagnostics-show-option',
+ '-fexcess-precision=standard',
'-fno-common',
'-fstack-protector',
'-fstack-protector-strong',
#define iszero_safe(x) (fpclassify(x) == FP_ZERO)
/* To avoid x == y and triggering compile warning -Wfloat-equal. This returns false if one of the argument is
- * NaN or infinity. One of the argument must be a floating point. */
-#define fp_equal(x, y) iszero_safe((x) - (y))
+ * NaN or infinity. One of the argument must be a floating point.
+ *
+ * The volatile temporaries force a memory roundtrip, truncating any excess precision (e.g. x87's
+ * 80-bit register width for double arithmetic) down to the declared type. -fexcess-precision=standard
+ * doesn't fully cover this on x87 — a function return value carried in ST(0) can still arrive at the
+ * caller in 80-bit precision (see gcc PR#323), so a value that should compare equal to a
+ * same-magnitude literal picks up extra mantissa bits and doesn't. The memory store-and-reload is
+ * the one operation guaranteed to truncate. The temporaries inherit the type of the subtraction
+ * expression so the macro stays generic over float / double / long double rather than silently
+ * truncating wider arguments. */
+#define fp_equal(x, y) \
+ ({ \
+ volatile __typeof__((x) - (y)) _fp_x = (x); \
+ volatile __typeof__((x) - (y)) _fp_y = (y); \
+ iszero_safe(_fp_x - _fp_y); \
+ })
/* 10^n. Exact for |n| ≤ 22; otherwise multiplies and may accumulate rounding error. Saturates to
* 0.0 or +Inf outside binary64's exponent range; large |n| is capped internally so untrusted
#include "math-util.h"
#include "tests.h"
+/* Computed at runtime via a noinline + volatile combination so the result crosses the function ABI
+ * boundary at the FPU's current precision (80-bit on i386/x87). Used to probe fp_equal's handling
+ * of excess precision — see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 for why
+ * -fexcess-precision=standard doesn't fully cover the caller side of a function return on x87. */
+static double _noinline_ one_tenth_via_division(void) {
+ volatile double ten = 10.0;
+ return 1.0 / ten;
+}
+
TEST(iszero_safe) {
/* zeros */
assert_se(iszero_safe(0.0));
assert_se( fp_equal(0, 1 / INFINITY));
assert_se( fp_equal(42 / INFINITY, 1 / -INFINITY));
assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY));
+
+ assert_se( fp_equal(one_tenth_via_division(), 0.1));
}
TEST(xexp10i) {