diff --git a/docs/reference/glib/glib-sections.txt b/docs/reference/glib/glib-sections.txt index 32a7664e8..0183b0898 100644 --- a/docs/reference/glib/glib-sections.txt +++ b/docs/reference/glib/glib-sections.txt @@ -219,6 +219,7 @@ MAX ABS CLAMP +G_APPROX_VALUE G_STRUCT_MEMBER @@ -3146,6 +3147,7 @@ g_assert_cmpint g_assert_cmpuint g_assert_cmphex g_assert_cmpfloat +g_assert_cmpfloat_with_epsilon g_assert_cmpmem g_assert_no_error g_assert_error diff --git a/glib/docs.c b/glib/docs.c index 080c1b0de..5a786311c 100644 --- a/glib/docs.c +++ b/glib/docs.c @@ -1788,6 +1788,26 @@ * Returns: the value of @x clamped to the range between @low and @high */ +/** + * G_APPROX_VALUE: + * @a: a numeric value + * @b: a numeric value + * @epsilon: a numeric value that expresses the tolerance between @a and @b + * + * Evaluates to a truth value if the absolute difference between @a and @b is + * smaller than @epsilon, and to a false value otherwise. + * + * For example, + * - `G_APPROX_VALUE (5, 6, 2)` evaluates to true + * - `G_APPROX_VALUE (3.14, 3.15, 0.001)` evaluates to false + * - `G_APPROX_VALUE (n, 0.f, FLT_EPSILON)` evaluates to true if `n` is within + * the single precision floating point epsilon from zero + * + * Returns: %TRUE if the two values are within the desired range + * + * Since: 2.58 + */ + /** * G_STRUCT_MEMBER: * @member_type: the type of the struct field diff --git a/glib/gmacros.h b/glib/gmacros.h index 0e180bb09..cfeb9a00b 100644 --- a/glib/gmacros.h +++ b/glib/gmacros.h @@ -329,6 +329,9 @@ #undef CLAMP #define CLAMP(x, low, high) (((x) > (high)) ? (high) : (((x) < (low)) ? (low) : (x))) +#define G_APPROX_VALUE(a, b, epsilon) \ + (((a) > (b) ? (a) - (b) : (b) - (a)) < (epsilon)) + /* Count the number of elements in an array. The array must be defined * as such; using this with a dynamically allocated array will give * incorrect results. diff --git a/glib/gtestutils.c b/glib/gtestutils.c index c866fc8c6..93f2ba095 100644 --- a/glib/gtestutils.c +++ b/glib/gtestutils.c @@ -89,10 +89,11 @@ * * In addition to the traditional g_assert(), the test framework provides * an extended set of assertions for comparisons: g_assert_cmpfloat(), - * g_assert_cmpint(), g_assert_cmpuint(), g_assert_cmphex(), - * g_assert_cmpstr(), and g_assert_cmpmem(). The advantage of these - * variants over plain g_assert() is that the assertion messages can be - * more elaborate, and include the values of the compared entities. + * g_assert_cmpfloat_with_epsilon(), g_assert_cmpint(), g_assert_cmpuint(), + * g_assert_cmphex(), g_assert_cmpstr(), and g_assert_cmpmem(). The + * advantage of these variants over plain g_assert() is that the assertion + * messages can be more elaborate, and include the values of the compared + * entities. * * A full example of creating a test suite with two tests using fixtures: * |[ @@ -636,6 +637,23 @@ * Since: 2.16 */ +/** + * g_assert_cmpfloat_with_epsilon: + * @n1: an floating point number + * @n2: another floating point number + * @epsilon: a numeric value that expresses the expected tolerance + * between @n1 and @n2 + * + * Debugging macro to compare two floating point numbers within an epsilon. + * + * The effect of `g_assert_cmpfloat_with_epsilon (n1, n2, epsilon)` is + * the same as `g_assert_true (abs (n1 - n2) < epsilon)`. The advantage + * of this macro is that it can produce a message that includes the + * actual values of @n1 and @n2. + * + * Since: 2.58 + */ + /** * g_assert_cmpmem: * @m1: pointer to a buffer diff --git a/glib/gtestutils.h b/glib/gtestutils.h index 1d05b97f4..02ab397b3 100644 --- a/glib/gtestutils.h +++ b/glib/gtestutils.h @@ -69,6 +69,13 @@ typedef void (*GTestFixtureFunc) (gpointer fixture, g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ #n1 " " #cmp " " #n2, (long double) __n1, #cmp, (long double) __n2, 'f'); \ } G_STMT_END +#define g_assert_cmpfloat_with_epsilon(n1,n2,epsilon) \ + G_STMT_START { \ + double __n1 = (n1), __n2 = (n2), __epsilon = (epsilon); \ + if (G_APPROX_VALUE (__n1, __n2, __epsilon)) ; else \ + g_assertion_message_cmpnum (G_LOG_DOMAIN, __FILE__, __LINE__, G_STRFUNC, \ + #n1 " == " #n2 " (+/- " #epsilon ")", __n1, "==", __n2, 'f'); \ + } G_STMT_END #define g_assert_cmpmem(m1, l1, m2, l2) G_STMT_START {\ gconstpointer __m1 = m1, __m2 = m2; \ int __l1 = l1, __l2 = l2; \ diff --git a/glib/tests/testing.c b/glib/tests/testing.c index 391350614..de95f2635 100644 --- a/glib/tests/testing.c +++ b/glib/tests/testing.c @@ -60,6 +60,13 @@ test_assertions_bad_cmpmem_data (void) exit (0); } +static void +test_assertions_bad_cmpfloat_epsilon (void) +{ + g_assert_cmpfloat_with_epsilon (3.14, 3.15, 0.001); + exit (0); +} + static void test_assertions (void) { @@ -68,6 +75,8 @@ test_assertions (void) g_assert_cmphex (2, ==, 2); g_assert_cmpfloat (3.3, !=, 7); g_assert_cmpfloat (7, <=, 3 + 4); + g_assert_cmpfloat_with_epsilon (3.14, 3.15, 0.01); + g_assert_cmpfloat_with_epsilon (3.14159, 3.1416, 0.0001); g_assert (TRUE); g_assert_cmpstr ("foo", !=, "faa"); fuu = g_strdup_printf ("f%s", "uu"); @@ -98,6 +107,10 @@ test_assertions (void) g_test_trap_assert_failed (); g_test_trap_assert_stderr ("*assertion failed*"); g_test_trap_assert_stderr_unmatched ("*assertion failed*len*"); + + g_test_trap_subprocess ("/misc/assertions/subprocess/bad_cmpfloat_epsilon", 0, 0); + g_test_trap_assert_failed (); + g_test_trap_assert_stderr ("*assertion failed*"); } /* test g_test_timer* API */ @@ -740,6 +753,7 @@ main (int argc, g_test_add_func ("/misc/assertions/subprocess/bad_cmpint", test_assertions_bad_cmpint); g_test_add_func ("/misc/assertions/subprocess/bad_cmpmem_len", test_assertions_bad_cmpmem_len); g_test_add_func ("/misc/assertions/subprocess/bad_cmpmem_data", test_assertions_bad_cmpmem_data); + g_test_add_func ("/misc/assertions/subprocess/bad_cmpfloat_epsilon", test_assertions_bad_cmpfloat_epsilon); g_test_add_data_func ("/misc/test-data", (void*) 0xc0c0baba, test_data_test); g_test_add ("/misc/primetoul", Fixturetest, (void*) 0xc0cac01a, fixturetest_setup, fixturetest_test, fixturetest_teardown); if (g_test_perf())