[hackers] [sbase] dc: Add initial version || Roberto E. Vargas Caballero
commit 608f88f08fcb049f30a1963f2bc72e429a63ba42
Author: Roberto E. Vargas Caballero <k0ga_AT_shike2.net>
AuthorDate: Mon Dec 15 16:01:56 2025 +0100
Commit: Roberto E. Vargas Caballero <k0ga_AT_shike2.net>
CommitDate: Wed Jan 14 12:49:01 2026 +0100
dc: Add initial version
This is the initial version of dc already tested in deep and
with a considerable good set of tests.
diff --git a/Makefile b/Makefile
index d77f1ac..6d18939 100644
--- a/Makefile
+++ b/Makefile
_AT_@ -110,6 +110,7 @@ BIN =\
cron\
cut\
date\
+ dc\
dd\
dirname\
du\
diff --git a/dc.1 b/dc.1
new file mode 100644
index 0000000..e28fbc9
--- /dev/null
+++ b/dc.1
_AT_@ -0,0 +1,260 @@
+.Dd January 14, 2026
+.Dt DC 1
+.Os sbase
+.Sh NAME
+.Nm dc
+.Nd arbitrary-precision desk calculator
+.Sh SYNOPSIS
+.Nm
+.Op Fl i
+.Op Ar file ...
+.Sh DESCRIPTION
+.Nm
+is a reverse-polish notation arbitrary-precision desk calculator.
+It reads and executes commands from each
+.Ar file
+in sequence.
+After processing all files,
+.Nm
+reads from stdin.
+.Pp
+Numbers are arbitrary-precision decimal values.
+Numbers may contain a decimal point and use
+.Ql _
+as a negative sign prefix.
+When the input base is greater than 10, letters A-F or a-f represent digits 10-15.
+A hexadecimal number beginning with a lower case letter
+must be preceded by a zero to distinguish it from the command associated with the letter.
+.Sh OPTIONS
+.Bl -tag -width Ds
+.It Fl i
+Extended identifier mode.
+Register names can be enclosed in
+.Ql < Ns No ... Ns >
+(numeric identifiers) or
+.Ql \&" Ns No ... Ns \&"
+(string identifiers).
+.El
+.Sh COMMANDS
+.Ss Printing
+.Bl -tag -width Ds
+.It Ic p
+Print the value on top of the stack with a newline,
+without popping it.
+.It Ic n
+Print the value on top of the stack without a newline,
+and pop it.
+.It Ic P
+Print the value on top of the stack as a byte stream.
+For strings, print the characters directly.
+For numbers, print it in base 100 representing the internal representation.
+The value is popped.
+.It Ic f
+Print all values on the stack, one per line, from top to bottom.
+.El
+.Ss Arithmetic
+.Bl -tag -width Ds
+.It Ic +
+Pop two values, add them, and push the result.
+.It Ic \-
+Pop two values, subtract the top from the second, and push the result.
+.It Ic *
+Pop two values, multiply them, and push the result.
+.It Ic /
+Pop two values, divide the second by the top, and push the quotient.
+The scale of the result is determined by the
+.Ic scale
+parameter.
+.It Ic %
+Pop two values, compute the remainder of dividing the second by the top,
+and push the result.
+.It Ic ~
+Pop two values, compute both quotient and remainder,
+pushing the quotient first then the remainder on top.
+.It Ic ^
+Pop two values, raise the second to the power of the top, and push the result.
+The exponent is truncated to an integer.
+.It Ic v
+Pop the top value, compute its square root, and push the result.
+The precision is determined by the
+.Ic scale
+parameter.
+.El
+.Ss Stack
+.Bl -tag -width Ds
+.It Ic c
+Clear the stack.
+.It Ic d
+Duplicate the top of the stack.
+.It Ic r
+Reverse the order of the top two values on the stack.
+.It Ic z
+Push the current stack depth.
+.El
+.Ss Registers
+Registers are named storage locations.
+In normal mode, register names are single characters.
+With the
+.Fl i
+option, extended names are available.
+Each register also has an associated stack.
+.Bl -tag -width Ds
+.It Ic s Ns Ar x
+Pop the top value and store it in register
+.Ar x .
+.It Ic l Ns Ar x
+Push a copy of the value in register
+.Ar x
+onto the stack.
+.It Ic S Ns Ar x
+Pop the top value and push it onto register
+.Ar x Ns 's
+stack.
+.It Ic L Ns Ar x
+Pop the top value from register
+.Ar x Ns 's
+stack and push it onto the main stack.
+.El
+.Ss Arrays
+Each register has an associated array.
+.Bl -tag -width Ds
+.It Ic : Ns Ar x
+Pop an index, then pop a value, and store the value at that index in array
+.Ar x .
+.It Ic ; Ns Ar x
+Pop an index and push the value at that index in array
+.Ar x .
+.El
+.Ss Parameters
+.Bl -tag -width Ds
+.It Ic i
+Pop the top value and use it as the input radix.
+The input base must be between 2 and 16.
+.It Ic o
+Pop the top value and use it as the output radix.
+The output base must be at least 2.
+.It Ic k
+Pop the top value and use it as the scale
+.Pq number of decimal places
+for arithmetic operations.
+The scale must be non-negative.
+.It Ic I
+Push the current input radix.
+.It Ic O
+Push the current output radix.
+.It Ic K
+Push the current scale.
+.El
+.Ss Strings and Macros
+.Bl -tag -width Ds
+.It Ic \&[ Ns Ar string Ns Ic \&]
+Push
+.Ar string
+onto the stack.
+Brackets inside the string must be balanced or escaped with a backslash.
+.It Ic x
+Pop the top value.
+If it is a string, execute it as a macro.
+If it is a number, push it back.
+.El
+.Ss Conditionals
+The conditional commands pop two values, compare them,
+and if the condition is true, execute the contents of register
+.Ar x
+as a macro.
+.Bl -tag -width Ds
+.It Ic > Ns Ar x
+Execute register
+.Ar x
+if the second value is greater than the top.
+.It Ic < Ns Ar x
+Execute register
+.Ar x
+if the second value is less than the top.
+.It Ic = Ns Ar x
+Execute register
+.Ar x
+if the two values are equal.
+.It Ic !> Ns Ar x
+Execute register
+.Ar x
+if the second value is not greater than (less or equal to) the top.
+.It Ic !< Ns Ar x
+Execute register
+.Ar x
+if the second value is not less than (greater or equal to) the top.
+.It Ic != Ns Ar x
+Execute register
+.Ar x
+if the two values are not equal.
+.El
+.Ss Control
+.Bl -tag -width Ds
+.It Ic q
+Quit.
+If executing a macro, exit two macro levels.
+.It Ic Q
+Pop a value and quit that many macro levels.
+.El
+.Ss Input
+.Bl -tag -width Ds
+.It Ic ?
+Read a line from stdin and execute it.
+.It Ic #
+Comment.
+The rest of the line is ignored.
+.It Ic !\& Ns Ar command
+Execute
+.Ar command
+as a shell command.
+.El
+.Ss Number Information
+.Bl -tag -width Ds
+.It Ic Z
+Pop a value and push its length.
+For numbers, push the number of significant digits.
+For strings, push the number of characters.
+.It Ic X
+Pop a value and push its scale (number of fractional digits).
+For strings, push 0.
+.El
+.Sh EXAMPLES
+Compute 6 * 7:
+.Bd -literal -offset indent
+6 7*p
+.Ed
+.Pp
+Compute 2^10:
+.Bd -literal -offset indent
+2 10^p
+.Ed
+.Pp
+Compute square root of 2 with 20 decimal places:
+.Bd -literal -offset indent
+20k 2vp
+.Ed
+.Pp
+Factorial using recursion (store in register f):
+.Bd -literal -offset indent
+[d1-d1<f*]sf 10lf xp
+.Ed
+.Sh SEE ALSO
+.Xr bc 1
+.Rs
+.%A R. H. Morris
+.%A L. L. Cherry
+.%T "DC \(em An Interactive Desk Calculator"
+.Re
+.Sh STANDARDS
+.Nm
+is compatible with traditional UNIX dc implementations.
+The
+.Fl i
+flag,
+.Ic #
+comments,
+the
+.Ic ~
+operator, and lowercase hexadecimal digits
+.Pq a-f
+are extensions to the traditional dc specification.
diff --git a/dc.c b/dc.c
new file mode 100644
index 0000000..3ba20d9
--- /dev/null
+++ b/dc.c
_AT_@ -0,0 +1,2199 @@
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <setjmp.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include "arg.h"
+#include "util.h"
+
+#define NDIGITS 10
+#define REGSIZ 16
+#define HASHSIZ 128
+
+enum {
+ NVAL,
+ STR,
+ NUM,
+};
+
+enum {
+ NE,
+ LE,
+ GE,
+};
+
+typedef struct num Num;
+typedef struct val Val;
+
+struct num {
+ int begin;
+ int scale;
+ int room;
+ signed char *buf, *wp, *rp;
+};
+
+struct digit {
+ int val;
+ struct digit *next;
+};
+
+struct val {
+ int type;
+ union {
+ Num *n;
+ char *s;
+ } u;
+
+ struct val *next;
+};
+
+struct ary {
+ int n;
+ Val *buf;
+ struct ary *next;
+};
+
+struct reg {
+ char *name;
+ Val val;
+ struct ary ary;
+ struct reg *next;
+};
+
+struct input {
+ FILE *fp;
+ char *buf;
+ size_t n;
+ char *s;
+ struct input *next;
+};
+
+static Val *stack;
+static jmp_buf env;
+static struct input *input;
+static struct reg *htbl[HASHSIZ];
+
+static signed char onestr[] = {1, 0};
+static Num zero, one = {.buf = onestr, .wp = onestr + 1};
+static char digits[] = "0123456789ABCDEF.";
+
+static int scale, ibase = 10, obase = 10;
+static int iflag;
+static int col;
+
+/*
+ * This dc implementation follows the decription of dc found in the paper
+ * DC - An Interactive Desk Calculator, by Robert Morris and Lorinda Cherry
+ */
+
+static void
+dumpnum(char *msg, Num *num)
+{
+ signed char *p;
+
+ printf("Num (%s)", msg ? msg : "");
+
+ if (!num) {
+ puts("NULL\n");
+ return;
+ }
+ printf("\n"
+ "\tscale=%d\n"
+ "\troom=%d\n"
+ "\tdigits=[\n",
+ num->scale,
+ num->room);
+ for (p = num->buf; p < num->wp; ++p)
+ printf("\t\t%02d\n", *p);
+ printf("\t]\n");
+}
+
+/*
+ * error is not called from the implementation of the
+ * arithmetic functions because that can drive to memory
+ * leaks very easily.
+ */
+static void
+error(char *fmt, ...)
+{
+ va_list va;
+
+ va_start(va, fmt);
+ xvprintf(fmt, va);
+ putc('\n', stderr);
+ va_end(va);
+
+ longjmp(env, 1);
+}
+
+static void
+freenum(Num *num)
+{
+ if (!num)
+ return;
+ free(num->buf);
+ free(num);
+}
+
+static Num *
+moreroom(Num *num, int more)
+{
+ int ro, wo, room;
+ signed char *p;
+
+ ro = num->rp - num->buf;
+ wo = num->wp - num->buf;
+ room = num->room;
+
+ if (room > INT_MAX - more)
+ eprintf("out of memory\n");
+
+ room = room + more;
+ if (room < NDIGITS)
+ room = NDIGITS;
+ p = erealloc(num->buf, room);
+ num->buf = p;
+ num->rp = p + ro;
+ num->wp = p + wo;
+ num->room = room;
+
+ return num;
+}
+
+static Num *
+grow(Num *num)
+{
+ return moreroom(num, NDIGITS);
+}
+
+static Num *
+expand(Num *num, int min)
+{
+ if (min < num->room)
+ return num;
+ return moreroom(num, min - num->room);
+}
+
+static Num *
+new(int room)
+{
+ Num *num = emalloc(sizeof(*num));
+
+ num->rp = num->wp = num->buf = NULL;
+ num->begin = num->room = num->scale = 0;
+
+ return moreroom(num, room);
+}
+
+static Num *
+zeronum(int ndigits)
+{
+ Num *num = new(ndigits);
+
+ num->wp = num->buf + ndigits;
+ memset(num->buf, 0, ndigits);
+
+ return num;
+}
+
+static Num *
+wrdigit(Num *num, int d)
+{
+ if (num->wp == &num->buf[num->room])
+ grow(num);
+ *num->wp++ = d;
+
+ return num;
+}
+
+static int
+rddigit(Num *num)
+{
+ if (num->rp == num->wp)
+ return -1;
+ return *num->rp++;
+}
+
+static int
+peek(Num *num)
+{
+ if (num->rp == num->wp)
+ return -1;
+ return *num->rp;
+}
+
+static Num *
+poke(Num *num, unsigned d)
+{
+ if (num->rp == &num->buf[num->room])
+ grow(num);
+ if (num->rp == num->wp)
+ num->wp++;
+ *num->rp = d;
+
+ return num;
+}
+
+static int
+begin(Num *num)
+{
+ return num->begin != 0;
+}
+
+static int
+first(Num *num)
+{
+ num->begin = 0;
+ num->rp = num->buf;
+ return num->rp != num->wp;
+}
+
+static int
+last(Num *num)
+{
+ if (num->wp != num->buf) {
+ num->begin = 0;
+ num->rp = num->wp - 1;
+ return 1;
+ }
+ num->begin = 1;
+ return 0;
+}
+
+static int
+prev(Num *num)
+{
+ if (num->rp > num->buf) {
+ --num->rp;
+ return 1;
+ }
+ num->begin = 1;
+ return 0;
+}
+
+static int
+next(Num *num)
+{
+ if (num->rp != num->wp + 1) {
+ ++num->rp;
+ return 1;
+ }
+ return 0;
+}
+
+static void
+truncate(Num *num)
+{
+ num->wp = num->rp;
+ if (num->rp != num->buf)
+ num->rp--;
+}
+
+static int
+more(Num *num)
+{
+ return (num->rp != num->wp);
+}
+
+static int
+length(Num *num)
+{
+ return num->wp - num->buf;
+}
+
+static int
+tell(Num *num)
+{
+ return num->rp - num->buf;
+}
+
+static void
+seek(Num *num, int pos)
+{
+ num->rp = num->buf + pos;
+}
+
+static void
+rshift(Num *num, int n)
+{
+ int diff;
+
+ diff = length(num) - n;
+ if (diff < 0) {
+ first(num);
+ truncate(num);
+ return;
+ }
+
+ memmove(num->buf, num->buf + n, diff);
+ num->rp = num->buf + diff;
+ truncate(num);
+}
+
+static void
+lshift(Num *num, int n)
+{
+ int len;
+
+ len = length(num);
+ expand(num, len + n);
+ memmove(num->buf + n, num->buf, len);
+ memset(num->buf, 0, n);
+ num->wp += n;
+}
+
+static Num *
+chsign(Num *num)
+{
+ int val, d, carry;
+
+ carry = 0;
+ for (first(num); more(num); next(num)) {
+ d = peek(num);
+ val = 100 - d - carry;
+
+ carry = 1;
+ if (val >= 100) {
+ val -= 100;
+ carry = 0;
+ }
+ poke(num, val);
+ }
+
+ prev(num);
+ if (carry != 0) {
+ if (peek(num) == 99)
+ poke(num, -1);
+ else
+ wrdigit(num, -1);
+ } else {
+ if (peek(num) == 0)
+ truncate(num);
+ }
+
+ return num;
+}
+
+static Num *
+copy(Num *num)
+{
+ Num *p;
+ int len = length(num);
+
+ p = new(len);
+ memcpy(p->buf, num->buf, len);
+ p->wp = p->buf + len;
+ p->rp = p->buf;
+ p->scale = num->scale;
+
+ return p;
+}
+
+static int
+negative(Num *num)
+{
+ return last(num) && peek(num) == -1;
+}
+
+static Num *
+norm(Num *n)
+{
+ /* trailing 0 */
+ for (last(n); peek(n) == 0; truncate(n))
+ ;
+
+ if (negative(n)) {
+ for (prev(n); peek(n) == 99; prev(n)) {
+ poke(n, -1);
+ next(n);
+ truncate(n);
+ }
+ }
+
+ /* adjust scale for 0 case*/
+ if (length(n) == 0)
+ n->scale = 0;
+ return n;
+}
+
+static Num *
+mulnto(Num *src, Num *dst, int n)
+{
+ div_t dd;
+ int d, carry;
+
+ first(dst);
+ truncate(dst);
+
+ carry = 0;
+ for (first(src); more(src); next(src)) {
+ d = peek(src) * n + carry;
+ dd = div(d, 100);
+ carry = dd.quot;
+ wrdigit(dst, dd.rem);
+ }
+
+ if (carry)
+ wrdigit(dst, carry);
+ return dst;
+}
+
+static Num *
+muln(Num *num, int n)
+{
+ div_t dd;
+ int d, carry;
+
+ carry = 0;
+ for (first(num); more(num); next(num)) {
+ d = peek(num) * n + carry;
+ dd = div(d, 100);
+ carry = dd.quot;
+ poke(num, dd.rem);
+ }
+
+ if (carry)
+ wrdigit(num, carry);
+ return num;
+}
+
+static int
+divn(Num *num, int n)
+{
+ div_t dd;
+ int val, carry;
+
+ carry = 0;
+ for (last(num); !begin(num); prev(num)) {
+ val = carry * 100 + peek(num);
+ dd = div(val, n);
+ poke(num, dd.quot);
+ carry = dd.rem;
+ }
+ norm(num);
+
+ return carry;
+}
+
+static void
+div10(Num *num, int n)
+{
+ div_t dd = div(n, 2);
+
+ if (dd.rem == 1)
+ divn(num, 10);
+
+ rshift(num, dd.quot);
+}
+
+static void
+mul10(Num *num, int n)
+{
+ div_t dd = div(n, 2);
+
+ if (dd.rem == 1)
+ muln(num, 10);
+
+ lshift(num, dd.quot);
+}
+
+static void
+align(Num *a, Num *b)
+{
+ int d;
+ Num *max, *min;
+
+ d = a->scale - b->scale;
+ if (d == 0) {
+ return;
+ } else if (d > 0) {
+ min = b;
+ max = a;
+ } else {
+ min = a;
+ max = b;
+ }
+
+ d = abs(d);
+ mul10(min, d);
+ min->scale += d;
+
+ assert(min->scale == max->scale);
+}
+
+static Num *
+addn(Num *num, int val)
+{
+ int d, carry = val;
+
+ for (first(num); carry; next(num)) {
+ d = more(num) ? peek(num) : 0;
+ d += carry;
+ carry = 0;
+
+ if (d >= 100) {
+ carry = 1;
+ d -= 100;
+ }
+ poke(num, d);
+ }
+
+ return num;
+}
+
+static Num *
+reverse(Num *num)
+{
+ int d;
+ signed char *p, *q;
+
+ for (p = num->buf, q = num->wp - 1; p < q; ++p, --q) {
+ d = *p;
+ *p = *q;
+ *q = d;
+ }
+
+ return num;
+}
+
+static Num *
+addnum(Num *a, Num *b)
+{
+ Num *c;
+ int len, alen, blen, carry, da, db, sum;
+
+ align(a, b);
+ alen = length(a);
+ blen = length(b);
+ len = (alen > blen) ? alen : blen;
+ c = new(len);
+
+ first(a);
+ first(b);
+ carry = 0;
+ while (len-- > 0) {
+ da = (more(a)) ? rddigit(a) : 0;
+ db = (more(b)) ? rddigit(b) : 0;
+
+ sum = da + db + carry;
+ if (sum >= 100) {
+ carry = 1;
+ sum -= 100;
+ } else if (sum < 0) {
+ carry = -1;
+ sum += 100;
+ } else {
+ carry = 0;
+ }
+
+ wrdigit(c, sum);
+ }
+
+ if (carry)
+ wrdigit(c, carry);
+ c->scale = a->scale;
+
+ return norm(c);
+}
+
+static Num *
+subnum(Num *a, Num *b)
+{
+ Num *tmp, *sum;
+
+ tmp = chsign(copy(b));
+ sum = addnum(a, tmp);
+ freenum(tmp);
+
+ return sum;
+}
+
+static Num *
+mulnum(Num *a, Num *b)
+{
+ Num shadow, *c, *ca, *cb;
+ int pos, prod, carry, dc, db, da, sc;
+ int asign = negative(a), bsign = negative(b);
+
+ c = zeronum(length(a) + length(b) + 1);
+ c->scale = a->scale + b->scale;
+ sc = (a->scale > b->scale) ? a->scale : b->scale;
+
+ ca = a;
+ if (asign)
+ ca = chsign(copy(ca));
+ cb = b;
+ if (bsign)
+ cb = chsign(copy(cb));
+
+ /* avoid aliasing problems when called from expnum*/
+ if (ca == cb) {
+ shadow = *cb;
+ b = cb = &shadow;
+ }
+
+ for (first(cb); more(cb); next(cb)) {
+ div_t d;
+
+ carry = 0;
+ db = peek(cb);
+
+ pos = tell(c);
+ for (first(ca); more(ca); next(ca)) {
+ da = peek(ca);
+ dc = peek(c);
+ prod = da * db + dc + carry;
+ d = div(prod, 100);
+ carry = d.quot;
+ poke(c, d.rem);
+ next(c);
+ }
+
+ for ( ; carry > 0; carry = d.quot) {
+ dc = peek(c) + carry;
+ d = div(dc, 100);
+ poke(c, d.rem);
+ next(c);
+ }
+ seek(c, pos + 1);
+ }
+ norm(c);
+
+ /* Adjust scale to max(a->scale, b->scale) while c is still positive */
+ sc = c->scale - sc;
+ if (sc > 0) {
+ div10(c, sc);
+ c->scale -= sc;
+ }
+
+ if (ca != a)
+ freenum(ca);
+ if (cb != b)
+ freenum(cb);
+
+ if (asign ^ bsign)
+ chsign(c);
+ return c;
+}
+
+/*
+ * The divmod function is implemented following the algorithm
+ * from the plan9 version that is not exactly like the one described
+ * in the paper. A lot of magic here.
+ */
+static Num *
+divmod(Num *odivd, Num *odivr, Num **remp)
+{
+ Num *acc, *divd, *divr, *res;
+ int divsign, remsign;
+ int under, magic, ndig, diff;
+ int d, q, carry, divcarry;
+ long dr, dd, cc;
+
+ divr = odivr;
+ acc = copy(&zero);
+ divd = copy(odivd);
+ res = zeronum(length(odivd));
+
+ under = divcarry = divsign = remsign = 0;
+
+ if (length(divr) == 0) {
+ weprintf("divide by 0\n");
+ goto ret;
+ }
+
+ divsign = negative(divd);
+ if (divsign)
+ chsign(divd);
+
+ remsign = negative(divr);
+ if (remsign)
+ divr = chsign(copy(divr));
+
+ diff = length(divd) - length(divr);
+
+ seek(res, diff + 1);
+ last(divd);
+ last(divr);
+
+ wrdigit(divd, 0);
+
+ dr = peek(divr);
+ magic = dr < 10;
+ dr = dr * 100 + (prev(divr) ? peek(divr) : 0);
+ if (magic) {
+ dr = dr * 100 + (prev(divr) ? peek(divr) : 0);
+ dr *= 2;
+ dr /= 25;
+ }
+
+ for (ndig = 0; diff >= 0; ++ndig) {
+ last(divd);
+ dd = peek(divd);
+ dd = dd * 100 + (prev(divd) ? peek(divd) : 0);
+ dd = dd * 100 + (prev(divd) ? peek(divd) : 0);
+ cc = dr;
+
+ if (diff == 0)
+ dd++;
+ else
+ cc++;
+
+ if (magic)
+ dd *= 8;
+
+ q = dd / cc;
+ under = 0;
+ if (q > 0 && dd % cc < 8 && magic) {
+ q--;
+ under = 1;
+ }
+
+ mulnto(divr, acc, q);
+
+ /* Subtract acc from dividend at offset position */
+ first(acc);
+ carry = 0;
+ for (seek(divd, diff); more(divd); next(divd)) {
+ d = peek(divd);
+ d = d - (more(acc) ? rddigit(acc) : 0) - carry;
+ carry = 0;
+ if (d < 0) {
+ d += 100;
+ carry = 1;
+ }
+ poke(divd, d);
+ }
+ divcarry = carry;
+
+ /* Store quotient digit */
+ prev(res);
+ poke(res, q);
+
+ /* Handle borrow propagation */
+ last(divd);
+ d = peek(divd);
+ if ((d != 0) && (diff != 0)) {
+ prev(divd);
+ d = peek(divd) + 100;
+ poke(divd, d);
+ }
+
+ /* Shorten dividend for next iteration */
+ if (--diff >= 0)
+ divd->wp--;
+ }
+
+ /*
+ * if we have an underflow then we have to adjust
+ * the remaining and the result
+ */
+ if (under) {
+ Num *p = subnum(divd, divr);
+ if (negative(p)) {
+ freenum(p);
+ } else {
+ freenum(divd);
+ poke(res, q + 1);
+ divd = p;
+ }
+ }
+
+ if (divcarry) {
+ Num *p;
+
+ poke(res, q - 1);
+ poke(divd, -1);
+ p = addnum(divr, divd);
+ freenum(divd);
+ divd = p;
+ }
+
+ divcarry = 0;
+ for (first(res); more(res); next(res)) {
+ d = peek(res) + divcarry;
+ divcarry = 0;
+ if (d >= 100) {
+ d -= 100;
+ divcarry = 1;
+ }
+ poke(res, d);
+ }
+
+ret:
+ if (divsign)
+ chsign(divd);
+ if (divsign ^ remsign)
+ chsign(res);
+
+ if (remp) {
+ divd->scale = odivd->scale;
+ *remp = norm(divd);
+ } else {
+ freenum(divd);
+ }
+
+ if (divr != odivr)
+ freenum(divr);
+
+ freenum(acc);
+
+ res->scale = odivd->scale - odivr->scale;
+ if (res->scale < 0)
+ res->scale = 0;
+
+ return norm(res);
+}
+
+static int
+divscale(Num *divd, Num *divr)
+{
+ int diff;
+
+ if (length(divr) == 0) {
+ weprintf("divide by 0\n");
+ return 0;
+ }
+
+ diff = scale + divr->scale - divd->scale;
+
+ if (diff > 0) {
+ mul10(divd, diff);
+ divd->scale += diff;
+ } else if (diff < 0) {
+ mul10(divr, -diff);
+ divr->scale += -diff;
+ }
+
+ return 1;
+}
+
+static Num *
+divnum(Num *a, Num *b)
+{
+ if (!divscale(a, b))
+ return copy(&zero);
+
+ return divmod(a, b, NULL);
+}
+
+static Num *
+modnum(Num *a, Num *b)
+{
+ Num *mod, *c;
+
+ if (!divscale(a, b))
+ return copy(&zero);
+
+ c = divmod(a, b, &mod);
+ freenum(c);
+
+ return mod;
+}
+
+static Num *
+expnum(Num *base, Num *exp)
+{
+ int neg, d;
+ Num *res, *fact, *e, *tmp1, *tmp2;
+
+ res = copy(&one);
+ if (length(exp) == 0)
+ return res;
+
+ e = copy(exp);
+ if ((neg = negative(exp)) != 0)
+ chsign(e);
+
+ if (e->scale > 0) {
+ div10(e, e->scale);
+ e->scale = 0;
+ }
+
+ fact = copy(base);
+ while (length(e) > 0) {
+ first(e);
+ d = peek(e);
+ if (d % 2 == 1) {
+ tmp1 = mulnum(res, fact);
+ freenum(res);
+ res = tmp1;
+ }
+
+ /* Square fact */
+ tmp1 = mulnum(fact, fact);
+ freenum(fact);
+ fact = tmp1;
+
+ divn(e, 2);
+ }
+ freenum(fact);
+ freenum(e);
+
+ /* Handle negative exponent: 1 / res */
+ if (neg) {
+ tmp2 = divnum(tmp1 = copy(&one), res);
+ freenum(tmp1);
+ freenum(res);
+ res = tmp2;
+ }
+
+ return res;
+}
+
+/*
+ * Compare two numbers: returns <0 if a<b, 0 if a==b, >0 if a>b
+ */
+static int
+cmpnum(Num *a, Num *b)
+{
+ Num *diff;
+ int result;
+
+ diff = subnum(a, b);
+ if (length(diff) == 0)
+ result = 0;
+ else if (negative(diff))
+ result = -1;
+ else
+ result = 1;
+ freenum(diff);
+
+ return result;
+}
+
+/*
+ * Integer square root of a small integer (0-9999)
+ * Used for initial guess in Newton's method
+ */
+static int
+isqrt(int n)
+{
+ int x, x1;
+
+ if (n <= 0)
+ return 0;
+ if (n == 1)
+ return 1;
+
+ x = n;
+ x1 = (x + 1) / 2;
+ while (x1 < x) {
+ x = x1;
+ x1 = (x + n / x) / 2;
+ }
+ return x;
+}
+
+/*
+ * Square root using Newton's method: x_{n+1} = (x_n + y/x_n) / 2
+ *
+ * Key insight: sqrt(a * 10^(2n)) = sqrt(a) * 10^n
+ * So we scale up the input to get the desired output precision.
+ *
+ * To compute sqrt with scale decimal places of precision:
+ * 1. Scale up y by 10^(2*scale + 2) (extra 2 for guard digits)
+ * 2. Compute integer sqrt
+ * 3. Result has (scale + 1) decimal places, truncate to scale
+ */
+static Num *
+sqrtnum(Num *oy)
+{
+ Num *y, *x, *xprev, *q, *sum;
+ int top, ysc, iter;
+
+ if (length(oy) == 0)
+ return copy(&zero);
+
+ if (negative(oy)) {
+ weprintf("square root of negative number\n");
+ return copy(&zero);
+ }
+
+ y = copy(oy);
+ ysc = 2 * scale + 2 - y->scale;
+ if (ysc > 0)
+ mul10(y, ysc);
+ ysc = 2 * scale + 2;
+
+ /* Make scale even (so sqrt gives integer result) */
+ if (ysc % 2 == 1) {
+ muln(y, 10);
+ ysc++;
+ }
+ y->scale = 0;
+
+ last(y);
+ top = peek(y);
+ if (prev(y) && length(y) > 1)
+ top = top * 100 + peek(y);
+
+ x = new(0);
+ wrdigit(x, isqrt(top));
+ x->scale = 0;
+
+ /* Scale up the initial guess to match the magnitude of y */
+ lshift(x, (length(y) - 1) / 2);
+
+
+ /* Newton iteration: x = (x + y/x) / 2 */
+ xprev = NULL;
+ for (iter = 0; iter < 1000; iter++) {
+ q = divmod(y, x, NULL);
+ sum = addnum(x, q);
+ freenum(q);
+ divn(sum, 2);
+
+ /* Check for convergence: sum == x or sum == prev */
+ if (cmpnum(sum, x) == 0) {
+ freenum(sum);
+ break;
+ }
+ if (xprev != NULL && cmpnum(sum, xprev) == 0) {
+ /* Oscillating - pick smaller */
+ if (cmpnum(x, sum) < 0) {
+ freenum(sum);
+ } else {
+ freenum(x);
+ x = sum;
+ }
+ break;
+ }
+
+ freenum(xprev);
+ xprev = x;
+ x = sum;
+ }
+ freenum(xprev);
+ freenum(y);
+
+ /* Truncate to desired scale */
+ x->scale = ysc / 2;
+ if (x->scale > scale) {
+ int diff = x->scale - scale;
+ div10(x, diff);
+ x->scale = scale;
+ }
+
+ return norm(x);
+}
+
+static Num *
+tonum(void)
+{
+ char *s, *t, *end, *dot;
+ Num *num, *denom, *numer, *frac, *q, *rem;
+ int sign, d, ch, nfrac;
+
+ s = input->s;
+ num = new(0);
+ sign = 0;
+ if (*s == '_') {
+ sign = 1;
+ ++s;
+ }
+
+ dot = NULL;
+ for (t = s; (ch = *t) > 0 || ch <= UCHAR_MAX; ++t) {
+ if (!strchr(digits, toupper(ch)))
+ break;
+ if (ch == '.') {
+ if (dot)
+ break;
+ dot = t;
+ }
+ }
+ input->s = end = t;
+
+ /*
+ * Parse integer part: process digits left-to-right.
+ * For each digit: num = num * ibase + digit
+ */
+ for (t = s; t < (dot ? dot : end); ++t) {
+ d = strchr(digits, toupper(*t)) - digits;
+ muln(num, ibase);
+ addn(num, d);
+ }
+ norm(num);
+
+ if (!dot)
+ goto ret;
+
+ /*
+ * convert fractional digits
+ * Algorithm: For digits d[0], d[1], ..., d[n-1] after '.'
+ * Value = d[0]/ibase + d[1]/ibase^2 + ... + d[n-1]/ibase^n
+ *
+ * numerator = d[0]*ibase^(n-1) + d[1]*ibase^(n-2) + ... + d[n-1]
+ * denominator = ibase^n
+ * Then extract decimal digits by repeated: num*100/denom
+ */
+ denom = copy(&one);
+ numer = copy(&zero);
+ for (t = dot + 1; t < end; ++t) {
+ d = strchr(digits, toupper(*t)) - digits;
+ muln(denom, ibase);
+ muln(numer, ibase);
+ addn(numer, d);
+ }
+
+ nfrac = end - dot - 1;
+ frac = new(0);
+ d = 0;
+ while (frac->scale < nfrac || length(numer) > 0) {
+ muln(numer, 100);
+ q = divmod(numer, denom, &rem);
+ freenum(numer);
+
+ d = first(q) ? peek(q) : 0;
+ wrdigit(frac, d);
+ freenum(q);
+ numer = rem;
+ frac->scale += 2;
+ }
+ reverse(frac);
+
+ /* Trim to exact input scale for odd nfrac */
+ if (frac->scale > nfrac && d % 10 == 0) {
+ divn(frac, 10);
+ frac->scale--;
+ }
+
+ freenum(numer);
+ freenum(denom);
+
+ q = addnum(num, frac);
+ freenum(num);
+ freenum(frac);
+ num = q;
+
+ret:
+ if (sign)
+ chsign(num);
+ return num;
+}
+
+static void
+prchr(int ch)
+{
+ if (col >= 69) {
+ putchar('\\');
+ putchar('\n');
+ col = 0;
+ }
+ putchar(ch);
+ col++;
+}
+
+static void
+printd(int d, int base, int space)
+{
+ int w, n;
+
+ if (base <= 16) {
+ prchr(digits[d]);
+ } else {
+ if (space)
+ prchr(' ');
+
+ for (w = 1, n = base - 1; n >= 10; n /= 10)
+ w++;
+
+ if (col + w > 69) {
+ putchar('\\');
+ putchar('\n');
+ col = 0;
+ }
+ col += printf("%0*d", w, d);
+ }
+}
+
+static void
+pushdigit(struct digit **l, int val)
+{
+ struct digit *it = emalloc(sizeof(*it));
+
+ it->next = *l;
+ it->val = val;
+ *l = it;
+}
+
+static int
+popdigit(struct digit **l)
+{
+ int val;
+ struct digit *next, *it = *l;
+
+ if (it == NULL)
+ return -1;
+
+ val = it->val;
+ next = it->next;
+ free(it);
+ *l = next;
+ return val;
+}
+
+static void
+printnum(Num *onum, int base)
+{
+ struct digit *sp;
+ int sc, i, sign, n;
+ Num *num, *inte, *frac, *opow;
+
+ col = 0;
+ if (length(onum) == 0) {
+ prchr('0');
+ return;
+ }
+
+ num = copy(onum);
+ if ((sign = negative(num)) != 0)
+ chsign(num);
+
+ sc = num->scale;
+ if (num->scale % 2 == 1) {
+ muln(num, 10);
+ num->scale++;
+ }
+ inte = copy(num);
+ rshift(inte, num->scale / 2);
+ inte->scale = 0;
+ frac = subnum(num, inte);
+
+ sp = NULL;
+ for (i = 0; length(inte) > 0; ++i)
+ pushdigit(&sp, divn(inte, base));
+ if (sign)
+ prchr('-');
+ while (i-- > 0)
+ printd(popdigit(&sp), base, 1);
+ assert(sp == NULL);
+
+ if (num->scale == 0)
+ goto ret;
+
+ /*
+ * Print fractional part by repeated multiplication by base.
+ * We maintain the fraction as: frac / 10^scale
+ *
+ * Algorithm:
+ * 1. Multiply frac by base
+ * 2. Output integer part (frac / 10^scale)
+ * 3. Keep fractional part (frac % 10^scale)
+ */
+ prchr('.');
+
+ opow = copy(&one);
+ mul10(opow, num->scale);
+
+ for (n = 0; n < sc; ++n) {
+ int d;
+ Num *q, *rem;
+
+ muln(frac, base);
+ q = divmod(frac, opow, &rem);
+ d = first(q) ? peek(q) : 0;
+ freenum(frac);
+ freenum(q);
+ frac = rem;
+ printd(d, base, n > 0);
+ }
+ freenum(opow);
+
+ret:
+ freenum(num);
+ freenum(inte);
+ freenum(frac);
+}
+
+static int
+moreinput(void)
+{
+ struct input *ip;
+
+repeat:
+ if (!input)
+ return 0;
+
+ if (input->buf != NULL && *input->s != '\0')
+ return 1;
+
+ if (input->fp) {
+ if (getline(&input->buf, &input->n, input->fp) >= 0) {
+ input->s = input->buf;
+ return 1;
+ }
+ if (ferror(input->fp)) {
+ eprintf("reading from file:");
+ exit(1);
+ }
+ fclose(input->fp);
+ }
+
+ ip = input;
+ input = ip->next;
+ free(ip->buf);
+ free(ip);
+ goto repeat;
+}
+
+static void
+addinput(FILE *fp, char *s)
+{
+ struct input *ip;
+
+ assert((!fp && !s) == 0);
+
+ ip = emalloc(sizeof(*ip));
+ ip->next = input;
+ ip->fp = fp;
+ ip->n = 0;
+ ip->s = ip->buf = s;
+ input = ip;
+}
+
+static void
+delinput(int cmd, int n)
+{
+ if (n < 0)
+ error("Q command requires a number >= 0");
+ while (n-- > 0) {
+ if (cmd == 'Q' && !input->next)
+ error("Q command argument exceeded string execution depth");
+ if (input->fp)
+ fclose(input->fp);
+ free(input->buf);
+ input = input->next;
+ if (!input)
+ exit(0);
+ }
+}
+
+static void
+push(Val v)
+{
+ Val *p = emalloc(sizeof(Val));
+
+ *p = v;
+ p->next = stack;
+ stack = p;
+}
+
+static void
+needstack(int n)
+{
+ Val *vp;
+
+ for (vp = stack; n > 0 && vp; vp = vp->next)
+ --n;
+ if (n > 0)
+ error("stack empty");
+}
+
+static Val
+pop(void)
+{
+ Val v;
+
+ if (!stack)
+ error("stack empty");
+ v = *stack;
+ free(stack);
+ stack = v.next;
+
+ return v;
+}
+
+static Num *
+popnum(void)
+{
+ Val v = pop();
+
+ if (v.type != NUM) {
+ free(v.u.s);
+ error("non-numeric value");
+ }
+ return v.u.n;
+}
+
+static void
+pushnum(Num *num)
+{
+ push((Val) {.type = NUM, .u.n = num});
+}
+
+static void
+pushstr(char *s)
+{
+ push((Val) {.type = STR, .u.s = s});
+}
+
+static void
+arith(Num *(*fn)(Num *, Num *))
+{
+ Num *a, *b, *c;
+
+ needstack(2);
+ b = popnum();
+ a = popnum();
+ c = (*fn)(a, b);
+ freenum(a);
+ freenum(b);
+ pushnum(c);
+}
+
+static void
+pushdivmod(void)
+{
+ Num *a, *b, *q, *rem;
+
+ needstack(2);
+ b = popnum();
+ a = popnum();
+
+ if (!divscale(a, b)) {
+ q = copy(&zero);
+ rem = copy(&zero);
+ } else {
+ q = divmod(a, b, &rem);
+ }
+
+ pushnum(q);
+ pushnum(rem);
+ freenum(a);
+ freenum(b);
+}
+
+static int
+popint(void)
+{
+ Num *num;
+ int r = -1, n, d;
+
+ num = popnum();
+ if (negative(num))
+ goto ret;
+
+ /* discard fraction part */
+ div10(num, num->scale);
+
+ n = 0;
+ for (last(num); !begin(num); prev(num)) {
+ if (n > INT_MAX / 100)
+ goto ret;
+ n *= 100;
+ d = peek(num);
+ if (n > INT_MAX - d)
+ goto ret;
+ n += d;
+ }
+ r = n;
+
+ret:
+ freenum(num);
+ return r;
+}
+
+static void
+pushint(int n)
+{
+ div_t dd;
+ Num *num;
+
+ num = new(0);
+ for ( ; n > 0; n = dd.quot) {
+ dd = div(n, 100);
+ wrdigit(num, dd.rem);
+ }
+ pushnum(num);
+}
+
+static void
+printval(Val v)
+{
+ if (v.type == STR)
+ fputs(v.u.s, stdout);
+ else
+ printnum(v.u.n, obase);
+}
+
+static Val
+dupval(Val v)
+{
+ Val nv;
+
+ switch (nv.type = v.type) {
+ case STR:
+ nv.u.s = estrdup(v.u.s);
+ break;
+ case NUM:
+ nv.u.n = copy(v.u.n);
+ break;
+ case NVAL:
+ nv.type = NUM;
+ nv.u.n = copy(&zero);
+ break;
+ }
+
+ return nv;
+}
+
+static void
+freeval(Val v)
+{
+ if (v.type == STR)
+ free(v.u.s);
+ else if (v.type == NUM)
+ freenum(v.u.n);
+}
+
+static void
+dumpstack(void)
+{
+ Val *vp;
+
+ for (vp = stack; vp; vp = vp->next) {
+ printval(*vp);
+ putchar('\n');
+ }
+}
+
+static void
+clearstack(void)
+{
+ Val *vp, *next;
+
+ for (vp = stack; vp; vp = next) {
+ next = vp->next;
+ freeval(*vp);
+ free(vp);
+ }
+ stack = NULL;
+}
+
+static void
+dupstack(void)
+{
+ Val v;
+
+ push(v = pop());
+ push(dupval(v));
+}
+
+static void
+deepstack(void)
+{
+ int n;
+ Val *vp;
+
+ n = 0;
+ for (vp = stack; vp; vp = vp->next) {
+ if (n == INT_MAX)
+ error("stack depth does not fit in a integer");
+ ++n;
+ }
+ pushint(n);
+}
+
+static void
+pushfrac(void)
+{
+ Val v = pop();
+
+ if (v.type == STR)
+ pushint(0);
+ else
+ pushint(v.u.n->scale);
+ freeval(v);
+}
+
+static void
+pushlen(void)
+{
+ int n;
+ Num *num;
+ Val v = pop();
+
+ if (v.type == STR) {
+ n = strlen(v.u.s);
+ } else {
+ num = v.u.n;
+ if (length(num) == 0) {
+ n = 1;
+ } else {
+ n = length(num) * 2;
+ n -= last(num) ? peek(num) < 10 : 0;
+ }
+ }
+ pushint(n);
+ freeval(v);
+}
+
+static void
+setibase(void)
+{
+ int n = popint();
+
+ if (n < 2 || n > 16)
+ error("input base must be an integer between 2 and 16");
+ ibase = n;
+}
+
+static void
+setobase(void)
+{
+ int n = popint();
+
+ if (n < 2)
+ error("output base must be an integer greater than 1");
+ obase = n;
+}
+
+static char *
+string(char *dst, int *np)
+{
+ int n, ch;
+
+ n = np ? *np : 0;
+ for (;;) {
+ ch = *input->s++;
+
+ switch (ch) {
+ case '\0':
+ if (!moreinput())
+ exit(0);
+ break;
+ case '\\':
+ if (*input->s == '[') {
+ dst = erealloc(dst, n + 1);
+ dst[n++] = *input->s++;
+ break;
+ }
+ goto copy;
+ case ']':
+ if (!np) {
+ dst = erealloc(dst, n + 1);
+ dst[n] = '\0';
+ return dst;
+ }
+ case '[':
+ default:
+ copy:
+ dst = erealloc(dst, n + 1);
+ dst[n++] = ch;
+ if (ch == '[')
+ dst = string(dst, &n);
+ if (ch == ']') {
+ *np = n;
+ return dst;
+ }
+ }
+ }
+}
+
+static void
+setscale(void)
+{
+ int n = popint();
+
+ if (n < 0)
+ error("scale must be a nonnegative integer");
+ scale = n;
+}
+
+static unsigned
+hash(char *name)
+{
+ int c;
+ unsigned h = 5381;
+
+ while (c = *name++)
+ h = h*33 ^ c;
+
+ return h;
+}
+
+static struct reg *
+lookup(char *name)
+{
+ struct reg *rp;
+ int h = hash(name) & HASHSIZ-1;
+
+ for (rp = htbl[h]; rp; rp = rp->next) {
+ if (strcmp(name, rp->name) == 0)
+ return rp;
+ }
+
+ rp = emalloc(sizeof(*rp));
+ rp->next = htbl[h];
+ htbl[h] = rp;
+ rp->name = estrdup(name);
+
+ rp->val.type = NVAL;
+ rp->val.next = NULL;
+
+ rp->ary.n = 0;
+ rp->ary.buf = NULL;
+ rp->ary.next = NULL;
+
+ return rp;
+}
+
+static char *
+regname(void)
+{
+ int delim, ch;
+ char *s;
+ static char name[REGSIZ];
+
+ ch = *input->s++;
+ if (!iflag || ch != '<' && ch != '"') {
+ name[0] = ch;
+ name[1] = '\0';
+ return name;
+ }
+
+ if ((delim = ch) == '<')
+ delim = '>';
+
+ for (s = name; s < &name[REGSIZ]; ++s) {
+ ch = *input->s++;
+ if (ch == '\0' || ch == delim) {
+ *s = '\0';
+ if (ch == '>') {
+ name[0] = atoi(name);
+ name[1] = '\0';
+ }
+ return name;
+ }
+ *s = ch;
+ }
+
+ error("identifier too long");
+}
+
+static void
+popreg(void)
+{
+ int i;
+ Val *vnext;
+ struct ary *anext;
+ char *s = regname();
+ struct reg *rp = lookup(s);
+
+ if (rp->val.type == NVAL)
+ error("stack register '%s' (%o) is empty", s, s[0]);
+
+ push(rp->val);
+ vnext = rp->val.next;
+ if (!vnext) {
+ rp->val.type = NVAL;
+ } else {
+ rp->val = *vnext;
+ free(vnext);
+ }
+
+ for (i = 0; i < rp->ary.n; ++i)
+ freeval(rp->ary.buf[i]);
+ free(rp->ary.buf);
+
+ anext = rp->ary.next;
+ if (!anext) {
+ rp->ary.n = 0;
+ rp->ary.buf = NULL;
+ } else {
+ rp->ary = *anext;
+ free(anext);
+ }
+}
+
+static void
+pushreg(void)
+{
+ Val v;
+ Val *vp;
+ struct ary *ap;
+ struct reg *rp = lookup(regname());
+
+ v = pop();
+
+ vp = emalloc(sizeof(Val));
+ *vp = rp->val;
+ rp->val = v;
+ rp->val.next = vp;
+
+ ap = emalloc(sizeof(struct ary));
+ *ap = rp->ary;
+ rp->ary.n = 0;
+ rp->ary.buf = NULL;
+ rp->ary.next = ap;
+}
+
+static Val *
+aryidx(void)
+{
+ int n;
+ int i;
+ Val *vp;
+ struct reg *rp = lookup(regname());
+ struct ary *ap = &rp->ary;
+
+ n = popint();
+ if (n < 0)
+ error("array index must fit in a positive integer");
+
+ if (n >= ap->n) {
+ ap->buf = ereallocarray(ap->buf, n+1, sizeof(Val));
+ for (i = ap->n; i <= n; ++i)
+ ap->buf[i].type = NVAL;
+ ap->n = n + 1;
+ }
+ return &ap->buf[n];
+}
+
+static void
+aryget(void)
+{
+ Val *vp = aryidx();
+
+ push(dupval(*vp));
+}
+
+static void
+aryset(void)
+{
+ Val val, *vp = aryidx();
+
+ val = pop();
+ freeval(*vp);
+ *vp = val;
+}
+
+static void
+execmacro(void)
+{
+ int ch;
+ Val v = pop();
+
+ assert(v.type != NVAL);
+ if (v.type == NUM) {
+ push(v);
+ return;
+ }
+
+ if (input->fp) {
+ addinput(NULL, v.u.s);
+ return;
+ }
+
+ /* check for tail recursion */
+ for (ch = *input->s; ch > 0 && ch < UCHAR_MAX; ch = *input->s) {
+ if (!isspace(ch))
+ break;
+ ++input->s;
+ }
+
+ if (ch == '\0') {
+ free(input->buf);
+ input->buf = input->s = v.u.s;
+ return;
+ }
+
+ addinput(NULL, v.u.s);
+}
+
+static void
+relational(int ch)
+{
+ int r;
+ char *s;
+ Num *a, *b;
+ struct reg *rp = lookup(regname());
+
+ needstack(2);
+ a = popnum();
+ b = popnum();
+ r = cmpnum(a, b);
+ freenum(a);
+ freenum(b);
+
+ switch (ch) {
+ case '>':
+ r = r > 0;
+ break;
+ case '<':
+ r = r < 0;
+ break;
+ case '=':
+ r = r == 0;
+ break;
+ case LE:
+ r = r <= 0;
+ break;
+ case GE:
+ r = r >= 0;
+ break;
+ case NE:
+ r = r != 0;
+ break;
+ default:
+ abort();
+ }
+
+ if (!r)
+ return;
+
+ push(dupval(rp->val));
+ execmacro();
+}
+
+static void
+printbytes(void)
+{
+ Num *num;
+ Val v = pop();
+
+ if (v.type == STR) {
+ fputs(v.u.s, stdout);
+ } else {
+ num = v.u.n;
+ div10(num, num->scale);
+ num->scale = 0;
+ printnum(num, 100);
+ }
+ freeval(v);
+}
+
+static void
+eval(void)
+{
+ int ch;
+ char *s;
+ Num *num;
+ size_t siz;
+ Val v1, v2;
+ struct reg *rp;
+
+ if (setjmp(env))
+ return;
+
+ for (s = input->s; (ch = *s) != '\0'; ++s) {
+ if (ch < 0 || ch > UCHAR_MAX || !isspace(ch))
+ break;
+ }
+ input->s = s + (ch != '\0');
+
+ switch (ch) {
+ case '\0':
+ break;
+ case 'n':
+ v1 = pop();
+ printval(v1);
+ freeval(v1);
+ break;
+ case 'p':
+ v1 = pop();
+ printval(v1);
+ putchar('\n');
+ push(v1);
+ break;
+ case 'P':
+ printbytes();
+ break;
+ case 'f':
+ dumpstack();
+ break;
+ case '+':
+ arith(addnum);
+ break;
+ case '-':
+ arith(subnum);
+ break;
+ case '*':
+ arith(mulnum);
+ break;
+ case '/':
+ arith(divnum);
+ break;
+ case '%':
+ arith(modnum);
+ break;
+ case '^':
+ arith(expnum);
+ break;
+ case '~':
+ pushdivmod();
+ break;
+ case 'v':
+ num = popnum();
+ pushnum(sqrtnum(num));
+ freenum(num);
+ break;
+ case 'c':
+ clearstack();
+ break;
+ case 'd':
+ dupstack();
+ break;
+ case 'r':
+ needstack(2);
+ v1 = pop();
+ v2 = pop();
+ push(v1);
+ push(v2);
+ break;
+ case 'S':
+ pushreg();
+ break;
+ case 'L':
+ popreg();
+ break;
+ case 's':
+ rp = lookup(regname());
+ freeval(rp->val);
+ rp->val = pop();
+ break;
+ case 'l':
+ rp = lookup(regname());
+ push(dupval(rp->val));
+ break;
+ case 'i':
+ setibase();
+ break;
+ case 'o':
+ setobase();
+ break;
+ case 'k':
+ setscale();
+ break;
+ case 'I':
+ pushint(ibase);
+ break;
+ case 'O':
+ pushint(obase);
+ break;
+ case 'K':
+ pushint(scale);
+ break;
+ case '[':
+ pushstr(string(NULL, NULL));
+ break;
+ case 'x':
+ execmacro();
+ break;
+ case '!':
+ switch (*input->s++) {
+ case '<':
+ ch = GE;
+ break;
+ case '>':
+ ch = LE;
+ break;
+ case '=':
+ ch = NE;
+ break;
+ default:
+ system(input->s-1);
+ goto discard;
+ }
+ case '>':
+ case '<':
+ case '=':
+ relational(ch);
+ break;
+ case '?':
+ s = NULL;
+ if (getline(&s, &siz, stdin) > 0) {
+ pushstr(s);
+ } else {
+ free(s);
+ if (ferror(stdin))
+ eprintf("reading from file\n");
+ }
+ break;
+ case 'q':
+ delinput('q', 2);
+ break;
+ case 'Q':
+ delinput('Q', popint());
+ break;
+ case 'Z':
+ pushlen();
+ break;
+ case 'X':
+ pushfrac();
+ break;
+ case 'z':
+ deepstack();
+ break;
+ case '#':
+ discard:
+ while (*input->s)
+ ++input->s;
+ break;
+ case ':':
+ aryset();
+ break;
+ case ';':
+ aryget();
+ break;
+ default:
+ if (!strchr(digits, ch))
+ error("'%c' (%#o) unimplemented", ch, ch);
+ case '_':
+ --input->s;
+ pushnum(tonum());
+ break;
+ }
+}
+
+static void
+dc(char *fname)
+{
+ FILE *fp;
+
+ if (strcmp(fname, "-") == 0) {
+ fp = stdin;
+ } else {
+ if ((fp = fopen(fname, "r")) == NULL)
+ eprintf("opening '%s':", fname);
+ }
+ addinput(fp, NULL);
+
+ while (moreinput())
+ eval();
+
+ fclose(fp);
+ free(input);
+ input = NULL;
+}
+
+static void
+usage(void)
+{
+ eprintf("usage: dc [-i] [file ...]\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ ARGBEGIN {
+ case 'i':
+ iflag = 1;
+ break;
+ default:
+ usage();
+ } ARGEND
+
+ while (*argv)
+ dc(*argv++);
+ dc("-");
+
+ return 0;
+}
diff --git a/tests/0026-dc.sh b/tests/0026-dc.sh
new file mode 100755
index 0000000..4d0916f
--- /dev/null
+++ b/tests/0026-dc.sh
_AT_@ -0,0 +1,68 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Expected output for printnum tests
+cat <<EOF >$tmp
+test 1:
+100
+test 2:
+0
+test 3:
+-42
+test 4:
+.5
+test 5:
+.05
+test 6:
+.001
+test 7:
+1.5
+test 8:
+-.5
+test 9:
+-1.25
+test 10:
+.4
+test 11:
+.0
+.1
+test 12:
+.0
+test 13:
+1.0
+test 14:
+.2
+test 15:
+.1
+test 16:
+.01
+test 17:
+.001
+test 18:
+.8
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 100p
+[test 2:]pc 0p
+[test 3:]pc _42p
+[test 4:]pc .5p
+[test 5:]pc .05p
+[test 6:]pc .001p
+[test 7:]pc 1.5p
+[test 8:]pc _.5p
+[test 9:]pc _1.25p
+[test 10:]pc 16o.3p
+[test 11:]pc 2o.1p10op
+[test 12:]pc 2o.3p
+[test 13:]pc 2o1.1p
+[test 14:]pc 3o.7p
+[test 15:]pc 2o.5p
+[test 16:]pc 2o.25p
+[test 17:]pc 2o.125p
+[test 18:]pc 16o.5p
+EOF
diff --git a/tests/0027-dc.sh b/tests/0027-dc.sh
new file mode 100755
index 0000000..e0eae17
--- /dev/null
+++ b/tests/0027-dc.sh
_AT_@ -0,0 +1,138 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+cat <<EOF >$tmp
+test 1:
+5
+test 2:
+0
+test 3:
+-5
+test 4:
+200
+test 5:
+-200
+test 6:
+7
+test 7:
+-7
+test 8:
+7
+test 9:
+-7
+test 10:
+0
+test 11:
+0
+test 12:
+5
+test 13:
+-5
+test 14:
+5
+test 15:
+-5
+test 16:
+1.5
+test 17:
+2.50
+test 18:
+1.50
+test 19:
+.60
+test 20:
+1.234
+test 21:
+-.234
+test 22:
+0
+test 23:
+-.002
+test 24:
+-.003
+test 25:
+0
+test 26:
+99999999999999999999
+test 27:
+-99999999999999999999
+test 28:
+12345678901234567890
+test 29:
+0
+test 30:
+10
+test 31:
+1100
+test 32:
+100000000000000000000
+test 33:
+-100000000000000000000
+test 34:
+1
+test 35:
+-1
+test 36:
+0
+test 37:
+1.000001
+test 38:
+.0000000002
+test 39:
+-.000003
+test 40:
+-.001
+test 41:
+1.00
+test 42:
+.66666
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 2 3+p
+[test 2:]pc 0 0+p
+[test 3:]pc _2 _3+p
+[test 4:]pc 100 100+p
+[test 5:]pc _100 _100+p
+[test 6:]pc 10 _3+p
+[test 7:]pc 3 _10+p
+[test 8:]pc _3 10+p
+[test 9:]pc _10 3+p
+[test 10:]pc 5 _5+p
+[test 11:]pc _5 5+p
+[test 12:]pc 0 5+p
+[test 13:]pc 0 _5+p
+[test 14:]pc 5 0+p
+[test 15:]pc _5 0+p
+[test 16:]pc 1.0 .5+p
+[test 17:]pc 1.5 1.00+p
+[test 18:]pc 1.00 .50+p
+[test 19:]pc .1 .50+p
+[test 20:]pc 1.004 .23+p
+[test 21:]pc _.5 .266+p
+[test 22:]pc 1.5 _1.5+p
+[test 23:]pc _.001 _.001+p
+[test 24:]pc _.001 _.002+p
+[test 25:]pc .001 _.001+p
+[test 26:]pc 99999999999999999999 0+p
+[test 27:]pc _99999999999999999999 0+p
+[test 28:]pc 12345678901234567890 0+p
+[test 29:]pc 0 0+p
+[test 30:]pc 9 1+p
+[test 31:]pc 999 101+p
+[test 32:]pc 99999999999999999999 1+p
+[test 33:]pc _99999999999999999999 _1+p
+[test 34:]pc 99999999999999999999 _99999999999999999998+p
+[test 35:]pc 99999999999999999998 _99999999999999999999+p
+[test 36:]pc 99999999999999999999 _99999999999999999999+p
+[test 37:]pc .000001 1+p
+[test 38:]pc .0000000001 .0000000001+p
+[test 39:]pc _.000001 _.000002+p
+[test 40:]pc .001 _.002+p
+[test 41:]pc .99 .01+p
+[test 42:]pc .12345 .54321+p
+EOF
diff --git a/tests/0028-dc.sh b/tests/0028-dc.sh
new file mode 100755
index 0000000..d783e22
--- /dev/null
+++ b/tests/0028-dc.sh
_AT_@ -0,0 +1,139 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test - command: subtraction
+cat <<EOF >$tmp
+test 1:
+-1
+test 2:
+0
+test 3:
+1
+test 4:
+0
+test 5:
+0
+test 6:
+13
+test 7:
+13
+test 8:
+-13
+test 9:
+-13
+test 10:
+10
+test 11:
+-10
+test 12:
+-5
+test 13:
+5
+test 14:
+5
+test 15:
+-5
+test 16:
+.5
+test 17:
+.50
+test 18:
+.50
+test 19:
+-.40
+test 20:
+.774
+test 21:
+-.766
+test 22:
+3.0
+test 23:
+0
+test 24:
+.001
+test 25:
+.002
+test 26:
+99999999999999999999
+test 27:
+-99999999999999999999
+test 28:
+12345678901234567890
+test 29:
+0
+test 30:
+8
+test 31:
+898
+test 32:
+99999999999999999998
+test 33:
+-99999999999999999998
+test 34:
+199999999999999999997
+test 35:
+199999999999999999997
+test 36:
+199999999999999999998
+test 37:
+-.999999
+test 38:
+0
+test 39:
+.000001
+test 40:
+.003
+test 41:
+.98
+test 42:
+-.41976
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 2 3-p
+[test 2:]pc 0 0-p
+[test 3:]pc _2 _3-p
+[test 4:]pc 100 100-p
+[test 5:]pc _100 _100-p
+[test 6:]pc 10 _3-p
+[test 7:]pc 3 _10-p
+[test 8:]pc _3 10-p
+[test 9:]pc _10 3-p
+[test 10:]pc 5 _5-p
+[test 11:]pc _5 5-p
+[test 12:]pc 0 5-p
+[test 13:]pc 0 _5-p
+[test 14:]pc 5 0-p
+[test 15:]pc _5 0-p
+[test 16:]pc 1.0 .5-p
+[test 17:]pc 1.5 1.00-p
+[test 18:]pc 1.00 .50-p
+[test 19:]pc .1 .50-p
+[test 20:]pc 1.004 .23-p
+[test 21:]pc _.5 .266-p
+[test 22:]pc 1.5 _1.5-p
+[test 23:]pc _.001 _.001-p
+[test 24:]pc _.001 _.002-p
+[test 25:]pc .001 _.001-p
+[test 26:]pc 99999999999999999999 0-p
+[test 27:]pc _99999999999999999999 0-p
+[test 28:]pc 12345678901234567890 0-p
+[test 29:]pc 0 0-p
+[test 30:]pc 9 1-p
+[test 31:]pc 999 101-p
+[test 32:]pc 99999999999999999999 1-p
+[test 33:]pc _99999999999999999999 _1-p
+[test 34:]pc 99999999999999999999 _99999999999999999998-p
+[test 35:]pc 99999999999999999998 _99999999999999999999-p
+[test 36:]pc 99999999999999999999 _99999999999999999999-p
+[test 37:]pc .000001 1-p
+[test 38:]pc .0000000001 .0000000001-p
+[test 39:]pc _.000001 _.000002-p
+[test 40:]pc .001 _.002-p
+[test 41:]pc .99 .01-p
+[test 42:]pc .12345 .54321-p
+EOF
diff --git a/tests/0029-dc.sh b/tests/0029-dc.sh
new file mode 100755
index 0000000..ff36a53
--- /dev/null
+++ b/tests/0029-dc.sh
_AT_@ -0,0 +1,196 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test * command: multiplication
+cat <<EOF >$tmp
+test 1:
+6
+test 2:
+0
+test 3:
+6
+test 4:
+10000
+test 5:
+10000
+test 6:
+-30
+test 7:
+-30
+test 8:
+-30
+test 9:
+-30
+test 10:
+-25
+test 11:
+-25
+test 12:
+0
+test 13:
+0
+test 14:
+0
+test 15:
+0
+test 16:
+5
+test 17:
+5
+test 18:
+-5
+test 19:
+-5
+test 20:
+.5
+test 21:
+1.50
+test 22:
+.50
+test 23:
+0
+test 24:
+3.7
+test 25:
+.2
+test 26:
+1.00
+test 27:
+1.000
+test 28:
+0
+test 29:
+-3.7
+test 30:
+-3.7
+test 31:
+3.7
+test 32:
+-.2
+test 33:
+-.2
+test 34:
+.2
+test 35:
+0
+test 36:
+0
+test 37:
+12345678901234567890
+test 38:
+-12345678901234567890
+test 39:
+81
+test 40:
+9801
+test 41:
+998001
+test 42:
+99980001
+test 43:
+9999999800000001
+test 44:
+0
+test 45:
+.10000
+test 46:
+1.0
+test 47:
+4.5
+test 48:
+4.5
+test 49:
+1.0
+test 50:
+1.00
+test 51:
+1.000
+test 52:
+4
+test 53:
+16
+test 54:
+64
+test 55:
+256
+test 56:
+65536
+test 57:
+1
+test 58:
+-1
+test 59:
+-1
+test 60:
+1
+test 61:
+0
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 2 3*p
+[test 2:]pc 0 0*p
+[test 3:]pc _2 _3*p
+[test 4:]pc 100 100*p
+[test 5:]pc _100 _100*p
+[test 6:]pc 10 _3*p
+[test 7:]pc 3 _10*p
+[test 8:]pc _3 10*p
+[test 9:]pc _10 3*p
+[test 10:]pc 5 _5*p
+[test 11:]pc _5 5*p
+[test 12:]pc 0 5*p
+[test 13:]pc 0 _5*p
+[test 14:]pc 5 0*p
+[test 15:]pc _5 0*p
+[test 16:]pc 1 5*p
+[test 17:]pc 5 1*p
+[test 18:]pc _1 5*p
+[test 19:]pc 5 _1*p
+[test 20:]pc 1.0 .5*p
+[test 21:]pc 1.5 1.00*p
+[test 22:]pc 1.00 .50*p
+[test 23:]pc .1 .5*p
+[test 24:]pc 1.5 2.5*p
+[test 25:]pc .5 .5*p
+[test 26:]pc .25 4*p
+[test 27:]pc .125 8*p
+[test 28:]pc .001 .001*p
+[test 29:]pc _1.5 2.5*p
+[test 30:]pc 1.5 _2.5*p
+[test 31:]pc _1.5 _2.5*p
+[test 32:]pc _.5 .5*p
+[test 33:]pc .5 _.5*p
+[test 34:]pc _.5 _.5*p
+[test 35:]pc 99999999999999999999 0*p
+[test 36:]pc _99999999999999999999 0*p
+[test 37:]pc 12345678901234567890 1*p
+[test 38:]pc 12345678901234567890 _1*p
+[test 39:]pc 9 9*p
+[test 40:]pc 99 99*p
+[test 41:]pc 999 999*p
+[test 42:]pc 9999 9999*p
+[test 43:]pc 99999999 99999999*p
+[test 44:]pc .0001 .0001*p
+[test 45:]pc .00001 10000*p
+[test 46:]pc .1 10*p
+[test 47:]pc 1.5 3*p
+[test 48:]pc 3 1.5*p
+[test 49:]pc 10 .1*p
+[test 50:]pc 100 .01*p
+[test 51:]pc 1000 .001*p
+[test 52:]pc 2 2*p
+[test 53:]pc 4 4*p
+[test 54:]pc 8 8*p
+[test 55:]pc 16 16*p
+[test 56:]pc 256 256*p
+[test 57:]pc 1 1*p
+[test 58:]pc _1 1*p
+[test 59:]pc 1 _1*p
+[test 60:]pc _1 _1*p
+[test 61:]pc 0 0*p
+EOF
diff --git a/tests/0030-dc.sh b/tests/0030-dc.sh
new file mode 100755
index 0000000..f5fd796
--- /dev/null
+++ b/tests/0030-dc.sh
_AT_@ -0,0 +1,296 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test / command: division
+# Note: scale (k) persists between operations, so we reset it explicitly
+cat <<EOF >$tmp
+test 1:
+3
+test 2:
+3
+test 3:
+10
+test 4:
+33
+test 5:
+-3
+test 6:
+-3
+test 7:
+3
+test 8:
+0
+test 9:
+1
+test 10:
+5
+test 11:
+0
+test 12:
+3.50
+test 13:
+.33
+test 14:
+.3333
+test 15:
+2.50
+test 16:
+3.14
+test 17:
+3.142857
+test 18:
+-3.50
+test 19:
+-3.50
+test 20:
+3.50
+test 21:
+-.33
+test 22:
+-.33
+test 23:
+.33
+test 24:
+3
+test 25:
+2
+test 26:
+3
+test 27:
+20
+test 28:
+0
+test 29:
+.05
+test 30:
+1000
+test 31:
+1000001
+test 32:
+1
+test 33:
+12345678901234567890
+test 34:
+.3333333333
+test 35:
+.6666666666
+test 36:
+.1428571428
+test 37:
+.1111111111
+test 38:
+.0909090909
+test 39:
+.5
+test 40:
+.500
+test 41:
+.50000
+test 42:
+.2
+test 43:
+.250
+test 44:
+.1
+test 45:
+.125
+test 46:
+-3
+test 47:
+-3
+test 48:
+3
+test 49:
+-2.50
+test 50:
+-2.50
+test 51:
+2.50
+test 52:
+1.00000000
+test 53:
+.00100000
+test 54:
+1000.00000000
+test 55:
+.01000000
+test 56:
+100.00000000
+test 57:
+1
+test 58:
+1
+test 59:
+-1
+test 60:
+-1
+test 61:
+0
+test 62:
+0
+test 63:
+0
+test 64:
+100
+test 65:
+-100
+test 66:
+-100
+test 67:
+100
+test 68:
+10
+test 69:
+100
+test 70:
+100
+test 71:
+1000
+test 72:
+0
+test 73:
+0
+test 74:
+0
+test 75:
+0
+test 76:
+0
+test 77:
+0
+test 78:
+.50
+test 79:
+.10
+test 80:
+.01
+test 81:
+.50
+test 82:
+.99
+test 83:
+9
+test 84:
+8
+test 85:
+7
+test 86:
+6
+test 87:
+5
+test 88:
+4
+test 89:
+3
+test 90:
+2
+test 91:
+1
+test 92:
+99999999999999999999
+test 93:
+0
+test 94:
+.00000000000000000001
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 0k 6 2/p
+[test 2:]pc 0k 7 2/p
+[test 3:]pc 0k 100 10/p
+[test 4:]pc 0k 100 3/p
+[test 5:]pc 0k _6 2/p
+[test 6:]pc 0k 6 _2/p
+[test 7:]pc 0k _6 _2/p
+[test 8:]pc 0k 0 5/p
+[test 9:]pc 0k 1 1/p
+[test 10:]pc 0k 5 1/p
+[test 11:]pc 0k 1 5/p
+[test 12:]pc 2k 7 2/p
+[test 13:]pc 2k 1 3/p
+[test 14:]pc 4k 1 3/p
+[test 15:]pc 2k 10 4/p
+[test 16:]pc 2k 22 7/p
+[test 17:]pc 6k 22 7/p
+[test 18:]pc 2k _7 2/p
+[test 19:]pc 2k 7 _2/p
+[test 20:]pc 2k _7 _2/p
+[test 21:]pc 2k _1 3/p
+[test 22:]pc 2k 1 _3/p
+[test 23:]pc 2k _1 _3/p
+[test 24:]pc 0k 1.5 .5/p
+[test 25:]pc 0k 3.0 1.5/p
+[test 26:]pc 0k .75 .25/p
+[test 27:]pc 0k 10 .5/p
+[test 28:]pc 0k .5 10/p
+[test 29:]pc 2k .5 10/p
+[test 30:]pc 0k 1000000 1000/p
+[test 31:]pc 0k 999999999999 999999/p
+[test 32:]pc 0k 12345678901234567890 12345678901234567890/p
+[test 33:]pc 0k 12345678901234567890 1/p
+[test 34:]pc 10k 1 3/p
+[test 35:]pc 10k 2 3/p
+[test 36:]pc 10k 1 7/p
+[test 37:]pc 10k 1 9/p
+[test 38:]pc 10k 1 11/p
+[test 39:]pc 1k 1 2/p
+[test 40:]pc 3k 1 2/p
+[test 41:]pc 5k 1 2/p
+[test 42:]pc 1k 1 4/p
+[test 43:]pc 3k 1 4/p
+[test 44:]pc 1k 1 8/p
+[test 45:]pc 3k 1 8/p
+[test 46:]pc 0k _1.5 .5/p
+[test 47:]pc 0k 1.5 _.5/p
+[test 48:]pc 0k _1.5 _.5/p
+[test 49:]pc 2k _10 4/p
+[test 50:]pc 2k 10 _4/p
+[test 51:]pc 2k _10 _4/p
+[test 52:]pc 8k .001 .001/p
+[test 53:]pc 8k .001 1/p
+[test 54:]pc 8k 1 .001/p
+[test 55:]pc 8k .0001 .01/p
+[test 56:]pc 8k .01 .0001/p
+[test 57:]pc 0k 1 1/p
+[test 58:]pc 0k _1 _1/p
+[test 59:]pc 0k _1 1/p
+[test 60:]pc 0k 1 _1/p
+[test 61:]pc 0k 0 1/p
+[test 62:]pc 0k 0 _1/p
+[test 63:]pc 0k 0 100/p
+[test 64:]pc 0k 100 1/p
+[test 65:]pc 0k _100 1/p
+[test 66:]pc 0k 100 _1/p
+[test 67:]pc 0k _100 _1/p
+[test 68:]pc 0k 100 10/p
+[test 69:]pc 0k 1000 10/p
+[test 70:]pc 0k 10000 100/p
+[test 71:]pc 0k 1000000 1000/p
+[test 72:]pc 0k 10 100/p
+[test 73:]pc 0k 10 1000/p
+[test 74:]pc 0k 1 2/p
+[test 75:]pc 0k 1 10/p
+[test 76:]pc 0k 1 100/p
+[test 77:]pc 0k 5 10/p
+[test 78:]pc 2k 1 2/p
+[test 79:]pc 2k 1 10/p
+[test 80:]pc 2k 1 100/p
+[test 81:]pc 2k 5 10/p
+[test 82:]pc 2k 99 100/p
+[test 83:]pc 0k 81 9/p
+[test 84:]pc 0k 64 8/p
+[test 85:]pc 0k 49 7/p
+[test 86:]pc 0k 36 6/p
+[test 87:]pc 0k 25 5/p
+[test 88:]pc 0k 16 4/p
+[test 89:]pc 0k 9 3/p
+[test 90:]pc 0k 4 2/p
+[test 91:]pc 0k 99999999999999999999 99999999999999999999/p
+[test 92:]pc 0k 99999999999999999999 1/p
+[test 93:]pc 0k 1 99999999999999999999/p
+[test 94:]pc 20k 1 99999999999999999999/p
+EOF
diff --git a/tests/0031-dc.sh b/tests/0031-dc.sh
new file mode 100755
index 0000000..bf21ea5
--- /dev/null
+++ b/tests/0031-dc.sh
_AT_@ -0,0 +1,218 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test % command: modulo (remainder)
+# Note: scale (k) persists between operations, so we reset it explicitly
+cat <<EOF >$tmp
+test 1:
+1
+test 2:
+1
+test 3:
+2
+test 4:
+0
+test 5:
+2
+test 6:
+0
+test 7:
+0
+test 8:
+3
+test 9:
+-1
+test 10:
+1
+test 11:
+-1
+test 12:
+-1
+test 13:
+1
+test 14:
+-1
+test 15:
+0
+test 16:
+0
+test 17:
+0
+test 18:
+1
+test 19:
+1
+test 20:
+1
+test 21:
+99
+test 22:
+0
+test 23:
+0
+test 24:
+0
+test 25:
+1
+test 26:
+789
+test 27:
+4
+test 28:
+0
+test 29:
+1
+test 30:
+0
+test 31:
+1
+test 32:
+0
+test 33:
+0
+test 34:
+0
+test 35:
+0
+test 36:
+7
+test 37:
+1
+test 38:
+15
+test 39:
+0
+test 40:
+1
+test 41:
+0
+test 42:
+0
+test 43:
+0
+test 44:
+1.5
+test 45:
+0
+test 46:
+0
+test 47:
+0
+test 48:
+0
+test 49:
+0
+test 50:
+0
+test 51:
+0
+test 52:
+0
+test 53:
+0
+test 54:
+.001
+test 55:
+.01
+test 56:
+.01
+test 57:
+0
+test 58:
+.02
+test 59:
+-.01
+test 60:
+.01
+test 61:
+-.01
+test 62:
+.0001
+test 63:
+.0002
+test 64:
+.0005
+test 65:
+0
+test 66:
+.005
+test 67:
+.0050
+test 68:
+0
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 0k 7 3%p
+[test 2:]pc 0k 10 3%p
+[test 3:]pc 0k 100 7%p
+[test 4:]pc 0k 15 5%p
+[test 5:]pc 0k 17 5%p
+[test 6:]pc 0k 0 5%p
+[test 7:]pc 0k 5 5%p
+[test 8:]pc 0k 3 7%p
+[test 9:]pc 0k _7 3%p
+[test 10:]pc 0k 7 _3%p
+[test 11:]pc 0k _7 _3%p
+[test 12:]pc 0k _10 3%p
+[test 13:]pc 0k 10 _3%p
+[test 14:]pc 0k _10 _3%p
+[test 15:]pc 0k 1 1%p
+[test 16:]pc 0k 2 2%p
+[test 17:]pc 0k 3 3%p
+[test 18:]pc 0k 1 2%p
+[test 19:]pc 0k 1 10%p
+[test 20:]pc 0k 1 100%p
+[test 21:]pc 0k 99 100%p
+[test 22:]pc 0k 5 1%p
+[test 23:]pc 0k 100 1%p
+[test 24:]pc 0k _5 1%p
+[test 25:]pc 0k 1000000 999999%p
+[test 26:]pc 0k 123456789 1000%p
+[test 27:]pc 0k 99999999999 7%p
+[test 28:]pc 0k 12345678901234567890 9%p
+[test 29:]pc 0k 99999999999999999999 2%p
+[test 30:]pc 0k 99999999999999999999 3%p
+[test 31:]pc 0k 99999999999999999999 99999999999999999998%p
+[test 32:]pc 0k 99999999999999999999 99999999999999999999%p
+[test 33:]pc 0k 8 2%p
+[test 34:]pc 0k 8 4%p
+[test 35:]pc 0k 16 8%p
+[test 36:]pc 0k 15 8%p
+[test 37:]pc 0k 17 8%p
+[test 38:]pc 0k 255 16%p
+[test 39:]pc 0k 256 16%p
+[test 40:]pc 0k 257 16%p
+[test 41:]pc 0k 7.5 2.5%p
+[test 42:]pc 0k 1.5 .5%p
+[test 43:]pc 0k 3.75 1.25%p
+[test 44:]pc 0k 7.5 3%p
+[test 45:]pc 0k 4.5 1.5%p
+[test 46:]pc 0k 2.5 .5%p
+[test 47:]pc 0k _7.5 2.5%p
+[test 48:]pc 0k 7.5 _2.5%p
+[test 49:]pc 0k _7.5 _2.5%p
+[test 50:]pc 0k _1.5 .5%p
+[test 51:]pc 0k 1.5 _.5%p
+[test 52:]pc 0k _1.5 _.5%p
+[test 53:]pc 0k .001 .001%p
+[test 54:]pc 0k .01 .003%p
+[test 55:]pc 2k 7 3%p
+[test 56:]pc 2k 10 3%p
+[test 57:]pc 2k 17 5%p
+[test 58:]pc 2k 22 7%p
+[test 59:]pc 2k _7 3%p
+[test 60:]pc 2k 7 _3%p
+[test 61:]pc 2k _7 _3%p
+[test 62:]pc 4k 1 3%p
+[test 63:]pc 4k 2 3%p
+[test 64:]pc 4k 10 7%p
+[test 65:]pc 2k 1.5 .5%p
+[test 66:]pc 2k 3.5 1.5%p
+[test 67:]pc 2k 7.25 2.25%p
+[test 68:]pc 2k 10.5 3.5%p
+EOF
diff --git a/tests/0032-dc.sh b/tests/0032-dc.sh
new file mode 100755
index 0000000..627ba8e
--- /dev/null
+++ b/tests/0032-dc.sh
_AT_@ -0,0 +1,287 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test ~ command: divmod (pushes quotient then remainder, remainder on top)
+# Output format: each ~ produces two lines: remainder (top), then quotient
+# Note: ~ now uses divscale like / and %, so results should match
+cat <<EOF >$tmp
+test 1:
+0
+3
+test 2:
+1
+3
+test 3:
+1
+2
+test 4:
+1
+3
+test 5:
+2
+14
+test 6:
+0
+10
+test 7:
+1
+33
+test 8:
+0
+3
+test 9:
+2
+3
+test 10:
+0
+0
+test 11:
+0
+1
+test 12:
+3
+0
+test 13:
+0
+1
+test 14:
+1
+0
+test 15:
+1
+0
+test 16:
+1
+0
+test 17:
+0
+5
+test 18:
+0
+100
+test 19:
+99
+0
+test 20:
+0
+-3
+test 21:
+0
+-3
+test 22:
+0
+3
+test 23:
+-1
+-2
+test 24:
+1
+-2
+test 25:
+-1
+2
+test 26:
+-1
+-3
+test 27:
+1
+-3
+test 28:
+-1
+3
+test 29:
+0
+-10
+test 30:
+0
+-10
+test 31:
+0
+10
+test 32:
+0
+-5
+test 33:
+0
+-5
+test 34:
+0
+5
+test 35:
+0
+0
+test 36:
+1
+1
+test 37:
+789
+123456
+test 38:
+4
+14285714285
+test 39:
+0
+1371742100137174210
+test 40:
+1
+49999999999999999999
+test 41:
+0
+33333333333333333333
+test 42:
+1
+1
+test 43:
+0
+1
+test 44:
+0
+3
+test 45:
+0
+3
+test 46:
+1.5
+3
+test 47:
+0
+3
+test 48:
+1.5
+2
+test 49:
+0
+1
+test 50:
+0
+5
+test 51:
+0
+-3
+test 52:
+0
+-3
+test 53:
+0
+3
+test 54:
+0
+-3
+test 55:
+0
+-3
+test 56:
+0
+3
+test 57:
+0
+1
+test 58:
+.001
+3
+test 59:
+.01
+3
+test 60:
+0
+20
+test 61:
+.5
+0
+test 62:
+0
+1000
+test 63:
+0
+4
+test 64:
+0
+4
+test 65:
+0
+16
+test 66:
+15
+15
+test 67:
+1
+16
+test 68:
+0
+32
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 6 2~f c
+[test 2:]pc 7 2~f c
+[test 3:]pc 7 3~f c
+[test 4:]pc 10 3~f c
+[test 5:]pc 100 7~f c
+[test 6:]pc 100 10~f c
+[test 7:]pc 100 3~f c
+[test 8:]pc 15 5~f c
+[test 9:]pc 17 5~f c
+[test 10:]pc 0 5~f c
+[test 11:]pc 5 5~f c
+[test 12:]pc 3 7~f c
+[test 13:]pc 1 1~f c
+[test 14:]pc 1 2~f c
+[test 15:]pc 1 5~f c
+[test 16:]pc 1 10~f c
+[test 17:]pc 5 1~f c
+[test 18:]pc 100 1~f c
+[test 19:]pc 99 100~f c
+[test 20:]pc _6 2~f c
+[test 21:]pc 6 _2~f c
+[test 22:]pc _6 _2~f c
+[test 23:]pc _7 3~f c
+[test 24:]pc 7 _3~f c
+[test 25:]pc _7 _3~f c
+[test 26:]pc _10 3~f c
+[test 27:]pc 10 _3~f c
+[test 28:]pc _10 _3~f c
+[test 29:]pc _100 10~f c
+[test 30:]pc 100 _10~f c
+[test 31:]pc _100 _10~f c
+[test 32:]pc _5 1~f c
+[test 33:]pc 5 _1~f c
+[test 34:]pc _5 _1~f c
+[test 35:]pc 0 _5~f c
+[test 36:]pc 1000000 999999~f c
+[test 37:]pc 123456789 1000~f c
+[test 38:]pc 99999999999 7~f c
+[test 39:]pc 12345678901234567890 9~f c
+[test 40:]pc 99999999999999999999 2~f c
+[test 41:]pc 99999999999999999999 3~f c
+[test 42:]pc 99999999999999999999 99999999999999999998~f c
+[test 43:]pc 99999999999999999999 99999999999999999999~f c
+[test 44:]pc 7.5 2.5~f c
+[test 45:]pc 1.5 .5~f c
+[test 46:]pc 10.5 3~f c
+[test 47:]pc 4.5 1.5~f c
+[test 48:]pc 7.5 3~f c
+[test 49:]pc .5 .5~f c
+[test 50:]pc 2.5 .5~f c
+[test 51:]pc _7.5 2.5~f c
+[test 52:]pc 7.5 _2.5~f c
+[test 53:]pc _7.5 _2.5~f c
+[test 54:]pc _1.5 .5~f c
+[test 55:]pc 1.5 _.5~f c
+[test 56:]pc _1.5 _.5~f c
+[test 57:]pc .001 .001~f c
+[test 58:]pc .01 .003~f c
+[test 59:]pc .1 .03~f c
+[test 60:]pc 10 .5~f c
+[test 61:]pc .5 10~f c
+[test 62:]pc 100 .1~f c
+[test 63:]pc 8 2~f c
+[test 64:]pc 16 4~f c
+[test 65:]pc 256 16~f c
+[test 66:]pc 255 16~f c
+[test 67:]pc 257 16~f c
+[test 68:]pc 1024 32~f c
+EOF
diff --git a/tests/0033-dc.sh b/tests/0033-dc.sh
new file mode 100755
index 0000000..aab5be8
--- /dev/null
+++ b/tests/0033-dc.sh
_AT_@ -0,0 +1,137 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Expected output for exponentiation tests
+# Values derived from system bc
+cat <<EOF >$tmp
+test 1:
+1
+test 2:
+2
+test 3:
+8
+test 4:
+1024
+test 5:
+243
+test 6:
+1000000
+test 7:
+4
+test 8:
+-8
+test 9:
+16
+test 10:
+-32
+test 11:
+-27
+test 12:
+81
+test 13:
+-1000
+test 14:
+-100000
+test 15:
+1000000
+test 16:
+1
+test 17:
+1
+test 18:
+1
+test 19:
+1
+test 20:
+.5000000000
+test 21:
+.2500000000
+test 22:
+.1250000000
+test 23:
+.0625000000
+test 24:
+.0010000000
+test 25:
+-.1250000000
+test 26:
+.0625000000
+test 27:
+2.25
+test 28:
+3.375
+test 29:
+.25
+test 30:
+.125
+test 31:
+2.25
+test 32:
+-3.375
+test 33:
+1.5625
+test 34:
+.0625
+test 35:
+.015625
+test 36:
+.0625
+test 37:
+-.015625
+test 38:
+.015625
+test 39:
+-.001953125
+test 40:
+4.0000000000
+test 41:
+8.0000000000
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 2 0^p
+[test 2:]pc 2 1^p
+[test 3:]pc 2 3^p
+[test 4:]pc 2 10^p
+[test 5:]pc 3 5^p
+[test 6:]pc 10 6^p
+[test 7:]pc _2 2^p
+[test 8:]pc _2 3^p
+[test 9:]pc _2 4^p
+[test 10:]pc _2 5^p
+[test 11:]pc _3 3^p
+[test 12:]pc _3 4^p
+[test 13:]pc _10 3^p
+[test 14:]pc _10 5^p
+[test 15:]pc _10 6^p
+[test 16:]pc 0 0^p
+[test 17:]pc 5 0^p
+[test 18:]pc _5 0^p
+[test 19:]pc 100 0^p
+[test 20:]pc 10k 2 _1^p
+[test 21:]pc 10k 2 _2^p
+[test 22:]pc 10k 2 _3^p
+[test 23:]pc 10k 4 _2^p
+[test 24:]pc 10k 10 _3^p
+[test 25:]pc 10k _2 _3^p
+[test 26:]pc 10k _2 _4^p
+[test 27:]pc 1.50 2^p
+[test 28:]pc 1.500 3^p
+[test 29:]pc .50 2^p
+[test 30:]pc .500 3^p
+[test 31:]pc _1.50 2^p
+[test 32:]pc _1.500 3^p
+[test 33:]pc 1.2500 2^p
+[test 34:]pc .2500 2^p
+[test 35:]pc .250000 3^p
+[test 36:]pc _.2500 2^p
+[test 37:]pc _.250000 3^p
+[test 38:]pc .125000 2^p
+[test 39:]pc _.125000000 3^p
+[test 40:]pc 10k .50 _2^p
+[test 41:]pc 10k .500 _3^p
+EOF
diff --git a/tests/0034-dc.sh b/tests/0034-dc.sh
new file mode 100755
index 0000000..0fed2d4
--- /dev/null
+++ b/tests/0034-dc.sh
_AT_@ -0,0 +1,150 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+cat <<EOF >$tmp
+test 1:
+0
+test 2:
+1
+test 3:
+2
+test 4:
+3
+test 5:
+5
+test 6:
+6
+test 7:
+7
+test 8:
+9
+test 9:
+10
+test 10:
+1
+test 11:
+1
+test 12:
+1.41
+test 13:
+1.4142
+test 14:
+1.414213
+test 15:
+1.7
+test 16:
+1.732
+test 17:
+1.73205
+test 18:
+.50
+test 19:
+.2500
+test 20:
+.10
+test 21:
+.0100
+test 22:
+.001000
+test 23:
+.7
+test 24:
+.353
+test 25:
+.3
+test 26:
+.316
+test 27:
+.31622
+test 28:
+.0316
+test 29:
+1.20
+test 30:
+1.5000
+test 31:
+1.22
+test 32:
+1.2247
+test 33:
+1.110
+test 34:
+1.11085
+test 35:
+.9486
+test 36:
+.999499
+test 37:
+1.58
+test 38:
+3.5128
+test 39:
+2.0
+test 40:
+2.00
+test 41:
+2.000
+test 42:
+2.0000000000
+test 43:
+100.0000
+test 44:
+11.111075
+test 45:
+100000000
+test 46:
+9999
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc 0k 0vp
+[test 2:]pc 0k 1vp
+[test 3:]pc 0k 4vp
+[test 4:]pc 0k 9vp
+[test 5:]pc 0k 25vp
+[test 6:]pc 0k 36vp
+[test 7:]pc 0k 49vp
+[test 8:]pc 0k 81vp
+[test 9:]pc 0k 100vp
+[test 10:]pc 0k 2vp
+[test 11:]pc 0k 3vp
+[test 12:]pc 2k 2vp
+[test 13:]pc 4k 2vp
+[test 14:]pc 6k 2vp
+[test 15:]pc 1k 3vp
+[test 16:]pc 3k 3vp
+[test 17:]pc 5k 3vp
+[test 18:]pc 2k .25vp
+[test 19:]pc 4k .0625vp
+[test 20:]pc 2k .01vp
+[test 21:]pc 4k .0001vp
+[test 22:]pc 6k .000001vp
+[test 23:]pc 1k .5vp
+[test 24:]pc 3k .125vp
+[test 25:]pc 1k .1vp
+[test 26:]pc 3k .1vp
+[test 27:]pc 5k .1vp
+[test 28:]pc 4k .001vp
+[test 29:]pc 2k 1.44vp
+[test 30:]pc 4k 2.25vp
+[test 31:]pc 2k 1.5vp
+[test 32:]pc 4k 1.5vp
+[test 33:]pc 3k 1.234vp
+[test 34:]pc 5k 1.234vp
+[test 35:]pc 4k .9vp
+[test 36:]pc 6k .999vp
+[test 37:]pc 2k 2.5vp
+[test 38:]pc 4k 12.34vp
+[test 39:]pc 1k 4vp
+[test 40:]pc 2k 4vp
+[test 41:]pc 3k 4vp
+[test 42:]pc 10k 4vp
+[test 43:]pc 4k 10000vp
+[test 44:]pc 6k 123.456vp
+[test 45:]pc 0k 10000000000000000vp
+[test 46:]pc 0k 99980001vp
+EOF
diff --git a/tests/0035-dc.sh b/tests/0035-dc.sh
new file mode 100755
index 0000000..646d019
--- /dev/null
+++ b/tests/0035-dc.sh
_AT_@ -0,0 +1,30 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test negative number sqrt - should produce error message and push 0
+# Test negative numbers: integers, fractions, odd and even fraction digits
+$EXEC ../dc <<EOF >$tmp 2>&1
+[test 1:]pc _1vp
+[test 2:]pc _4vp
+[test 3:]pc _.5vp
+[test 4:]pc _.25vp
+EOF
+
+diff -u - $tmp <<'EOF'
+../dc: square root of negative number
+../dc: square root of negative number
+../dc: square root of negative number
+../dc: square root of negative number
+test 1:
+0
+test 2:
+0
+test 3:
+0
+test 4:
+0
+EOF
diff --git a/tests/0036-dc.sh b/tests/0036-dc.sh
new file mode 100755
index 0000000..2ac059c
--- /dev/null
+++ b/tests/0036-dc.sh
_AT_@ -0,0 +1,64 @@
+#!/bin/sh
+
+set -e
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test i, o, k, I, O, K commands
+cat <<'EOF' >$tmp
+test 1:
+10
+test 2:
+10
+test 3:
+0
+test 4:
+16
+test 5:
+16
+10
+test 6:
+5
+test 7:
+A
+test 8:
+FF
+test 9:
+10
+test 10:
+1010
+test 11:
+10
+test 12:
+.33333
+test 13:
+ 12 15
+test 14:
+ 01 04 19 19
+test 15:
+ 01.10
+test 16:
+.05 00
+EOF
+
+$EXEC ../dc <<'EOF' | diff -u $tmp -
+[test 1:]pc Ip
+[test 2:]pc Op
+[test 3:]pc Kp
+[test 4:]pc 16i Ip
+[test 5:]pc Ao Ip Op
+[test 6:]pc Ai 5k Kp
+[test 7:]pc 16o 10p
+[test 8:]pc 255p
+[test 9:]pc 10o 16i Ap
+[test 10:]pc Ai 2o 10p
+[test 11:]pc Ao 2i 1010p
+[test 12:]pc Ai 5k 1 3/p
+[test 13:]pc 20o 255p
+[test 14:]pc 9999p
+[test 15:]pc 1.5p
+[test 16:]pc .25p
+EOF
diff --git a/tests/0037-dc.sh b/tests/0037-dc.sh
new file mode 100755
index 0000000..ece05a3
--- /dev/null
+++ b/tests/0037-dc.sh
_AT_@ -0,0 +1,77 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Expected output for z, Z, and X operators
+cat <<EOF >$tmp
+test 1:
+0
+test 2:
+1
+test 3:
+2
+test 4:
+3
+test 5:
+5
+test 6:
+1
+test 7:
+3
+test 8:
+2
+test 9:
+3
+test 10:
+4
+test 11:
+1
+test 12:
+1
+test 13:
+1
+test 14:
+1
+test 15:
+1
+test 16:
+1
+test 17:
+0
+test 18:
+1
+test 19:
+2
+test 20:
+3
+test 21:
+5
+EOF
+
+# Test z (stack depth), Z (digit count/string length), X (scale)
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc zp c
+[test 2:]pc 1 zp c
+[test 3:]pc 1 2 zp c
+[test 4:]pc 1 2 3 zp c
+[test 5:]pc 12345Zp c
+[test 6:]pc 0Zp c
+[test 7:]pc 123Zp c
+[test 8:]pc 1.5Zp c
+[test 9:]pc 1.23Zp c
+[test 10:]pc 1.001Zp c
+[test 11:]pc 0.5Zp c
+[test 12:]pc 0.05Zp c
+[test 13:]pc 0.005Zp c
+[test 14:]pc .5Zp c
+[test 15:]pc .05Zp c
+[test 16:]pc .005Zp c
+[test 17:]pc 0Xp c
+[test 18:]pc 1.2Xp c
+[test 19:]pc 1.23Xp c
+[test 20:]pc 1.234Xp c
+[test 21:]pc [hello]Zp c
+EOF
diff --git a/tests/0038-dc.sh b/tests/0038-dc.sh
new file mode 100755
index 0000000..216af13
--- /dev/null
+++ b/tests/0038-dc.sh
_AT_@ -0,0 +1,72 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Expected output for f, c, d, and r operators
+cat <<EOF >$tmp
+test 1:
+test 2:
+3
+2
+1
+test 3:
+0
+test 4:
+5
+5
+test 5:
+3
+3
+2
+1
+test 6:
+2
+1
+test 7:
+2
+3
+1
+test 8:
+10
+test 9:
+1
+test 10:
+15
+test 11:
+test 12:
+1
+1
+1
+1
+test 13:
+-5
+-5
+test 14:
+1.5
+1.5
+test 15:
+2
+3
+1
+EOF
+
+$EXEC ../dc <<EOF | diff -u $tmp -
+[test 1:]pc f
+[test 2:]pc 1 2 3 f c
+[test 3:]pc 1 2 3 c zp c
+[test 4:]pc 5 d f c
+[test 5:]pc 1 2 3 d f c
+[test 6:]pc 2 1 r f c
+[test 7:]pc 1 2 3 r f c
+[test 8:]pc 5 d +p c
+[test 9:]pc 1 2 r -p c
+[test 10:]pc 5 d d + +p c
+[test 11:]pc 1 2 3 c f
+[test 12:]pc 1 d d d f c
+[test 13:]pc _5 d f c
+[test 14:]pc 1.5 d f c
+[test 15:]pc 1 2 3 r f c
+EOF
diff --git a/tests/0039-dc.sh b/tests/0039-dc.sh
new file mode 100755
index 0000000..12a0d6a
--- /dev/null
+++ b/tests/0039-dc.sh
_AT_@ -0,0 +1,101 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test s, l, S, L register commands
+$EXEC ../dc <<'EOF' >$tmp 2>&1
+[test 1:]pc 5 sa la p c
+[test 2:]pc lz p c
+[test 3:]pc 1 sb 2 lb p c
+[test 4:]pc 1 sc 2 sc lc p c
+[test 5:]pc 1 sd ld ld +p c
+[test 6:]pc 5 Se le p c
+[test 7:]pc 1 Sf 2 Sf 3 Sf lf p c
+[test 8:]pc 1 Sg 2 Sg Lg p c
+[test 9:]pc 1 Sh 2 Sh Lh Lh +p c
+[test 10:]pc 1 Si Li p c
+[test 11:]pc 1 sj 2 Sj 3 Sj Lj Lj lj p c
+[test 12:]pc _42 sk lk p c
+[test 13:]pc 1.5 sl ll p c
+[test 14:]pc 99999999999999999999 sm lm p c
+[test 15:]pc [hello] sn ln p c
+[test 16:]pc 1 so 2 sp lo lp +p c
+[test 17:]pc 1 Sq 2 Sr Lq Lr +p c
+[test 18:]pc 1 St 2 St 3 St Lt p Lt p Lt p c
+[test 19:]pc 1 2 3 Su Su Su Lu Lu Lu + +p c
+[test 20:]pc 1 sv lv lv lv + +p c
+[test 21:]pc 1 Sw 2 Sw 3 Sw 4 Sw 5 Sw Lw p Lw p Lw p Lw p Lw p c
+[test 22:]pc 1 Sx 2 Sy 3 Sx 4 Sy Lx Ly * Lx Ly * +p c
+[test 23:]pc 42 s0 100 S0 L0 p L0 p c
+[test 24:]pc LA
+[test 25:]pc 1 SB LB LB
+[test 26:]pc sC
+[test 27:]pc SD
+EOF
+
+diff -u - $tmp <<'EOF'
+../dc: stack register 'A' (101) is empty
+../dc: stack register 'B' (102) is empty
+../dc: stack empty
+../dc: stack empty
+test 1:
+5
+test 2:
+0
+test 3:
+1
+test 4:
+2
+test 5:
+2
+test 6:
+5
+test 7:
+3
+test 8:
+2
+test 9:
+3
+test 10:
+1
+test 11:
+1
+test 12:
+-42
+test 13:
+1.5
+test 14:
+99999999999999999999
+test 15:
+hello
+test 16:
+3
+test 17:
+3
+test 18:
+3
+2
+1
+test 19:
+6
+test 20:
+3
+test 21:
+5
+4
+3
+2
+1
+test 22:
+14
+test 23:
+100
+42
+test 24:
+test 25:
+test 26:
+test 27:
+EOF
diff --git a/tests/0040-dc.sh b/tests/0040-dc.sh
new file mode 100755
index 0000000..c92c5c8
--- /dev/null
+++ b/tests/0040-dc.sh
_AT_@ -0,0 +1,135 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test x, >, !>, <, !<, =, != commands
+# Note: dc pops values and compares: first_popped OP second_popped
+# So "3 5 >a" pops 5 then 3, checks 5 > 3 (true)
+# And "5 3 >a" pops 3 then 5, checks 3 > 5 (false)
+$EXEC ../dc <<'EOF' >$tmp 2>&1
+[test 1:]pc [42p]x c
+[test 2:]pc 5 x p c
+[test 3:]pc []x c
+[test 4:]pc [[10p]x]x c
+[test 5:]pc [[YES]p]sa 3 5 >a c
+[test 6:]pc [[NO]p]sa 5 3 >a c
+[test 7:]pc [[NO]p]sa 5 5 >a c
+[test 8:]pc [[YES]p]sa 5 3 <a c
+[test 9:]pc [[NO]p]sa 3 5 <a c
+[test 10:]pc [[NO]p]sa 5 5 <a c
+[test 11:]pc [[YES]p]sa 5 5 =a c
+[test 12:]pc [[NO]p]sa 5 3 =a c
+[test 13:]pc [[NO]p]sa 3 5 !>a c
+[test 14:]pc [[YES]p]sa 5 3 !>a c
+[test 15:]pc [[YES]p]sa 5 5 !>a c
+[test 16:]pc [[NO]p]sa 5 3 !<a c
+[test 17:]pc [[YES]p]sa 3 5 !<a c
+[test 18:]pc [[YES]p]sa 5 5 !<a c
+[test 19:]pc [[YES]p]sa 5 3 !=a c
+[test 20:]pc [[NO]p]sa 5 5 !=a c
+[test 21:]pc [[NO]p]sa _3 _5 >a c
+[test 22:]pc [[YES]p]sa _5 _3 >a c
+[test 23:]pc [[NO]p]sa 3 _5 >a c
+[test 24:]pc [[YES]p]sa _3 5 >a c
+[test 25:]pc [[YES]p]sa 0 0 =a c
+[test 26:]pc [[YES]p]sa _0 0 =a c
+[test 27:]pc [[YES]p]sa 1.4 1.5 >a c
+[test 28:]pc [[YES]p]sa 1.5 1.5 =a c
+[test 29:]pc [[YES]p]sa 1.5 1.4 <a c
+[test 30:]pc [[YES]p]sa 99999999999999999998 99999999999999999999 >a c
+[test 31:]pc [d p 1 - d 0 <a]sa 5 la x c
+[test 32:]pc [[YES]p]sa [2 2 =a]sb 2 2 =b c
+[test 33:]pc 99 sa la x p c
+[test 34:]pc [3p]sa [2p]sb 2 3 >a 3 2 <b c
+[test 35:]pc [[NO]p]sa 1 2 <a z p c
+[test 36:]pc [[[[[77p]]]]]x x x x x c
+[test 37:]pc [[YES]p]sa 2k 1.50 1.5 =a c
+[test 38:]pc [1p]x [2p]x [3p]x c
+[test 39:]pc x
+[test 40:]pc [[NO]p]sa 5 >a
+[test 41:]pc [[NO]p]sa >a
+EOF
+
+diff -u - $tmp <<'EOF'
+../dc: stack empty
+../dc: stack empty
+../dc: stack empty
+test 1:
+42
+test 2:
+5
+test 3:
+test 4:
+10
+test 5:
+YES
+test 6:
+test 7:
+test 8:
+YES
+test 9:
+test 10:
+test 11:
+YES
+test 12:
+test 13:
+test 14:
+YES
+test 15:
+YES
+test 16:
+test 17:
+YES
+test 18:
+YES
+test 19:
+YES
+test 20:
+test 21:
+test 22:
+YES
+test 23:
+test 24:
+YES
+test 25:
+YES
+test 26:
+YES
+test 27:
+YES
+test 28:
+YES
+test 29:
+YES
+test 30:
+YES
+test 31:
+5
+4
+3
+2
+1
+test 32:
+YES
+test 33:
+99
+test 34:
+3
+2
+test 35:
+0
+test 36:
+77
+test 37:
+YES
+test 38:
+1
+2
+3
+test 39:
+test 40:
+test 41:
+EOF
diff --git a/tests/0041-dc.sh b/tests/0041-dc.sh
new file mode 100755
index 0000000..8b5c0fb
--- /dev/null
+++ b/tests/0041-dc.sh
_AT_@ -0,0 +1,101 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+cat <<'EOF' > $tmp
+../dc: stack empty
+../dc: Q command argument exceeded string execution depth
+../dc: Q command requires a number >= 0
+../dc: Q command argument exceeded string execution depth
+test 1:
+test 2:
+test 3:
+test 4:
+test 5:
+99
+test 6:
+1
+4
+test 7:
+in-macro
+after-macro
+test 8:
+inner
+after-all
+test 9:
+before
+after
+test 10:
+not-equal
+continued
+test 11:
+equal
+continued
+test 12:
+3
+2
+done
+test 12a:
+3
+done
+test 13:
+0
+1
+2
+done
+test 13a:
+0
+done
+test 14:
+deep
+outer
+final
+test 15:
+42
+test 16:
+done
+test 17:
+first
+last
+test 18:
+before
+test 19:
+before-q
+test 20:
+equal
+EOF
+
+($EXEC ../dc <<'EOF'
+[test 1:]pc Q
+[test 2:]pc 1Q
+[test 3:]pc _1Q
+[test 4:]pc [100Q]x
+[test 5:]pc 99 [1Q]x p
+[test 6:]pc [[1p q 2p]x 3p]x 4p
+[test 7:]pc [[in-macro]p 1Q [not-printed]p]x [after-macro]p
+[test 8:]pc [[[inner]p 2Q [not1]p]x [not2]p]x [after-all]p
+[test 9:]pc [before]p 0Q [after]p
+[test 10:]pc [[equal-quit]p q]sa 5 3 =a [not-equal]p [continued]p
+[test 11:]pc [[equal-quit]p q]sa 5 5 !=a [equal]p [continued]p
+[test 12:]pc 3[[p 1- d 2 !>b 1Q]x]sb lbx [done]p
+[test 12a:]pc 3[[p 1- d 2 >b 1Q]x]sb lbx [done]p
+[test 13:]pc 0[[p 1+ d 2 !<b 1Q]x]sb lbx [done]p
+[test 13a:]pc 0[[p 1+ d 2 <b 1Q]x]sb lbx [done]p
+[test 14:]pc [[[[deep]p 2Q [x]p]x [y]p]x [outer]p]x [final]p
+[test 15:]pc [[42 q]x [x]p]x p
+[test 16:]pc [[1Q [not]p]x [done]p]x
+[test 17:]pc [[[first]p q q q]x [x]p]x [last]p
+[test 18:]pc [before]p q [after]p
+EOF
+
+$EXEC ../dc <<'EOF'
+[test 19:]pc [[before-q]p q [after-q]p]x [never]p
+EOF
+
+$EXEC ../dc <<'EOF'
+[test 20:]pc [[equal]p q]sa 5 5 =a [not-printed]p
+EOF
+) 2>&1 | diff -u - $tmp
diff --git a/tests/0042-dc.sh b/tests/0042-dc.sh
new file mode 100755
index 0000000..cf9e835
--- /dev/null
+++ b/tests/0042-dc.sh
_AT_@ -0,0 +1,107 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test : and ; array commands
+$EXEC ../dc <<'EOF' >$tmp 2>&1
+[test 1:]pc 42 0:a 0;a p c
+[test 2:]pc 10 0:b 20 1:b 30 2:b 0;b p 1;b p 2;b p c
+[test 3:]pc 100 5:c 5;c p c
+[test 4:]pc _42 0:d 0;d p c
+[test 5:]pc 1.5 0:e 0;e p c
+[test 6:]pc 99999999999999999999 0:f 0;f p c
+[test 7:]pc [hello] 0:g 0;g p c
+[test 8:]pc 1 0:h 2 0:h 0;h p c
+[test 9:]pc 5 10:i 10;i p c
+[test 10:]pc 1 0:j 2 1:j 3 2:j 0;j 1;j + 2;j +p c
+[test 11:]pc 100 0:k 0;k 0;k *p c
+[test 12:]pc 7 3:l 3;l 3;l 3;l + +p c
+[test 13:]pc 1 0:0 2 1:0 0;0 1;0 +p c
+[test 14:]pc 50 0:m 0;m 2/p c
+[test 15:]pc 10 0:n 0;n 5 * 2:n 2;n p c
+[test 16:]pc 42 _1:o
+[test 17:]pc _1;p
+[test 18:]pc 100 0:q 1 Sq 0;q p Lq p 0;q p c
+[test 19:]pc 10 0:r 1 Sr 20 0:r 2 Sr 30 0:r 0;r p Lr p 0;r p Lr p 0;r p c
+[test 20:]pc 5 0:s 1 Ss 2 Ss Ls p 0;s p Ls p 0;s p c
+[test 21:]pc 42 0:t 99 st 0;t p lt p c
+[test 22:]pc 1 0:u 2 1:u 99 Su 50 0:u 0;u p Lu p 0;u p 1;u p c
+[test 23:]pc 10 0:v 20 1:v 1 Sv 2 Sv Lv p Lv p 0;v p 1;v p c
+[test 24:]pc 100 5:w 1 Sw 200 5:w 2 Sw 300 5:w 5;w p Lw p 5;w p Lw p 5;w p c
+EOF
+
+diff -u - $tmp <<'EOF'
+../dc: array index must fit in a positive integer
+../dc: array index must fit in a positive integer
+test 1:
+42
+test 2:
+10
+20
+30
+test 3:
+100
+test 4:
+-42
+test 5:
+1.5
+test 6:
+99999999999999999999
+test 7:
+hello
+test 8:
+2
+test 9:
+5
+test 10:
+6
+test 11:
+10000
+test 12:
+21
+test 13:
+3
+test 14:
+25
+test 15:
+50
+test 16:
+test 17:
+test 18:
+0
+1
+100
+test 19:
+30
+2
+20
+1
+10
+test 20:
+2
+0
+1
+5
+test 21:
+42
+99
+test 22:
+50
+99
+1
+2
+test 23:
+2
+1
+10
+20
+test 24:
+300
+2
+200
+1
+100
+EOF
diff --git a/tests/0043-dc.sh b/tests/0043-dc.sh
new file mode 100755
index 0000000..7d9d5e8
--- /dev/null
+++ b/tests/0043-dc.sh
_AT_@ -0,0 +1,59 @@
+#!/bin/sh
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Test -i flag for extended register names
+# <n> syntax: n is parsed as decimal and used as register name byte
+# "str" syntax: str is used as multi-character register name
+
+$EXEC ../dc -i <<'EOF' >$tmp 2>&1
+[test 1:]pc 42 s<65> l<65> p c
+[test 2:]pc 100 s"foo" l"foo" p c
+[test 3:]pc 99 s<65> lA p c
+[test 4:]pc 1 S<66> 2 S<66> L<66> p L<66> p c
+[test 5:]pc 10 S"bar" 20 S"bar" L"bar" p L"bar" p c
+[test 6:]pc 5 s<67> lC p c
+[test 7:]pc 1 s"x" 2 s"xy" 3 s"xyz" l"x" p l"xy" p l"xyz" p c
+[test 8:]pc 77 s<0> l<0> p c
+[test 9:]pc 88 s"D" lD p c
+[test 10:]pc [42p] s<69> l<69> x c
+[test 11:]pc [99p] s"macro" l"macro" x c
+[test 12:]pc 1 s<70> 2 s<70> 3 s<70> l<70> p c
+[test 13:]pc 10 s"reg" 20 s"reg" 30 s"reg" l"reg" p c
+EOF
+
+diff -u - $tmp <<'EOF'
+test 1:
+42
+test 2:
+100
+test 3:
+99
+test 4:
+2
+1
+test 5:
+20
+10
+test 6:
+5
+test 7:
+1
+2
+3
+test 8:
+77
+test 9:
+88
+test 10:
+42
+test 11:
+99
+test 12:
+3
+test 13:
+30
+EOF
diff --git a/tests/0044-dc.sh b/tests/0044-dc.sh
new file mode 100755
index 0000000..e48fc66
--- /dev/null
+++ b/tests/0044-dc.sh
_AT_@ -0,0 +1,36 @@
+#!/bin/sh
+
+set -e
+
+tmp=$$.tmp
+
+trap 'rm -f $tmp' EXIT
+trap 'exit $?' HUP INT TERM
+
+# Expected output for line wrapping tests (derived from system dc)
+cat <<'EOF' >$tmp
+test 1:
+327339060789614187001318969682759915221664204604306478948329136809613\
+379640467455488327009232590415715088668412756007100921725654588539305\
+3328527589376
+test 2:
+-32733906078961418700131896968275991522166420460430647894832913680961\
+337964046745548832700923259041571508866841275600710092172565458853930\
+53328527589376
+test 3:
+.33333333333333333333333333333333333333333333333333333333333333333333\
+33333333333333333333333333333333
+test 4:
+123456789012345678901234567890123456789012345678901234567890123456789
+test 5:
+123456789012345678901234567890123456789012345678901234567890123456789\
+0
+EOF
+
+$EXEC ../dc <<'EOF' | diff -u $tmp -
+[test 1:]pc 2 500^ p
+[test 2:]pc 0 2 500^ - p
+[test 3:]pc 100k 1 3 / p
+[test 4:]pc 123456789012345678901234567890123456789012345678901234567890123456789 p
+[test 5:]pc 1234567890123456789012345678901234567890123456789012345678901234567890 p
+EOF
Received on Wed Jan 14 2026 - 12:51:12 CET
This archive was generated by hypermail 2.3.0
: Wed Jan 14 2026 - 13:12:13 CET