comparison q/t.c @ 14:388074ff9474

Add fixed point code
author Daniel O'Connor <darius@dons.net.au>
date Tue, 25 Feb 2025 13:28:29 +1030
parents
children
comparison
equal deleted inserted replaced
13:032acb7fbc04 14:388074ff9474
1 /**@file t.c
2 * @brief Q-Number (Q16.16, signed) library test bench
3 * @copyright Richard James Howe (2018)
4 * @license MIT
5 * @email howe.r.j.89@gmail.com
6 * @site <https://github.com/howerj/q>
7 *
8 * This file contains a wrapper for testing the Q Number library,
9 * it includes a command processor and some built in tests. View
10 * the help string later on for more information */
11 #include "q.h"
12 #include <assert.h>
13 #include <ctype.h>
14 #include <errno.h>
15 #include <stdbool.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19
20 #define N (16)
21 #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
22
23 static int qprint(FILE *out, const q_t p) {
24 assert(out);
25 char buf[64 + 1] = { 0, };
26 const int r = qsprint(p, buf, sizeof buf);
27 return r < 0 ? r : fprintf(out, "%s", buf);
28 }
29
30 static int printq(FILE *out, q_t q, const char *msg) {
31 assert(out);
32 assert(msg);
33 if (fprintf(out, "%s = ", msg) < 0)
34 return -1;
35 if (qprint(out, q) < 0)
36 return -1;
37 if (fputc('\n', out) != '\n')
38 return -1;
39 return 0;
40 }
41
42 static int print_sincos(FILE *out, const q_t theta) {
43 assert(out);
44 q_t sine = qinfo.zero, cosine = qinfo.zero;
45 qsincos(theta, &sine, &cosine);
46 if (qprint(out, theta) < 0)
47 return -1;
48 if (fputc(',', out) != ',')
49 return -1;
50 if (qprint(out, sine) < 0)
51 return -1;
52 if (fputc(',', out) != ',')
53 return -1;
54 if (qprint(out, cosine) < 0)
55 return -1;
56 if (fputc('\n', out) != '\n')
57 return -1;
58 return 0;
59 }
60
61 static int print_sincos_table(FILE *out) {
62 assert(out);
63 const q_t tpi = qdiv(qinfo.pi, qint(20));
64 const q_t end = qmul(qinfo.pi, qint(2));
65 const q_t start = qnegate(end);
66 if (fprintf(out, "theta,sine,cosine\n") < 0)
67 return -1;
68 for (q_t i = start; qless(i, end); i = qadd(i, tpi))
69 if (print_sincos(out, i) < 0)
70 return -1;
71 return 0;
72 }
73
74 static int qinfo_print(FILE *out, const qinfo_t *qi) {
75 assert(out);
76 assert(qi);
77 if (fprintf(out, "Q%u.%u Info\n", (unsigned)qi->whole, (unsigned)qi->fractional) < 0) return -1;
78 if (printq(out, qi->bit, "bit") < 0) return -1;
79 if (printq(out, qi->one, "one") < 0) return -1;
80 if (printq(out, qi->zero, "zero") < 0) return -1;
81 if (printq(out, qi->pi, "pi") < 0) return -1;
82 if (printq(out, qi->e, "e") < 0) return -1;
83 if (printq(out, qi->sqrt2, "sqrt2") < 0) return -1;
84 if (printq(out, qi->sqrt3, "sqrt3") < 0) return -1;
85 if (printq(out, qi->ln2, "ln2") < 0) return -1;
86 if (printq(out, qi->ln10, "ln10") < 0) return -1;
87 if (printq(out, qi->min, "min") < 0) return -1;
88 if (printq(out, qi->max, "max") < 0) return -1;
89 if (printq(out, qcordic_circular_gain(-1), "circular-gain") < 0) return -1;
90 if (printq(out, qcordic_hyperbolic_gain(-1), "hyperbolic-gain") < 0) return -1;
91 return 0;
92 }
93
94 static int qconf_print(FILE *out, const qconf_t *qc) {
95 assert(out);
96 assert(qc);
97 if (fprintf(out, "Q Configuration\n") < 0) return -1;
98 const char *bounds = "unknown";
99 qbounds_t bound = qc->bound;
100 if (bound == qbound_saturate)
101 bounds = "saturate";
102 if (bound == qbound_wrap)
103 bounds = "wrap";
104 if (fprintf(out, "overflow handler: %s\n", bounds) < 0) return -1;
105 if (fprintf(out, "input/output radix: %u (0 = special case)\n", qc->base) < 0) return -1;
106 if (fprintf(out, "decimal places: %d\n", qc->dp) < 0) return -1;
107 return 0;
108 }
109
110 static FILE *fopen_or_die(const char *file, const char *mode) {
111 assert(file && mode);
112 FILE *h = NULL;
113 errno = 0;
114 if (!(h = fopen(file, mode))) {
115 (void)fprintf(stderr, "file open \"%s\" (mode %s) failed: %s\n", file, mode, strerror(errno));
116 exit(EXIT_FAILURE);
117 }
118 return h;
119 }
120
121 typedef enum {
122 EVAL_OK_E,
123 EVAL_COMMENT_E,
124 EVAL_ERROR_SCAN_E,
125 EVAL_ERROR_TYPE_E,
126 EVAL_ERROR_CONVERT_E,
127 EVAL_ERROR_OPERATION_E,
128 EVAL_ERROR_ARG_COUNT_E,
129 EVAL_ERROR_UNEXPECTED_RESULT_E,
130 EVAL_ERROR_LIMIT_MODE_E,
131
132 EVAL_ERROR_MAX_ERRORS_E, /**< not an error, but a count of errors */
133 } eval_errors_e;
134
135 static const char *eval_error(int e) {
136 if (e < 0 || e >= EVAL_ERROR_MAX_ERRORS_E)
137 return "unknown";
138 const char *msgs[EVAL_ERROR_MAX_ERRORS_E] = {
139 [EVAL_OK_E] = "ok",
140 [EVAL_COMMENT_E] = "(comment)",
141 [EVAL_ERROR_SCAN_E] = "invalid input line",
142 [EVAL_ERROR_TYPE_E] = "unknown function type",
143 [EVAL_ERROR_CONVERT_E] = "numeric conversion failed",
144 [EVAL_ERROR_OPERATION_E] = "unknown operation",
145 [EVAL_ERROR_ARG_COUNT_E] = "incorrect argument count",
146 [EVAL_ERROR_LIMIT_MODE_E] = "unknown limit mode ('|' or '%' allowed)",
147 [EVAL_ERROR_UNEXPECTED_RESULT_E] = "unexpected result",
148 };
149 return msgs[e] ? msgs[e] : "unknown";
150 }
151
152 static int eval_unary_arith(q_t (*m)(q_t), q_t expected, q_t bound, q_t arg1, q_t *result) {
153 assert(m);
154 assert(result);
155 const q_t r = m(arg1);
156 *result = r;
157 if (qwithin_interval(r, expected, bound))
158 return 0;
159 return -1;
160 }
161
162 static int eval_binary_arith(q_t (*f)(q_t, q_t), q_t expected, q_t bound, q_t arg1, q_t arg2, q_t *result) {
163 assert(f);
164 assert(result);
165 const q_t r = f(arg1, arg2);
166 *result = r;
167 if (qwithin_interval(r, expected, bound))
168 return 0;
169 return -1;
170 }
171
172 static int comment(char *line) {
173 assert(line);
174 const size_t length = strlen(line);
175 for (size_t i = 0; i < length; i++) {
176 if (line[i] == '#')
177 return 1;
178 if (!isspace(line[i]))
179 break;
180 }
181 for (size_t i = 0; i < length; i++) {
182 if (line[i] == '#') {
183 line[i] = 0;
184 return 0;
185 }
186 }
187 return 0;
188 }
189
190 static void bounds_set(char method) {
191 assert(method == '|' || method == '%');
192 if (method == '|')
193 qconf.bound = qbound_saturate;
194 qconf.bound = qbound_wrap;
195 }
196
197 static int eval(char *line, q_t *result) {
198 assert(line);
199 assert(result);
200 *result = qinfo.zero;
201 if (comment(line))
202 return EVAL_COMMENT_E;
203 char operation[N] = { 0 }, expected[N] = { 0 }, bounds[N], arg1[N] = { 0 }, arg2[N] = { 0 };
204 char limit = '|';
205 const int count = sscanf(line, "%15s %15s +- %15s %c %15s %15s", operation, expected, bounds, &limit, arg1, arg2);
206 if (limit != '|' && limit != '%')
207 return -EVAL_ERROR_LIMIT_MODE_E;
208 bounds_set(limit);
209 if (count < 5)
210 return -EVAL_ERROR_SCAN_E;
211 const qoperations_t *func = qop(operation);
212 if (!func)
213 return -EVAL_ERROR_OPERATION_E;
214 q_t e = qinfo.zero, b = qinfo.zero, a1 = qinfo.zero, a2 = qinfo.zero;
215 const int argc = count - 4;
216 if (func->arity != argc)
217 return -EVAL_ERROR_ARG_COUNT_E;
218 if (qconv(&e, expected) < 0)
219 return -EVAL_ERROR_CONVERT_E;
220 if (qconv(&b, bounds) < 0)
221 return -EVAL_ERROR_CONVERT_E;
222 if (qconv(&a1, arg1) < 0)
223 return -EVAL_ERROR_CONVERT_E;
224 switch (func->arity) {
225 case 1: if (eval_unary_arith(func->eval.unary, e, b, a1, result) < 0)
226 return -EVAL_ERROR_UNEXPECTED_RESULT_E;
227 break;
228 case 2: if (qconv(&a2, arg2) < 0)
229 return -EVAL_ERROR_CONVERT_E;
230 if (eval_binary_arith(func->eval.binary, e, b, a1, a2, result) < 0)
231 return -EVAL_ERROR_UNEXPECTED_RESULT_E;
232 break;
233 default:
234 return -EVAL_ERROR_TYPE_E;
235 }
236 return EVAL_OK_E;
237 }
238
239 static void trim(char *s) {
240 assert(s);
241 const int size = strlen(s);
242 for (int i = size - 1; i >= 0; i--) {
243 if (!isspace(s[i]))
244 break;
245 s[i] = 0;
246 }
247 }
248
249 static int eval_file(FILE *input, FILE *output) {
250 assert(input);
251 assert(output);
252 char line[256] = { 0 };
253 int rv = 0;
254 while (fgets(line, sizeof(line) - 1, input)) {
255 q_t result = 0;
256 const int r = eval(line, &result);
257 if (r == EVAL_COMMENT_E)
258 continue;
259 if (r < 0) {
260 const char *msg = eval_error(-r);
261 trim(line);
262 if (fprintf(output, "error: %s = ", line) < 0) return -1;
263 if (qprint(output, result) < 0) return -1;
264 if (fprintf(output, " : %s/%d\n", msg, r) < 0) return -1;
265 rv = -1;
266 continue;
267 }
268 trim(line);
269 char rstring[64 + 1] = { 0, };
270 if (qsprint(result, rstring, sizeof(rstring) - 1) < 0) return -1;
271 if (fprintf(output, "ok: %s | (%s)\n", line, rstring) < 0) return -1;
272 memset(line, 0, sizeof line);
273 }
274 return rv;
275 }
276
277 /* --- Unit Test Framework --- */
278
279 typedef struct {
280 unsigned passed,
281 run;
282 } unit_test_t;
283
284 static inline unit_test_t _unit_test_start(const char *file, const char *func, unsigned line) {
285 assert(file);
286 assert(func);
287 unit_test_t t = { .run = 0, .passed = 0 };
288 fprintf(stdout, "Start tests: %s in %s:%u\n\n", func, file, line);
289 return t;
290 }
291
292 static inline void _unit_test_statement(const char *expr_str) {
293 assert(expr_str);
294 if (fprintf(stdout, " STATE: %s\n", expr_str) < 0)
295 abort();
296 }
297
298 static inline void _unit_test(unit_test_t *t, int failed, const char *expr_str, const char *file, const char *func, unsigned line, int die) {
299 assert(t);
300 assert(expr_str);
301 assert(file);
302 assert(func);
303 if (failed) {
304 fprintf(stdout, " FAILED: %s (%s:%s:%u)\n", expr_str, file, func, line);
305 if (die) {
306 fputs("VERIFY FAILED - EXITING\n", stdout);
307 exit(EXIT_FAILURE);
308 }
309 } else {
310 fprintf(stdout, " OK: %s\n", expr_str);
311 t->passed++;
312 }
313 t->run++;
314 }
315
316 static inline int unit_test_finish(unit_test_t *t) {
317 assert(t);
318 fprintf(stdout, "Tests passed/total: %u/%u\n", t->passed, t->run);
319 if (t->run != t->passed) {
320 (void)fputs("[FAILED]\n", stdout);
321 return -1;
322 }
323 if (fputs("[SUCCESS]\n", stdout) < 0)
324 return -1;
325 return 0;
326 }
327
328 #define unit_test_statement(T, EXPR) do { (void)(T); EXPR; _unit_test_statement(( #EXPR)); } while (0)
329 #define unit_test_start() _unit_test_start(__FILE__, __func__, __LINE__)
330 #define unit_test(T, EXPR) _unit_test((T), 0 == (EXPR), (# EXPR), __FILE__, __func__, __LINE__, 0)
331 #define unit_test_verify(T, EXPR) _unit_test((T), 0 == (EXPR), (# EXPR), __FILE__, __func__, __LINE__, 1)
332
333 static int test_sanity(void) {
334 unit_test_t t = unit_test_start();
335 q_t t1 = 0, t2 = 0;
336 unit_test_statement(&t, t1 = qint(1));
337 unit_test_statement(&t, t2 = qint(2));
338 unit_test(&t, qtoi(qadd(t1, t2)) == 3);
339 return unit_test_finish(&t);
340 }
341
342 static int test_pack(void) {
343 unit_test_t t = unit_test_start();
344 q_t p1 = 0, p2 = 0;
345 char buf[sizeof(p1)] = { 0 };
346 unit_test_statement(&t, p1 = qnegate(qinfo.pi));
347 unit_test_statement(&t, p2 = 0);
348 unit_test(&t, qunequal(p1, p2));
349 unit_test(&t, qpack(&p1, buf, sizeof p1 - 1) < 0);
350 unit_test(&t, qpack(&p1, buf, sizeof buf) == sizeof(p1));
351 unit_test(&t, qunpack(&p2, buf, sizeof buf) == sizeof(p2));
352 unit_test(&t, qequal(p1, p2));
353 return unit_test_finish(&t);
354 }
355
356 static int test_fma(void) {
357 unit_test_t t = unit_test_start();
358
359 q_t a, b, c, r;
360 const q_t one_and_a_half = qdiv(qint(3), qint(2));
361
362 /* incorrect, but expected, result due to saturation */
363 unit_test_statement(&t, a = qinfo.max);
364 unit_test_statement(&t, b = one_and_a_half);
365 unit_test_statement(&t, c = qinfo.min);
366 unit_test_statement(&t, r = qadd(qmul(a, b), c));
367 unit_test(&t, qwithin_interval(r, qint(0), qint(1)));
368
369 /* correct result using Fused Multiply Add */
370 unit_test_statement(&t, a = qinfo.max);
371 unit_test_statement(&t, b = one_and_a_half);
372 unit_test_statement(&t, c = qinfo.min);
373 unit_test_statement(&t, r = qfma(a, b, c));
374 unit_test(&t, qwithin_interval(r, qdiv(qinfo.max, qint(2)), qint(1)));
375
376 return unit_test_finish(&t);
377 }
378
379 static inline int test_filter(void) {
380 unit_test_t t = unit_test_start();
381 qfilter_t lpf = { .raw = 0 }, hpf = { .raw = 0 };
382 const q_t beta = qdiv(qint(1), qint(3));
383 qfilter_init(&lpf, qint(0), beta, qint(0));
384 qfilter_init(&hpf, qint(0), beta, qint(0));
385 for (int i = 0; i < 100; i++) {
386 char low[64 + 1] = { 0, }, high[64 + 1] = { 0, };
387 const q_t step = qdiv(qint(i), qint(100));
388 const q_t input = qint(1);
389 qfilter_low_pass(&lpf, step, input);
390 qfilter_high_pass(&hpf, step, input);
391 if (qsprint(qfilter_value(&lpf), low, sizeof(low) - 1ull) < 0) return -1;
392 if (qsprint(qfilter_value(&hpf), high, sizeof(high) - 1ull) < 0) return -1;
393 if (fprintf(stdout, "%2d: %s\t%s\n", i, low, high) < 0) return -1;
394 }
395 return unit_test_finish(&t);
396 }
397
398 static int qmatrix_print(FILE *out, const q_t *m) {
399 assert(out);
400 assert(m);
401 const size_t alloc = qmatrix_string_length(m);
402 char *ms = malloc(alloc + 1);
403 if (!ms)
404 return -1;
405 int r = qmatrix_sprintb(m, ms, alloc + 1, 10);
406 if (r >= 0)
407 r = fprintf(out, "%s\n", ms);
408 free(ms);
409 return r;
410 }
411
412 #define QMATRIX(ROW, COLUMN, ...) { 0, ROW * COLUMN, ROW, COLUMN, __VA_ARGS__ }
413 #define QMATRIXZ(ROW, COLUMN) QMATRIX((ROW), (COLUMN), 0)
414 #define QMATRIXSZ(ROW, COLUMN) ((((ROW)*(COLUMN)) + 4)*sizeof(q_t))
415
416 static int test_matrix(void) {
417 unit_test_t t = unit_test_start();
418 FILE *out = stdout;
419 q_t a[] = QMATRIX(2, 3,
420 QINT(1), QINT(2), QINT(3),
421 QINT(4), QINT(5), QINT(6),
422 );
423 q_t b[] = QMATRIX(3, 2,
424 QINT(2), QINT(3),
425 QINT(4), QINT(5),
426 QINT(6), QINT(7),
427 );
428 const q_t abr[] = QMATRIX(2, 2,
429 QINT(28), QINT(34),
430 QINT(64), QINT(79),
431 );
432 const q_t abrp[] = QMATRIX(2, 2,
433 QINT(28), QINT(64),
434 QINT(34), QINT(79),
435 );
436 q_t ab[QMATRIXSZ(2, 2)] = QMATRIXZ(2, 2);
437 q_t abp[QMATRIXSZ(2, 2)] = QMATRIXZ(2, 2);
438 unit_test_verify(&t, 0 == qmatrix_mul(ab, a, b));
439 unit_test_verify(&t, 0 == qmatrix_transpose(abp, ab));
440 unit_test(&t, qmatrix_equal(ab, abr));
441 unit_test(&t, qmatrix_equal(ab, abrp));
442 qmatrix_print(out, a);
443 qmatrix_print(out, b);
444 qmatrix_print(out, ab);
445 qmatrix_print(out, abp);
446 return unit_test_finish(&t);
447 }
448
449 static int test_matrix_trace(void) {
450 unit_test_t t = unit_test_start();
451 q_t a[] = QMATRIX(2, 2,
452 QINT(1), QINT(2),
453 QINT(4), QINT(5),
454 );
455 q_t b[] = QMATRIX(2, 2,
456 QINT(2), QINT(3),
457 QINT(4), QINT(5),
458 );
459 q_t ta[sizeof(a) / sizeof(a[0])] = QMATRIX(2, 2, 0);
460 q_t tb[sizeof(b) / sizeof(b[0])] = QMATRIX(2, 2, 0);
461 q_t apb[sizeof(a) / sizeof(a[0])] = QMATRIX(2, 2, 0);
462 BUILD_BUG_ON(sizeof a != sizeof ta);
463 BUILD_BUG_ON(sizeof a != sizeof tb);
464 BUILD_BUG_ON(sizeof a != sizeof b);
465 BUILD_BUG_ON(sizeof a != sizeof apb);
466 unit_test_verify(&t, 0 == qmatrix_transpose(ta, a));
467 unit_test_verify(&t, 0 == qmatrix_transpose(tb, b));
468 unit_test_verify(&t, 0 == qmatrix_add(apb, a, b));
469 unit_test(&t, qequal(qmatrix_trace(a), QINT(6)));
470 unit_test(&t, qequal(qmatrix_trace(b), QINT(7)));
471 unit_test(&t, qequal(qmatrix_trace(a), qmatrix_trace(ta)));
472 unit_test(&t, qequal(qmatrix_trace(apb), qadd(qmatrix_trace(a), qmatrix_trace(b))));
473 printq(stdout, qmatrix_determinant(a), "det(a)");
474 return unit_test_finish(&t);
475 }
476
477 static q_t qid(q_t x) { return x; }
478 static q_t qsq(q_t x) { return qmul(x, x); }
479
480 static int test_simpson(void) {
481 unit_test_t t = unit_test_start();
482 unit_test(&t, qwithin_interval(qsimpson(qid, QINT(0), QINT(10), 100), QINT(50), QINT(1))); // (x^2)/2 + C
483 unit_test(&t, qwithin_interval(qsimpson(qsq, qnegate(QINT(2)), QINT(5), 100), QINT(44), QINT(1))); // (x^3)/3 + C
484 return unit_test_finish(&t);
485 }
486
487 static int internal_tests(void) {
488 typedef int (*unit_test_t)(void);
489 unit_test_t tests[] = {
490 test_sanity,
491 test_pack,
492 test_fma,
493 // test_filter,
494 test_matrix,
495 test_matrix_trace,
496 test_simpson,
497 NULL
498 };
499 for (size_t i = 0; tests[i]; i++)
500 if (tests[i]() < 0)
501 return -1;
502 return 0;
503 }
504
505 static int help(FILE *out, const char *arg0) {
506 assert(out);
507 assert(arg0);
508 const char *h = "\n\
509 Program: Q-Number (Q16.16, signed) library testbench\n\
510 Author: Richard James Howe (2018)\n\
511 License: MIT\n\
512 E-mail: howe.r.j.89@gmail.com\n\
513 Site: https://github.com/howerj/q\n\n\
514 Options:\n\
515 \t-s\tprint a sine-cosine table\n\
516 \t-h\tprint this help message and exit\n\
517 \t-i\tprint library information\n\
518 \t-t\trun internal unit tests\n\
519 \t-v\tprint version information and exit\n\
520 \tfile\texecute commands in file\n\n\
521 This program wraps up a Q-Number library and allows it to be tested by\n\
522 giving it commands, either from stdin, or from a file. The format is\n\
523 strict and the parser primitive, but it is only meant to be used to\n\
524 test that the library is doing the correct thing and not as a\n\
525 calculator.\n\n\
526 Commands evaluated consist of an operator with the require arguments\n\
527 (either one or two arguments) and compare the result with an expected\n\
528 value, which can within a specified bounds for the test to pass. If\n\
529 the test fails the program exits, indicating failure. The format is:\n\
530 \n\
531 \toperator expected +- allowance | arg1 arg2\n\n\
532 operators include '+', '-', '/', 'rem', '<', which require two\n\
533 arguments, and unary operators like 'sin', and 'negate', which require\n\
534 only one. 'expected', 'allowance', 'arg1' and 'arg2' are all fixed\n\
535 numbers of the form '-12.45'. 'expected' is the expected result,\n\
536 'allowance' the +/- amount the result is allowed to deviated by, and\n\
537 'arg1' and 'arg2' the operator arguments.\n\
538 \n\n";
539 if (fprintf(out, "usage: %s -h -s -i -v -t -c file\n", arg0) < 0) return -1;
540 if (fputs(h, out) < 0) return -1;
541 return 0;
542 }
543
544 int main(int argc, char **argv) {
545 bool ran = false;
546 for (int i = 1; i < argc; i++) {
547 if (!strcmp("-h", argv[i])) {
548 if (help(stdout, argv[0]) < 0)
549 return 1;
550 return 0;
551 } else if (!strcmp("-s", argv[i])) {
552 print_sincos_table(stdout);
553 ran = true;
554 } else if (!strcmp("-v", argv[i])) {
555 fprintf(stdout, "version 1.0\n");
556 return 0;
557 } else if (!strcmp("-t", argv[i])) {
558 if (internal_tests() < 0)
559 return 1;
560 ran = true;
561 } else if (!strcmp("-i", argv[i])) {
562 if (qinfo_print(stdout, &qinfo) < 0)
563 return 1;
564 if (qconf_print(stdout, &qconf) < 0)
565 return 1;
566 ran = true;
567 } else {
568 FILE *input = fopen_or_die(argv[i], "rb");
569 FILE *output = stdout;
570 const int r = eval_file(input, output);
571 ran = true;
572 fclose(input);
573 if (r < 0)
574 return 1;
575 }
576 }
577 if (!ran)
578 return eval_file(stdin, stdout);
579 return 0;
580 }
581