diff options
| author | Anders Betts <anders.betts@gmail.com> | 2026-09-17 19:55:36 +0200 |
|---|---|---|
| committer | Anders Betts <anders.betts@gmail.com> | 2026-09-17 19:55:36 +0200 |
| commit | 380195f7cd5e57acf2c1cf2bc41069e6b0b979ed (patch) | |
| tree | 32a88fb22a7fbe8f1fd5c105156d1f928c93950d /src/formula.c | |
| download | bokf-0.1.0.tar.gz bokf-0.1.0.zip | |
Initial commit: daemon, clients, docs, Docker deploy pipelinev0.1.0
Diffstat (limited to 'src/formula.c')
| -rw-r--r-- | src/formula.c | 243 |
1 files changed, 243 insertions, 0 deletions
diff --git a/src/formula.c b/src/formula.c new file mode 100644 index 0000000..1642515 --- /dev/null +++ b/src/formula.c @@ -0,0 +1,243 @@ +#include "formula.h" + +#include <math.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +struct parser { + const char *p; + double x; + char err[128]; +}; + +static void skip_ws(struct parser *ps) +{ + while (*ps->p == ' ' || *ps->p == '\t') + ps->p++; +} + +static int parse_expr(struct parser *ps, double *out); + +static int parse_primary(struct parser *ps, double *out) +{ + skip_ws(ps); + if (*ps->p == '(') { + ps->p++; + if (parse_expr(ps, out) != 0) + return -1; + skip_ws(ps); + if (*ps->p != ')') { + snprintf(ps->err, sizeof ps->err, "saknar ')'"); + return -1; + } + ps->p++; + return 0; + } + if (*ps->p == 'x' || *ps->p == 'X') { + ps->p++; + *out = ps->x; + return 0; + } + if ((*ps->p >= '0' && *ps->p <= '9') || *ps->p == '.') { + char *end = NULL; + double v = strtod(ps->p, &end); + if (!end || end == ps->p) { + snprintf(ps->err, sizeof ps->err, "ogiltigt tal"); + return -1; + } + ps->p = end; + *out = v; + return 0; + } + snprintf(ps->err, sizeof ps->err, "förväntade tal, x eller '('"); + return -1; +} + +static int parse_factor(struct parser *ps, double *out) +{ + skip_ws(ps); + if (*ps->p == '-') { + ps->p++; + if (parse_factor(ps, out) != 0) + return -1; + *out = -*out; + return 0; + } + if (*ps->p == '+') { + ps->p++; + return parse_factor(ps, out); + } + return parse_primary(ps, out); +} + +static int parse_term(struct parser *ps, double *out) +{ + double left; + if (parse_factor(ps, &left) != 0) + return -1; + for (;;) { + skip_ws(ps); + char op = *ps->p; + if (op != '*' && op != '/') + break; + ps->p++; + double right; + if (parse_factor(ps, &right) != 0) + return -1; + if (op == '*') { + left *= right; + } else { + if (right == 0.0) { + snprintf(ps->err, sizeof ps->err, "division med noll"); + return -1; + } + left /= right; + } + } + *out = left; + return 0; +} + +static int parse_expr(struct parser *ps, double *out) +{ + double left; + if (parse_term(ps, &left) != 0) + return -1; + for (;;) { + skip_ws(ps); + char op = *ps->p; + if (op != '+' && op != '-') + break; + ps->p++; + double right; + if (parse_term(ps, &right) != 0) + return -1; + if (op == '+') + left += right; + else + left -= right; + } + *out = left; + return 0; +} + +int formula_eval(const char *formula, double x, double *out_kr, char *errbuf, + size_t errlen) +{ + if (errbuf && errlen) + errbuf[0] = '\0'; + if (!formula || !*formula) { + if (errbuf) + snprintf(errbuf, errlen, "tom formel"); + return -1; + } + struct parser ps; + ps.p = formula; + ps.x = x; + ps.err[0] = '\0'; + double out = 0; + if (parse_expr(&ps, &out) != 0) { + if (errbuf) + snprintf(errbuf, errlen, "%s", ps.err); + return -1; + } + skip_ws(&ps); + if (*ps.p) { + if (errbuf) + snprintf(errbuf, errlen, "oväntat tecken '%c'", *ps.p); + return -1; + } + if (!isfinite(out)) { + if (errbuf) + snprintf(errbuf, errlen, "ogiltigt resultat"); + return -1; + } + *out_kr = out; + return 0; +} + +int formula_valid(const char *formula) +{ + double v; + return formula_eval(formula, 1.0, &v, NULL, 0) == 0; +} + +int formula_resolve_rows(const struct template_row *rows, size_t nrows, + double x, struct resolved_row *out, size_t *out_n, + char *errbuf, size_t errlen) +{ + if (errbuf && errlen) + errbuf[0] = '\0'; + size_t n = 0; + for (size_t i = 0; i < nrows; i++) { + double kr = 0; + char err[128] = ""; + if (formula_eval(rows[i].formula, x, &kr, err, sizeof err) != 0) { + if (errbuf) + snprintf(errbuf, errlen, "rad %zu: %s", i + 1, err); + return -1; + } + int64_t ore = (int64_t)llround(kr * 100.0); + if (ore == 0) + continue; + struct resolved_row *r = &out[n++]; + memset(r, 0, sizeof *r); + snprintf(r->account, sizeof r->account, "%s", rows[i].account); + if (ore > 0) + r->debit_ore = ore; + else + r->credit_ore = -ore; + if (rows[i].description) + snprintf(r->description, sizeof r->description, "%s", + rows[i].description); + } + if (n < 2) { + if (errbuf) + snprintf(errbuf, errlen, + "mallen gav färre än två rader med belopp"); + return -1; + } + + int64_t sum_d = 0, sum_c = 0; + for (size_t i = 0; i < n; i++) { + sum_d += out[i].debit_ore; + sum_c += out[i].credit_ore; + } + int64_t diff = sum_d - sum_c; + if (diff != 0) { + /* the rounding remainder goes to the largest row */ + size_t biggest = 0; + int64_t best = -1; + for (size_t i = 0; i < n; i++) { + int64_t amount = out[i].debit_ore ? out[i].debit_ore + : out[i].credit_ore; + if (amount > best) { + best = amount; + biggest = i; + } + } + struct resolved_row *r = &out[biggest]; + if (r->debit_ore) { + r->debit_ore -= diff; + if (r->debit_ore < 0) { + r->credit_ore = -r->debit_ore; + r->debit_ore = 0; + } + } else { + r->credit_ore += diff; + if (r->credit_ore < 0) { + r->debit_ore = -r->credit_ore; + r->credit_ore = 0; + } + } + if (r->debit_ore == 0 && r->credit_ore == 0) { + if (errbuf) + snprintf(errbuf, errlen, + "kunde inte balansera avrundningen"); + return -1; + } + } + *out_n = n; + return 0; +} |
