Skip to content

Commit e9877ad

Browse files
committed
New YT_EQ_DOUBLE_REL macro for relative compares
Updated examples and readme
1 parent c474fa8 commit e9877ad

File tree

8 files changed

+92
-53
lines changed

8 files changed

+92
-53
lines changed

Readme.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,20 +81,26 @@ See [Parameterised test](./example/add_parameterised_test.c) example
8181
Assertions macros check state expectations from an SUT (System Under Test). These are several of
8282
these macros.
8383

84-
| Macro name | Validates |
85-
|--------------------------|-----------------------------------------------------|
86-
| `YT_EQ_SCALAR(a, b)` | a == b |
87-
| `YT_NEQ_SCALAR(a, b)` | a != b |
88-
| `YT_GEQ_SCALAR(a, b)` | a >= b |
89-
| `YT_LEQ_SCALAR(a, b)` | a <= b |
90-
| `YT_GRT_SCALAR(a, b)` | a > b |
91-
| `YT_LES_SCALAR(a, b)` | a < b |
92-
| `YT_EQ_MEM(a, b, sz)` | First `sz` bytes in buffers `a` & `b` are equal |
93-
| `YT_NEQ_MEM(a, b, sz)` | First `sz` bytes in buffers `a` & `b` are not equal |
94-
| `YT_EQ_STRING(a, b)` | String `a` and `b` are equal |
95-
| `YT_NEQ_STRING(a, b)` | String `a` and `b` are not equal |
96-
| `YT_EQ_DOUBLE(a, b, e)` | Approx match a == b. Passes if `mod(a - b) <= e` |
97-
| `YT_NEQ_DOUBLE(a, b, e)` | Approx match a != b. Passes if `mod(a - b) > e` |
84+
| Macro name | Validates |
85+
|------------------------------|-----------------------------------------------------------|
86+
| `YT_EQ_SCALAR(a, b)` | a == b |
87+
| `YT_NEQ_SCALAR(a, b)` | a != b |
88+
| `YT_GEQ_SCALAR(a, b)` | a >= b |
89+
| `YT_LEQ_SCALAR(a, b)` | a <= b |
90+
| `YT_GRT_SCALAR(a, b)` | a > b |
91+
| `YT_LES_SCALAR(a, b)` | a < b |
92+
| `YT_EQ_MEM(a, b, sz)` | First `sz` bytes in buffers `a` & `b` are equal |
93+
| `YT_NEQ_MEM(a, b, sz)` | First `sz` bytes in buffers `a` & `b` are not equal |
94+
| `YT_EQ_STRING(a, b)` | String `a` and `b` are equal |
95+
| `YT_NEQ_STRING(a, b)` | String `a` and `b` are not equal |
96+
| `YT_EQ_DOUBLE_ABS(a, b, e)` | Approx match a == b. Passes if `mod(a - b) <= e` |
97+
| `YT_NEQ_DOUBLE_ABS(a, b, e)` | Approx match a != b. Passes if `mod(a - b) > e` |
98+
| `YT_EQ_DOUBLE_REL(a, b, e)` | Approx match a == b. Passes if `mod(a - b) <= max(a,b)*e` |
99+
| `YT_NEQ_DOUBLE_REL(a, b, e)` | Approx match a != b. Passes if `mod(a - b) > max(a,b)*e` |
100+
101+
`YT_EQ_DOUBLE_REL(a, b, e)` passes if the difference of a & b is less or equal to e% of the largest
102+
of the two floating point numbers. For example, `YT_EQ_DOUBLE_REL(1.1234, 1.12, 0.01)` passes
103+
because their difference of 0.0034 is < 1% of 1.1234.
98104

99105
See [Basic tests](./example/basic_tests.c) example
100106

example/basic_tests.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
YT_TEST (basic, scaler_tests_floats)
77
{
8-
YT_NEQ_DOUBLE (1.0, 2.0, 0.01); // Passes because 1.0 != 2.0
9-
YT_NEQ_DOUBLE (1.1234, 1.12, 0.001); // Passes because 1.1234 - 1.12 > 0.001
8+
YT_NEQ_DOUBLE_ABS (1.0, 2.0, 0.01); // Passes because 1.0 != 2.0
9+
YT_NEQ_DOUBLE_ABS (1.1234, 1.12, 0.001); // Passes because 1.1234 - 1.12 > 0.001
1010

11-
YT_EQ_DOUBLE (1.1234, 1.12, 0.01); // Passes because 1.1234 - 1.12 <= 0.01
12-
YT_EQ_DOUBLE (0.0, 0.0, 0.001); // Passes because 0.0 == 0.0
11+
YT_EQ_DOUBLE_ABS (1.1234, 1.12, 0.01); // Passes because 1.1234 - 1.12 <= 0.01
12+
YT_EQ_DOUBLE_ABS (0.0, 0.0, 0.001); // Passes because 0.0 == 0.0
1313

1414
YT_END();
1515
}

example/sensor_test.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ YT_TESTP (sensor, read_temperature_test, uint16_t, double)
3333
// readADC() called by read_temperature() SUT function should return the adc value we expect.
3434
readADC_fake.ret = adc_value;
3535

36-
YT_EQ_DOUBLE (read_temperature(), temperature_exp, 0.00999);
36+
//YT_EQ_DOUBLE_ABS (read_temperature(), temperature_exp, 0.00999);
37+
YT_EQ_DOUBLE_REL (read_temperature(), temperature_exp, 0.001);
3738
YT_END();
3839
}
3940

tests/basic_failures.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@
33

44
YT_TEST (basic, scaler_tests_floats)
55
{
6-
YT_NEQ_DOUBLE (1.0, 1.0, 0.01); // Fails because 1.0 == 1.0
7-
YT_NEQ_DOUBLE (1.1234, 1.12, 0.01); // Fails because 1.1234 - 1.12 <= 0.01. Meaning the two are
8-
// treated as equal.
6+
YT_NEQ_DOUBLE_ABS (1.0, 1.0, 0.01); // Fails because 1.0 == 1.0
7+
YT_NEQ_DOUBLE_ABS (1.1234, 1.12, 0.01); // Fails because 1.1234 - 1.12 <= 0.01. Meaning the two
8+
// are treated as equal.
99

10-
YT_EQ_DOUBLE (1.1234, 1.12, 0.001); // Fails because 1.1234 - 1.12 > 0.001
11-
YT_EQ_DOUBLE (0.0, 1.0, 0.001); // Fails because 0.0 != 1.0
10+
YT_EQ_DOUBLE_ABS (1.1234, 1.12, 0.001); // Fails because 1.1234 - 1.12 > 0.001
11+
YT_EQ_DOUBLE_ABS (0.0, 1.0, 0.001); // Fails because 0.0 != 1.0
1212

13+
YT_EQ_DOUBLE_REL (1.1234, 1.12, 0.001); // Fails as 1.1234 - 1.12 > 0.01 % of 1.1234
1314
YT_END();
1415
}
1516

tests/basic_passing.c

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
YT_TEST (basic, scaler_tests_floats)
55
{
6-
YT_NEQ_DOUBLE (1.0, 2.0, 0.01); // Passed because 1.0 != 2.0
7-
YT_NEQ_DOUBLE (1.1234, 1.12, 0.001); // Passed because 1.1234 - 1.12 > 0.001
6+
YT_NEQ_DOUBLE_ABS (1.0, 2.0, 0.01); // Passed because 1.0 != 2.0
7+
YT_NEQ_DOUBLE_ABS (1.1234, 1.12, 0.001); // Passed because 1.1234 - 1.12 > 0.001
88

9-
YT_EQ_DOUBLE (1.1234, 1.12, 0.01); // Passed because 1.1234 - 1.12 <= 0.01
10-
YT_EQ_DOUBLE (0.0, 0.0, 0.001); // Passed because 1.0 != 2.0
9+
YT_EQ_DOUBLE_ABS (1.1234, 1.12, 0.01); // Passed because 1.1234 - 1.12 <= 0.01
10+
YT_EQ_DOUBLE_ABS (0.0, 0.0, 0.001); // Passed because 1.0 != 2.0
11+
12+
YT_EQ_DOUBLE_REL (1.1234, 1.12, 0.01); // Passed as 1.1234 - 1.12 <= 1% of 1.1234
1113

1214
YT_END();
1315
}

tests/exps/basic_failures.exp

70 Bytes
Binary file not shown.

tests/exps/basic_passing.exp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
  basic:scaler_tests_ints  OK [0 of 8 failed]
2-
  basic:scaler_tests_floats  OK [0 of 4 failed]
2+
  basic:scaler_tests_floats  OK [0 of 5 failed]
33
  basic:string_tests  OK [0 of 3 failed]
44
  basic:memory_tests  OK [0 of 2 failed]
55

yukti.h

Lines changed: 53 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -250,11 +250,7 @@ typedef struct YT__Arg {
250250
{ \
251251
.isOpt = false, .val = (uintptr_t)(v) \
252252
}
253-
#define _ \
254-
(YT__Arg) \
255-
{ \
256-
.isOpt = true, .val = 0 \
257-
}
253+
#define _ (YT__Arg){ .isOpt = true, .val = 0 }
258254

259255
#define YT__RECORD_CALL(n, f, ...) \
260256
do { \
@@ -848,25 +844,56 @@ static int YT__equal_string (const char* a, const char* b, int* i);
848844
#define AUTOTYPE __auto_type
849845
#endif /* __cplusplus */
850846

851-
static bool yt__approxeq (double a, double b, double epsilon)
847+
/* Performs either relative or absolute comparison on floating point numbers.
848+
* Relative comparison: Checks if the difference is smaller than some percentage than the of the two
849+
* largest number.
850+
* Absolute comparison: Checks if the difference is smaller than an absolute number. The problem is
851+
* the floating point accuracy changes depending on how big or small the number is, so the epsilon
852+
* that works for smaller numbers will not work for larger ones.
853+
*/
854+
static bool yt__approxeq (bool is_abs, double a, double b, double epsilon)
852855
{
853-
double ut_a = fabs (a);
854-
double ut_b = fabs (b);
856+
double ut_a = fabs (a);
857+
double ut_b = fabs (b);
858+
double ut_diff = fabs (a - b);
859+
860+
// NanS are not numbers, so any compare with numbers must fail
861+
if (isnan (a) || isnan (b))
862+
return false;
855863

856-
if ((isfinite (a) && isfinite (b)) || (ut_a == 0.0 && ut_b == 0.0) || (isnan (a) && isnan (b)))
864+
// Zero comparison does work well for relative checks, so this takes care of that, also handles
865+
// the trivial case.
866+
if ((a == b) || (ut_a == 0.0 && ut_b == 0))
857867
return true;
858-
if ((isfinite (a) && !isfinite (b)) || (!isfinite (a) && isfinite (b)))
868+
869+
// Infinities are treated as valid, but no comparison is done, instead test matches if both are
870+
// same, otherwise fails if only one is infinity and the other is not
871+
if (isinf (ut_a) && isinf (ut_b))
872+
return true;
873+
874+
if ((!isinf (ut_a) && isinf (ut_b)) || (isinf (ut_a) && !isinf (ut_b)))
859875
return false;
860876

861-
// Source: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
862-
double ut_diff = fabs (a - b);
863-
double ut_largest = (ut_a > ut_b) ? ut_a : ut_b;
864-
return (ut_diff <= ut_largest * epsilon);
877+
if (is_abs) {
878+
return (ut_diff <= epsilon);
879+
} else {
880+
double ut_largest = (ut_a > ut_b) ? ut_a : ut_b;
881+
return (ut_diff <= ut_largest * epsilon);
882+
}
865883
}
866884

867-
#define YT__TEST_DOUBLE(e, a, o, b) \
885+
#define YT__TEST_DOUBLE_REL(e, a, o, b) \
886+
do { \
887+
YT__current_testrecord->total_exp_count++; \
888+
if (!(yt__approxeq (false, a, b, e) o true)) { \
889+
YT__FAILED (a o b, "[%f !" #o " %f]", a, b); \
890+
} \
891+
} while (0)
892+
893+
#define YT__TEST_DOUBLE_ABS(e, a, o, b) \
868894
do { \
869-
if (!(yt__approxeq (a, b, e) o true)) { \
895+
YT__current_testrecord->total_exp_count++; \
896+
if (!(yt__approxeq (true, a, b, e) o true)) { \
870897
YT__FAILED (a o b, "[%f !" #o " %f]", a, b); \
871898
} \
872899
} while (0)
@@ -900,14 +927,16 @@ static bool yt__approxeq (double a, double b, double epsilon)
900927
YT__FAILED (a o b, "[Idx: %d, '%c' !" #o " '%c']", i, ut_a[i], ut_b[i]); \
901928
} while (0)
902929

903-
#define YT_EQ_DOUBLE(a, b, e) YT__TEST_DOUBLE (e, a, ==, b)
904-
#define YT_NEQ_DOUBLE(a, b, e) YT__TEST_DOUBLE (e, a, !=, b)
905-
#define YT_EQ_SCALAR(a, b) YT__TEST_SCALAR (a, ==, b)
906-
#define YT_NEQ_SCALAR(a, b) YT__TEST_SCALAR (a, !=, b)
907-
#define YT_GEQ_SCALAR(a, b) YT__TEST_SCALAR (a, >=, b)
908-
#define YT_LEQ_SCALAR(a, b) YT__TEST_SCALAR (a, <=, b)
909-
#define YT_LES_SCALAR(a, b) YT__TEST_SCALAR (a, <, b)
910-
#define YT_GRT_SCALAR(a, b) YT__TEST_SCALAR (a, >, b)
930+
#define YT_EQ_DOUBLE_REL(a, b, e) YT__TEST_DOUBLE_REL (e, a, ==, b)
931+
#define YT_NEQ_DOUBLE_REL(a, b, e) YT__TEST_DOUBLE_REL (e, a, !=, b)
932+
#define YT_EQ_DOUBLE_ABS(a, b, e) YT__TEST_DOUBLE_ABS (e, a, ==, b)
933+
#define YT_NEQ_DOUBLE_ABS(a, b, e) YT__TEST_DOUBLE_ABS (e, a, !=, b)
934+
#define YT_EQ_SCALAR(a, b) YT__TEST_SCALAR (a, ==, b)
935+
#define YT_NEQ_SCALAR(a, b) YT__TEST_SCALAR (a, !=, b)
936+
#define YT_GEQ_SCALAR(a, b) YT__TEST_SCALAR (a, >=, b)
937+
#define YT_LEQ_SCALAR(a, b) YT__TEST_SCALAR (a, <=, b)
938+
#define YT_LES_SCALAR(a, b) YT__TEST_SCALAR (a, <, b)
939+
#define YT_GRT_SCALAR(a, b) YT__TEST_SCALAR (a, >, b)
911940

912941
#define YT_EQ_MEM(a, b, sz) YT__TEST_MEM (a, ==, b, sz)
913942
#define YT_NEQ_MEM(a, b, sz) YT__TEST_MEM (a, !=, b, sz)

0 commit comments

Comments
 (0)