From: Yu Watanabe Date: Mon, 18 Jul 2022 19:28:57 +0000 (+0900) Subject: math-util: introduce iszero_safe() and fp_equal() X-Git-Tag: v252-rc1~609^2~2 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=fbccfa95c4c0f5ff5bb3a1616f810036996ce3d5;p=thirdparty%2Fsystemd.git math-util: introduce iszero_safe() and fp_equal() --- diff --git a/src/basic/math-util.h b/src/basic/math-util.h new file mode 100644 index 00000000000..125e0c62092 --- /dev/null +++ b/src/basic/math-util.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include + +#include "macro.h" + +/* On some optimization level, iszero(x) is converted to (x == 0.0), and emits warning -Wfloat-equal. + * The argument must be a floating point, i.e. one of float, double, or long double. */ +#define iszero_safe(x) (fpclassify(x) == FP_ZERO) + +/* To avoid x == y and triggering compile warning -Wfloat-equal. This retuns 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)) diff --git a/src/basic/meson.build b/src/basic/meson.build index cc4d948abd1..84576efd897 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -129,6 +129,7 @@ basic_sources = files( 'login-util.c', 'login-util.h', 'macro.h', + 'math-util.h', 'memfd-util.c', 'memfd-util.h', 'memory-util.c', diff --git a/src/test/meson.build b/src/test/meson.build index cc590f4f3d9..cab9c9aea28 100644 --- a/src/test/meson.build +++ b/src/test/meson.build @@ -209,6 +209,10 @@ tests += [ [files('test-macro.c')], + [files('test-math-util.c'), + [], + [libm]], + [files('test-mkdir.c')], [files('test-json.c'), diff --git a/src/test/test-math-util.c b/src/test/test-math-util.c new file mode 100644 index 00000000000..891bbfc84a5 --- /dev/null +++ b/src/test/test-math-util.c @@ -0,0 +1,111 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "math-util.h" +#include "tests.h" + +TEST(iszero_safe) { + /* zeros */ + assert_se(iszero_safe(0.0)); + assert_se(iszero_safe(-0.0)); + assert_se(iszero_safe(0e0)); + assert_se(iszero_safe(-0e0)); + assert_se(iszero_safe(0e+0)); + assert_se(iszero_safe(0e-0)); + assert_se(iszero_safe(-0e-0)); + assert_se(iszero_safe(-0e000)); + assert_se(iszero_safe(0e000)); + + /* non-zero normal values */ + assert_se(!iszero_safe(42.0)); + assert_se(!iszero_safe(M_PI)); + assert_se(!iszero_safe(DBL_MAX)); + assert_se(!iszero_safe(-DBL_MAX)); + assert_se(!iszero_safe(DBL_MIN)); + assert_se(!iszero_safe(-DBL_MIN)); + assert_se(!iszero_safe(1 / DBL_MAX)); + + /* subnormal values */ + assert_se(!iszero_safe(DBL_MIN / 2)); + assert_se(!iszero_safe(-DBL_MIN / 42)); + assert_se(!iszero_safe(1 / DBL_MAX / 2)); + + /* too small values which cannot be in subnormal form */ + assert_se( iszero_safe(DBL_MIN / DBL_MAX)); + assert_se( iszero_safe(DBL_MIN / -DBL_MAX)); + assert_se( iszero_safe(-DBL_MIN / DBL_MAX)); + assert_se( iszero_safe(-DBL_MIN / -DBL_MAX)); + + /* NaN or infinity */ + assert_se(!iszero_safe(NAN)); + assert_se(!iszero_safe(INFINITY)); + assert_se(!iszero_safe(-INFINITY)); + assert_se(!iszero_safe(1 / NAN)); + + /* inverse of infinity */ + assert_se( iszero_safe(1 / INFINITY)); + assert_se( iszero_safe(1 / -INFINITY)); + assert_se( iszero_safe(-1 / INFINITY)); + assert_se( iszero_safe(-1 / -INFINITY)); + assert_se( iszero_safe(42 / -INFINITY)); + assert_se( iszero_safe(-42 / -INFINITY)); + assert_se( iszero_safe(DBL_MIN / INFINITY)); + assert_se( iszero_safe(DBL_MIN / -INFINITY)); + assert_se( iszero_safe(DBL_MAX / INFINITY / 2)); + assert_se( iszero_safe(DBL_MAX / -INFINITY * DBL_MAX)); + assert_se(!iszero_safe(DBL_MAX * 2 / INFINITY)); + + /* infinity / infinity is NaN */ + assert_se(!iszero_safe(INFINITY / INFINITY)); + assert_se(!iszero_safe(INFINITY * 2 / INFINITY)); + assert_se(!iszero_safe(INFINITY / DBL_MAX / INFINITY)); +} + +TEST(fp_equal) { + /* normal values */ + assert_se( fp_equal(0.0, -0e0)); + assert_se( fp_equal(3.0, 3)); + assert_se(!fp_equal(3.000001, 3)); + assert_se( fp_equal(M_PI, M_PI)); + assert_se(!fp_equal(M_PI, -M_PI)); + assert_se( fp_equal(DBL_MAX, DBL_MAX)); + assert_se(!fp_equal(DBL_MAX, -DBL_MAX)); + assert_se(!fp_equal(-DBL_MAX, DBL_MAX)); + assert_se( fp_equal(-DBL_MAX, -DBL_MAX)); + assert_se( fp_equal(DBL_MIN, DBL_MIN)); + assert_se(!fp_equal(DBL_MIN, -DBL_MIN)); + assert_se(!fp_equal(-DBL_MIN, DBL_MIN)); + assert_se( fp_equal(-DBL_MIN, -DBL_MIN)); + + /* subnormal values */ + assert_se( fp_equal(DBL_MIN / 10, DBL_MIN / 10)); + assert_se(!fp_equal(DBL_MIN / 10, -DBL_MIN / 10)); + assert_se(!fp_equal(-DBL_MIN / 10, DBL_MIN / 10)); + assert_se( fp_equal(-DBL_MIN / 10, -DBL_MIN / 10)); + assert_se(!fp_equal(DBL_MIN / 10, DBL_MIN / 15)); + assert_se(!fp_equal(DBL_MIN / 10, DBL_MIN / 15)); + + /* subnormal difference */ + assert_se(!fp_equal(DBL_MIN / 10, DBL_MIN + DBL_MIN / 10)); + assert_se( fp_equal(3.0, 3.0 + DBL_MIN / 2)); /* 3.0 + DBL_MIN / 2 is truncated to 3.0 */ + + /* too small values */ + assert_se( fp_equal(DBL_MIN / DBL_MAX, -DBL_MIN / DBL_MAX)); + + /* NaN or infinity */ + assert_se(!fp_equal(NAN, NAN)); + assert_se(!fp_equal(NAN, 0)); + assert_se(!fp_equal(NAN, INFINITY)); + assert_se(!fp_equal(INFINITY, INFINITY)); + assert_se(!fp_equal(INFINITY, -INFINITY)); + assert_se(!fp_equal(-INFINITY, INFINITY)); + assert_se(!fp_equal(-INFINITY, -INFINITY)); + + /* inverse of infinity */ + assert_se( fp_equal(0, 1 / INFINITY)); + assert_se( fp_equal(42 / INFINITY, 1 / -INFINITY)); + assert_se(!fp_equal(42 / INFINITY, INFINITY / INFINITY)); +} + +DEFINE_TEST_MAIN(LOG_DEBUG);