aboutsummaryrefslogtreecommitdiff
path: root/core/tri.h
blob: 3837e956ed63b922abd4a1e8ee5e4ba298ab54dc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
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 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