#ifndef CORE_TRI_H #define CORE_TRI_H #include #include #include #include #include #include #include #include /* 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 true; return false; } 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_gt(b, a); } static inline bool tri_ge(tri_t a, tri_t b) { return tri_le(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