summaryrefslogtreecommitdiff
path: root/src/formula.c
diff options
context:
space:
mode:
authorAnders Betts <anders.betts@gmail.com>2026-09-17 19:55:36 +0200
committerAnders Betts <anders.betts@gmail.com>2026-09-17 19:55:36 +0200
commit380195f7cd5e57acf2c1cf2bc41069e6b0b979ed (patch)
tree32a88fb22a7fbe8f1fd5c105156d1f928c93950d /src/formula.c
downloadbokf-380195f7cd5e57acf2c1cf2bc41069e6b0b979ed.tar.gz
bokf-380195f7cd5e57acf2c1cf2bc41069e6b0b979ed.zip
Initial commit: daemon, clients, docs, Docker deploy pipelinev0.1.0
Diffstat (limited to 'src/formula.c')
-rw-r--r--src/formula.c243
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;
+}