aboutsummaryrefslogtreecommitdiff
path: root/core/tri.h
diff options
context:
space:
mode:
authorKimplul <kimi.h.kuparinen@gmail.com>2023-10-28 00:00:21 +0300
committerKimplul <kimi.h.kuparinen@gmail.com>2023-10-28 00:00:21 +0300
commitd96b8c7d12fc61ec609d6138627b9d6d18139a9a (patch)
tree82bc9a4c2b22de6c021097fb79a57b6d7756a127 /core/tri.h
downloadtri-d96b8c7d12fc61ec609d6138627b9d6d18139a9a.tar.gz
tri-d96b8c7d12fc61ec609d6138627b9d6d18139a9a.zip
move to monorepo
+ Will probably have to make some changes to dir layout to make things make more sense
Diffstat (limited to 'core/tri.h')
-rw-r--r--core/tri.h711
1 files changed, 711 insertions, 0 deletions
diff --git a/core/tri.h b/core/tri.h
new file mode 100644
index 0000000..e4336ec
--- /dev/null
+++ b/core/tri.h
@@ -0,0 +1,711 @@
+#ifndef CORE_TRI_H
+#define CORE_TRI_H
+
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stddef.h>
+#include <assert.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+/* type that can contain the binary value of a tri */
+typedef uint64_t tri_t;
+
+#define TRI_WIDTH 27
+
+/* (3^(27) - 1) / 2 */
+#define TRI_MAX 3812798742493LL
+#define TRI_MIN -TRI_MAX
+
+/* 54 bits */
+#define TRI_BMASK 0x7fffffffffffff
+/* alternating ...0101 */
+#define TRI_PMASK 0x15555555555555
+/* alternating ...1010 */
+#define TRI_NMASK 0x2aaaaaaaaaaaaa
+
+/**
+ * fairly trivial mapping,
+ * where operations are mapped as N/O/P, where
+ * N means that negative values are mapped,
+ * O means neutral and P means positive.
+ *
+ * So for example NOP means N is mapped to N,
+ * O is mapped to O, P is mapped to P.
+ */
+enum triop {
+ N = 1,
+ O = 2,
+ P = 3,
+};
+typedef enum triop triop_t;
+
+/* syntactic sugar around passing arguments to TRIOP3 and TRIOP9 */
+#define NNN N, N, N
+#define NNO N, N, O
+#define NNP N, N, P
+#define NON N, O, N
+#define NOO N, O, O
+#define NOP N, O, P
+#define NPN N, P, N
+#define NPO N, P, O
+#define NPP N, P, P
+
+#define ONN O, N, N
+#define ONO O, N, O
+#define ONP O, N, P
+#define OON O, O, N
+#define OOO O, O, O
+#define OOP O, O, P
+#define OPN O, P, N
+#define OPO O, P, O
+#define OPP O, P, P
+
+#define PNN P, N, N
+#define PNO P, N, O
+#define PNP P, N, P
+#define PON P, O, N
+#define POO P, O, O
+#define POP P, O, P
+#define PPN P, P, N
+#define PPO P, P, O
+#define PPP P, P, P
+
+#define GET_TRIOP(t, x)\
+ (((t) >> ((x) * 2)) & 0x3)
+
+#define _SET_TRIOP(t, x)\
+ ((t) << ((x) * 2))
+
+#define _TRIOP3(a, b, c, i) \
+ (_SET_TRIOP((a), (i) + 0) | _SET_TRIOP((b), (i) + 1) | _SET_TRIOP((c), (i) + 2))
+
+#define TRIOP3(a) \
+ _TRIOP3(a, 0)
+
+#define _TRIOP9(a, b, c, d, e, f, g, h, i) \
+ (_TRIOP3(a, b, c, 0) | _TRIOP3(d, e, f, 3) | _TRIOP3(g, h, i, 6))
+
+#define TRIOP9(a, b, c) \
+ _TRIOP9(a, b, c)
+
+static inline int tri_get_trit(tri_t t, size_t i)
+{
+ bool p = (t >> ((2 * i) + 0)) & 1;
+ bool n = (t >> ((2 * i) + 1)) & 1;
+
+ /* assume normalized */
+ if (!p && !n)
+ return 0;
+
+ if (p)
+ return 1;
+
+ if (n)
+ return -1;
+
+ /* shouldn't be possible */
+ return 0;
+}
+
+static inline tri_t tri_set_trit(tri_t t, size_t i, int f)
+{
+ /* preclear area */
+ t &= ~(0x3ULL << (i * 2));
+ if (f == 0)
+ return t;
+
+ if (f < 0)
+ return t | (0x2ULL << (i * 2));
+
+ return t | (0x1ULL << (i * 2));
+}
+
+/* keep n lowest trits */
+static inline tri_t tri_mask(tri_t t, size_t n)
+{
+ return t & ((1ULL << (n * 2)) - 1);
+}
+
+/* discard n lowest trits */
+static inline tri_t tri_discard(tri_t t, size_t n)
+{
+ return t & ~((1ULL << (n * 2)) - 1);
+}
+
+static inline tri_t tri_ns(tri_t t)
+{
+ tri_t n = ( t & TRI_NMASK) >> 1;
+ tri_t p = (~t & TRI_PMASK) >> 0;
+
+ tri_t ns = (n & p);
+ ns |= ns << 1;
+
+ return ns;
+}
+
+static inline tri_t tri_ps(tri_t t)
+{
+ tri_t n = (~t & TRI_NMASK) >> 1;
+ tri_t p = ( t & TRI_PMASK) >> 0;
+
+ tri_t ps = (n & p);
+ ps |= ps << 1;
+
+ return ps;
+}
+
+static inline tri_t tri_zs(tri_t t)
+{
+ tri_t n = (~t & TRI_NMASK) >> 1;
+ tri_t p = (~t & TRI_PMASK) >> 0;
+
+ tri_t z = (n & p);
+ z |= z << 1;
+
+ return z;
+}
+
+static inline tri_t tri_us(tri_t t)
+{
+ tri_t n = ( t & TRI_NMASK) >> 1;
+ tri_t p = ( t & TRI_PMASK) >> 0;
+
+ tri_t u = (n & p);
+ u |= u << 1;
+
+ return u;
+}
+
+static inline tri_t tri_normalize(tri_t t)
+{
+ /* convert binary ones in both positive and negative trits to zero */
+ tri_t t0 = ((t & TRI_PMASK) << 1) & t;
+ t0 |= t0 << 1;
+ return (t & ~t0) & TRI_BMASK;
+}
+
+static inline tri_t tri_bias(tri_t a)
+{
+ tri_t z = tri_zs(a);
+ tri_t p = tri_ps(a);
+ return (TRI_PMASK & z) | (TRI_NMASK & p);
+}
+
+static inline tri_t tri_unbias(tri_t a)
+{
+ tri_t n = tri_ns(a);
+ tri_t z = tri_zs(a);
+ return (TRI_PMASK & n) | (TRI_NMASK & z);
+}
+
+static inline tri_t tri_neg(tri_t t)
+{
+ tri_t p = t & TRI_PMASK;
+ tri_t n = t & TRI_NMASK;
+ return (p << 1) | (n >> 1);
+}
+
+static inline tri_t tri_from(int64_t v)
+{
+ static const uint16_t tbl[] = {
+#include "t9.inc"
+ };
+
+ bool neg = false;
+ if (v < 0) {
+ neg = true;
+ v = - v;
+ }
+
+ tri_t s = 0;
+ for (size_t i = 0; i < 5; ++i) {
+ int64_t d = v / 729;
+ int64_t r = v - (d * 729);
+
+ tri_t t = tbl[r];
+ s |= (t & 0xfff) << (i * 12);
+
+ v = d;
+
+ tri_t c = t >> 12;
+ if (c == 1) v += 1;
+ else if (c == 2) v -= 1;
+ }
+
+ if (neg)
+ s = tri_neg(s);
+
+ return s & TRI_BMASK;
+}
+
+static inline int64_t tri_to(tri_t t)
+{
+ /* apparently an algorithm that a superoptimizer found so I'm not even
+ * going to try to understand what's going on */
+ tri_t acc = tri_bias(t);
+
+ acc = acc - (((acc >> 2) & 0x3333333333333333) * ( 4 - 3));
+ acc = acc - (((acc >> 4) & 0x0F0F0F0F0F0F0F0F) * ( 16 - 9));
+ acc = acc - (((acc >> 8) & 0x00FF00FF00FF00FF) * ( 256 - 81));
+ acc = acc - (((acc >> 16) & 0x0000FFFF0000FFFF) * ( 65536 - 6561));
+ acc = acc - (((acc >> 32) & 0x00000000FFFFFFFF) * (4294967296 - 43046721));
+
+ return (int64_t)acc - TRI_MAX;
+}
+
+static inline tri_t tri_parse(const char *s, size_t len, char n, char o, char p)
+{
+ int64_t r = 1;
+ int64_t sum = 0;
+ size_t l = len < TRI_WIDTH ? len : TRI_WIDTH;
+ for (size_t i = 0; i < l; r *= 3, ++i) {
+ char t = s[len - 1 - i];
+ if (t == n) { sum -= r; continue; }
+ if (t == p) { sum += r; continue; }
+ if (t == o) continue;
+
+ /* not pretty but good enough for now */
+ fprintf(stderr, "invalid trinary digit: %c\n", t);
+ abort();
+ }
+
+ return tri_from(sum);
+}
+
+static inline tri_t tri_parse_default(const char *s, size_t len)
+{
+ return tri_parse(s, len, 'i', '0', '1');
+}
+
+/* does not append trailing NULL */
+/* could be useful to add how many zeroes to print out? */
+static inline size_t tri_fmt(char *s, size_t len, tri_t t, char n, char o, char p)
+{
+ int64_t v = tri_to(t);
+ if (v == 0) {
+ s[0] = '0';
+ return 1;
+ }
+
+ /* naive conversion to trinary is pretty nifty as it automatically stops
+ * once we reach zero, but I still need to add padding and possibly base
+ * as well */
+ size_t i = 0;
+ while (v) {
+ if (i >= len)
+ break;
+
+ int d = v % 3;
+
+ switch (d) {
+ case +2:
+ case -1: s[i] = n; v += 1; break;
+
+ case -2:
+ case +1: s[i] = p; v -= 1; break;
+
+ default: s[i] = o;
+ }
+
+ v /= 3;
+ i++;
+ }
+
+ /* reverse string */
+ for (size_t j = 0; j < i / 2; ++j) {
+ size_t k = i - j - 1;
+ char tmp = s[j];
+ s[j] = s[k];
+ s[k] = tmp;
+ }
+ return i;
+}
+
+static inline size_t tri_fmt_default(char *s, size_t len, tri_t t)
+{
+ return tri_fmt(s, len, t, 'i', '0', '1');
+}
+
+static inline tri_t tri_sl(tri_t t, size_t i)
+{
+ return t << (i * 2);
+}
+
+static inline tri_t tri_sr(tri_t t, size_t i)
+{
+ return t >> (i * 2);
+}
+
+/* bias happends to be used in several places */
+#define BIAS 0x5555555555555555ULL
+static inline tri_t tri_bsum(tri_t a, tri_t b)
+{
+ /* directly lifted from libter27,
+ * http://homepage.cs.uiowa.edu/~dwjones/ternary/libtern.shtml
+ * added comments to help myself understand what's going on */
+
+ /* Really cool algorithm, first `a` is mapped to another number system,
+ * where 00 is invalid, and the trinary values look like
+ * 00 "0" => 01
+ * 01 "1" => 10
+ * 10 "2" => 11
+ *
+ * This is so our hardware addition functions according to
+ *
+ * c+b | 00 | 01 | 10
+ * ----+----+----+---
+ * 01 | 01 | 10 | 11
+ * 10 | 10 | 11 |+00
+ * 11 | 11 |+00 |+01
+ *
+ * + means carry. Note how similar the above table is to unbalanced
+ * trinary:
+ *
+ * a+b | 0 | 1 | 2
+ * ----+---+---+--
+ * 0 | 0 | 1 | 2
+ * 1 | 1 | 2 |+0
+ * 2 | 2 |+0 |+1
+ *
+ * Ignoring carries for the moment, we can map 01 => 0, 10 => 1 and
+ * 11 => 2. To get back to our normal number system, we can subtract the
+ * bias again, except for where carries happen. However, notice that the
+ * number that is left behind IS the number we want! So we just need to
+ * detect when a carry has occured, and we don't have to fix up that
+ * location.
+ *
+ * Getting the carry occurences is done with b ^ c ^ d.
+ * b^c is the standard way to compute the sum of two bits and discarding
+ * the carry. Therefore, if (b^c) is different from d, we know that the
+ * previous stage must have sent us a carry!
+ */
+
+ /* add bias to make binary addition in hardware more closely
+ * match unbalanced trinary addition */
+ tri_t c = a + BIAS;
+
+ /* caclulate biased sum with errors */
+ tri_t d = b + c;
+
+ /* detect if a carry happened in the previous stage, if one is detected
+ * then that region shouldn't be subtracted in the final step */
+ tri_t e = (~(b ^ c ^ d) & BIAS) >> 2;
+
+ /* subtract to counter adding bias in first step and fix errors that
+ * show up in second stage */
+ return (d - e) & TRI_BMASK;
+}
+
+static inline tri_t tri_bneg(tri_t a)
+{
+ return 0x2aaaaaaaaaaaaaaaULL - a;
+}
+
+#define TRI_B0 0x1540000000000000ULL
+static inline tri_t tri_bsl(tri_t a, int i)
+{
+ return (a << (2 * i)) | (TRI_B0 >> (2 * (31 - i)));
+}
+
+static inline tri_t tri_bsr(tri_t a, int i)
+{
+ return (a >> (2 * i)) | (TRI_B0 << (2 * (31 - i)));
+}
+
+static inline tri_t tri_badd(tri_t a, tri_t b)
+{
+ /* do some biased number magic for partial sum */
+ tri_t bsum = tri_bsum(a, b);
+
+ /* subtract bias for full sum. note that bias + 1 is the same as -bias */
+ tri_t sum = tri_bsum(bsum, BIAS + 1);
+ return sum;
+}
+
+static inline tri_t tri_bsub(tri_t a, tri_t b)
+{
+ return tri_badd(a, tri_bneg(b));
+}
+
+static inline tri_t tri_add(tri_t a, tri_t b)
+{
+ /* convert to biased numbers */
+ tri_t ab = tri_bias(a);
+ tri_t bb = tri_bias(b);
+
+ /* internal helper */
+ tri_t sum = tri_badd(ab, bb);
+
+ /* convert back to balanced numbers */
+ tri_t r = tri_unbias(sum);
+ return r;
+}
+#undef BIAS
+
+static inline tri_t tri_sub(tri_t a, tri_t b)
+{
+ return tri_add(a, tri_neg(b));
+}
+
+#define TRI_N13 0b101010
+#define TRI_N12 0b101000
+#define TRI_N11 0b101001
+#define TRI_N10 0b100010
+#define TRI_N9 0b100000
+#define TRI_N8 0b100001
+#define TRI_N7 0b100110
+#define TRI_N6 0b100100
+#define TRI_N5 0b100101
+#define TRI_N4 0b001010
+#define TRI_N3 0b001000
+#define TRI_N2 0b001001
+#define TRI_N1 0b000010
+#define TRI_0 0b000000
+#define TRI_P1 0b000001
+#define TRI_P2 0b000110
+#define TRI_P3 0b000100
+#define TRI_P4 0b000101
+#define TRI_P5 0b011010
+#define TRI_P6 0b011000
+#define TRI_P7 0b011001
+#define TRI_P8 0b010010
+#define TRI_P9 0b010000
+#define TRI_P10 0b010001
+#define TRI_P11 0b010110
+#define TRI_P12 0b010100
+#define TRI_P13 0b010101
+
+static inline tri_t tri_mul(tri_t a, tri_t b)
+{
+ /* probably needs a little bit more testing */
+ tri_t pa = tri_bias(a);
+ tri_t na = tri_bneg(a);
+ tri_t p = tri_bias(0);
+
+ for (int i = 0; i < 9; ++i) {
+ tri_t m = b & 0x3f;
+ b >>= 6;
+
+ switch (m) {
+ case TRI_N2: p = tri_badd(p, na); /* fallthru */
+ case TRI_N1: p = tri_badd(p, na);
+ break;
+
+ case TRI_N5: p = tri_badd(p, na); /* fallthru */
+ case TRI_N4: p = tri_badd(p, na); /* fallthru */
+ case TRI_N3: p = tri_badd(p, tri_bsl(na, 1));
+ break;
+
+ case TRI_N8: p = tri_badd(p, na); /* fallthru */
+ case TRI_N7: p = tri_badd(p, na); /* fallthru */
+ case TRI_N6: p = tri_badd(p, tri_bsl(na, 1));
+ p = tri_badd(p, tri_bsl(na, 1));
+ break;
+
+ case TRI_N11: p = tri_badd(p, na); /* fallthru */
+ case TRI_N10: p = tri_badd(p, na); /* fallthru */
+ case TRI_N9: p = tri_badd(p, tri_bsl(na, 2));
+ break;
+
+ case TRI_N13: p = tri_badd(p, na); /* fallthru */
+ case TRI_N12: p = tri_badd(p, tri_bsl(na, 2));
+ p = tri_badd(p, tri_bsl(na, 1));
+ break;
+
+ case TRI_0: break;
+ case TRI_P2: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P1: p = tri_badd(p, pa);
+ break;
+
+ case TRI_P5: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P4: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P3: p = tri_badd(p, tri_bsl(pa, 1));
+ break;
+
+ case TRI_P8: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P7: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P6: p = tri_badd(p, tri_bsl(pa, 1));
+ p = tri_badd(p, tri_bsl(pa, 1));
+ break;
+
+ case TRI_P11: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P10: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P9: p = tri_badd(p, tri_bsl(pa, 2));
+ break;
+
+ case TRI_P13: p = tri_badd(p, pa); /* fallthru */
+ case TRI_P12: p = tri_badd(p, tri_bsl(pa, 2));
+ p = tri_badd(p, tri_bsl(pa, 1));
+ break;
+
+ default: printf("misshandled 0x%lx\n", m);
+ abort();
+ }
+ }
+
+ return tri_unbias(p);
+}
+
+#if 0
+/* I'll deal with division later, turns out hardware and software division
+ * differs */
+static inline tri_t tri_div(tri_t a, tri_t b)
+{
+ /* ditto */
+ trival_t ba = tri_to(a);
+ trival_t bb = tri_to(b);
+ return tri_from(ba / bb);
+}
+
+static inline tri_t tri_rem(tri_t a, tri_t b)
+{
+ trival_t ba = tri_to(a);
+ trival_t bb = tri_to(b);
+ return tri_from(ba % bb);
+}
+#endif
+
+/* return sign of whole tri, -1 for negative, 0 for zero, 1 for positive */
+static inline int tri_sign(tri_t t)
+{
+ if (t == 0)
+ return 0;
+
+ int lz = __builtin_clzll(t);
+ if (lz % 2 == 0)
+ return 1;
+
+ return -1;
+}
+
+static inline bool tri_eq(tri_t a, tri_t b)
+{
+ return a == b;
+}
+
+static inline bool tri_lt(tri_t a, tri_t b)
+{
+ tri_t r = tri_sub(a, b);
+ int s = tri_sign(r);
+ if (s >= 0)
+ return false;
+
+ return true;
+}
+
+static inline bool tri_le(tri_t a, tri_t b)
+{
+ return tri_eq(a, b) || tri_lt(a, b);
+}
+
+static inline bool tri_gt(tri_t a, tri_t b)
+{
+ return tri_le(b, a);
+}
+
+static inline bool tri_ge(tri_t a, tri_t b)
+{
+ return tri_lt(b, a);
+}
+
+#define tri_illegal_op()\
+ do { fprintf(stderr, "illegal op, aborting\n"); abort(); } while (0)
+
+#define tri_unop(o, a)\
+ tri_unop_do(_TRIOP3(o, 0), a)
+
+static inline tri_t tri_unop_do(triop_t op, tri_t a)
+{
+
+ tri_t n = 0;
+ tri_t p = 0;
+
+ tri_t an = tri_ns(a);
+ tri_t ap = tri_ps(a);
+ tri_t az = tri_zs(a);
+
+ /* handle mapping n */
+ switch (GET_TRIOP(op, 0)) {
+ case N: n |= an; break;
+ case P: p |= an; break;
+ case O: /* noop */ break;
+ default: tri_illegal_op();
+ }
+
+ /* map o */
+ switch (GET_TRIOP(op, 1)) {
+ case N: n |= az; break;
+ case P: p |= az; break;
+ case O: /* noop */ break;
+ default: tri_illegal_op();
+ }
+
+ /* map p */
+ switch (GET_TRIOP(op, 2)) {
+ case N: n |= ap; break;
+ case P: p |= ap; break;
+ case O: /* noop */ break;
+ default: tri_illegal_op();
+ }
+
+ return (TRI_PMASK & p) | (TRI_NMASK & n);
+}
+
+#define tri_diop(o0, o1, o2, a, b)\
+ tri_diop_do(_TRIOP9(o0, o1, o2), a, b)
+
+static inline tri_t tri_diop_do(triop_t op, tri_t a, tri_t b)
+{
+ tri_t n = 0;
+ tri_t p = 0;
+
+ tri_t an = tri_ns(a);
+ tri_t ap = tri_ps(a);
+ tri_t az = tri_zs(a);
+
+ tri_t bn = tri_ns(b);
+ tri_t bp = tri_ps(b);
+ tri_t bz = tri_zs(b);
+
+ for (int i = 0; i < 9; i += 3) {
+ /* select map */
+ tri_t r = 0;
+ switch (i) {
+ case 0: r = an; break;
+ case 3: r = az; break;
+ case 6: r = ap; break;
+ }
+
+ /* map n */
+ switch (GET_TRIOP(op, i + 0)) {
+ case N: n |= r & bn; break;
+ case P: p |= r & bn; break;
+ case O: /* noop */ break;
+ default: tri_illegal_op();
+ }
+
+ /* map o */
+ switch (GET_TRIOP(op, i + 1)) {
+ case N: n |= r & bz; break;
+ case P: p |= r & bz; break;
+ case O: /* noop */ break;
+ default: tri_illegal_op();
+ }
+
+ /* map p */
+ switch (GET_TRIOP(op, i + 2)) {
+ case N: n |= r & bp; break;
+ case P: p |= r & bp; break;
+ case O: /* noop */ break;
+ default: tri_illegal_op();
+ }
+ }
+
+ return (TRI_PMASK & p) | (TRI_NMASK & n);
+}
+
+#undef tri_illegal_op
+#endif