14
|
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
|