diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36
new file mode 100644
index 000000000..13ef785cf
--- /dev/null
+++ b/modules/shared/bin/int36
@@ -0,0 +1,129 @@
+#!/usr/bin/env node
+/**
+ * @fileoverview Test the Int36 class
+ * @author Jeff Parsons
+ * @copyright © Jeff Parsons 2012-2017
+ * @suppress {missingProperties}
+ *
+ * This file is part of PCjs, a computer emulation software project at .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every modified copy of this work
+ * and to display that copyright notice when the software starts running; see COPYRIGHT in
+ * .
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of PCjs
+ * for purposes of the GNU General Public License, and the author does not claim any copyright
+ * as to their contents.
+ */
+
+"use strict";
+
+var repl = require("repl");
+var Defines = require("../../shared/lib/defines");
+var Int36 = require("../../shared/lib/int36");
+
+var i36Reg = new Int36();
+
+/**
+ * dumpInt36(i36)
+ *
+ * @param {Int36} i36
+ * @return {string}
+ */
+function dumpInt36(i36)
+{
+ return i36.toString() + " (" + i36.toString(8, true) + ")";
+}
+
+/**
+ * test(sCmd, fREPL)
+ *
+ * @param {string} sCmd
+ * @param {boolean} [fREPL]
+ * @return {*}
+ */
+function test(sCmd, fREPL)
+{
+ var aTokens = sCmd.split(' ');
+
+ var sOp = aTokens[0];
+ var sNum1 = aTokens[1] && aTokens[1].replace(/,/g, '');
+ var sNum2 = aTokens[2];
+
+ var i36Op = new Int36(+sNum1);
+
+ switch(sOp) {
+ case "set":
+ i36Reg = new Int36(+sNum1, +sNum2);
+ break;
+
+ case "add":
+ i36Reg.add(i36Op);
+ break;
+
+ case "sub":
+ i36Reg.sub(i36Op);
+ break;
+
+ case "mul":
+ i36Reg.mul(i36Op);
+ break;
+
+ case "div":
+ i36Reg.div(i36Op);
+ break;
+
+ case "print":
+ break;
+
+ default:
+ console.log("unrecognized command: " + sCmd);
+ return false;
+ }
+
+ console.log(sOp + (sNum1? (" " + dumpInt36(i36Op)) : "") + ": " + dumpInt36(i36Reg));
+
+ return true;
+}
+
+/**
+ * onCommand(cmd, context, filename, callback)
+ *
+ * @param {string} cmd
+ * @param {Object} context
+ * @param {string} filename
+ * @param {function(Object|null, Object)} callback
+ */
+var onCommand = function (cmd, context, filename, callback)
+{
+ var result = false;
+ var match = cmd.match(/^\(?\s*([\S\s]*?)\s*\)?$/);
+ if (match && match[1]) result = test(match[1], true);
+ callback(null, result);
+};
+
+test("set -34,359,738,368");
+test("add 1");
+test("sub 1");
+test("sub 1");
+test("add 1");
+test("add 0");
+
+repl.start({
+ prompt: "int36> ",
+ input: process.stdin,
+ output: process.stdout,
+ eval: onCommand
+});
diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js
new file mode 100644
index 000000000..1518fe414
--- /dev/null
+++ b/modules/shared/lib/int36.js
@@ -0,0 +1,278 @@
+/**
+ * @fileoverview Support for 36-bit integers
+ * @author Jeff Parsons (@jeffpar)
+ * @copyright © Jeff Parsons 2012-2017
+ *
+ * This file is part of PCjs, a computer emulation software project at .
+ *
+ * PCjs is free software: you can redistribute it and/or modify it under the terms of the
+ * GNU General Public License as published by the Free Software Foundation, either version 3
+ * of the License, or (at your option) any later version.
+ *
+ * PCjs is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
+ * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with PCjs. If not,
+ * see .
+ *
+ * You are required to include the above copyright notice in every modified copy of this work
+ * and to display that copyright notice when the software starts running; see COPYRIGHT in
+ * .
+ *
+ * Some PCjs files also attempt to load external resource files, such as character-image files,
+ * ROM files, and disk image files. Those external resource files are not considered part of PCjs
+ * for purposes of the GNU General Public License, and the author does not claim any copyright
+ * as to their contents.
+ */
+
+"use strict";
+
+var DEBUG = true;
+
+class Int36 {
+ /**
+ * Int36(hi, lo)
+ *
+ * The constructor creates an Int36 from either:
+ *
+ * 1) another Int36
+ * 2) a single (signed) 36-bit value
+ * 3) a pair of 18-bit values (the signs are irrelevant)
+ * 4) nothing (initial value will be zero)
+ *
+ * We guarantee that an Int36 value will be (and will always remain) a signed value within this range:
+ *
+ * -Math.pow(2, 35) <= i <= Math.pow(2, 35) - 1
+ *
+ * Those lower and upper bounds are defined as Int36.MINVAL and Int36.MAXVAL. The sign of an Int36
+ * value is determined by (and should always match) its highest bit (bit 35).
+ *
+ * NOTE: We use modern bit numbering, where bit 0 is the right-most (least-significant) bit and
+ * bit 35 is the left-most bit. This is opposite of the PDP-10 convention, which defined bit 0 as the
+ * left-most bit and bit 35 as the right-most bit.
+ *
+ * Although the integer precision of JavaScript floating-point (IEEE 754 double-precision) numbers is:
+ *
+ * -Math.pow(2, 53) <= i <= Math.pow(2, 53)
+ *
+ * it seems unwise to ever permit the internal value to creep outside the signed 36-bit range, because
+ * floating-point operations will drop least-significant bits in favor of most-significant bits when a
+ * result becomes too large, which is the opposite of what integer operations traditionally do. There
+ * might be some optimization benefits to performing our internal 36-bit truncation "lazily", but at
+ * least initially, I prefer to truncate the results of all operations immediately.
+ *
+ * Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept
+ * another Int36. The latter are more efficient, because (as just explained) an Int36's internal value
+ * should always be in range, whereas external numbers could be out of range OR have a fractional value
+ * OR be something else entirely (NaN, Infinity, -Infinity, undefined, etc), so numeric inputs are
+ * always passed through the static validate() function.
+ *
+ * We could eliminate the two flavors and check each parameter's type, like we do in the constructor,
+ * but constructor calls are infrequent (if they're not, you're doing something wrong), whereas Int36-only
+ * operations should be as fast and unchecked as possible.
+ *
+ * @param {Int36|number} [hi] (if omitted, the default is zero)
+ * @param {number} [lo] (if present, both lo and hi must be 18-bit numbers)
+ */
+ constructor(hi = 0, lo)
+ {
+ if (hi instanceof Int36) {
+ this.value = hi.value;
+ }
+ else if (isNaN(lo)) {
+ /*
+ * Checking isNaN(lo) includes checking for undefined. And since there's no guarantee
+ * that hi is within the 36-bit range, we call validate() to make sure.
+ */
+ this.value = Int36.validate(hi);
+ } else {
+ /*
+ * We're masking both inputs to 18 bits, making them positive, so the result will
+ * be positive and no more than 36 bits; however, the value could be greater than MAXVAL,
+ * meaning the sign bit (bit 35) is set, in which case we must perform a two's complement
+ * conversion, by subtracting 2^36.
+ */
+ this.value = (hi & Int36.MASKLO) * Int36.BIT18 + (lo & Int36.MASKLO);
+ if (this.value > Int36.MAXVAL) {
+ this.value -= Int36.BIT36;
+ }
+ }
+ this.error = Int36.ERROR.NONE;
+ }
+
+ /**
+ * validate(num)
+ *
+ * @param {number} num
+ * @return {number}
+ */
+ static validate(num)
+ {
+ var result = Math.trunc(num || 0) % Int36.BIT36;
+ if (result > Int36.MAXVAL) {
+ result -= Int36.BIT36;
+ } else if (result < Int36.MINVAL) {
+ result += Int36.BIT36;
+ }
+ if (DEBUG && num !== result) console.log("Int36.validate(" + num + " out of range, truncated to " + result + ")");
+ return result;
+ }
+
+ /**
+ * toString(radix, fUnsigned)
+ *
+ * @param {number} [radix] (default is 10)
+ * @param {boolean} [fUnsigned] (default is signed)
+ */
+ toString(radix = 10, fUnsigned)
+ {
+ var s;
+ var value = this.value;
+ if (fUnsigned) {
+ if (value < 0) {
+ value += Int36.BIT36;
+ }
+ if (radix == 8) {
+ s = "0o" + ("00000000000" + value.toString(8)).slice(-12);
+ if (DEBUG && this.error) s += " error 0x" + this.error.toString(16);
+ }
+ }
+ if (!s) s = value.toString(radix);
+ return s;
+ }
+
+ /**
+ * truncate(result)
+ *
+ * NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy,
+ * not to remove any fractional portion that might also exist. If an operation could have produced
+ * a non-integer result (eg, div()), it's the caller's responsibility to deal with that first.
+ *
+ * @param {number} result
+ * @return {number}
+ */
+ truncate(result)
+ {
+ if (DEBUG && result !== Math.trunc(result)) console.log("Int36.truncate(" + result + " is not an integer)");
+ this.error = Int36.ERROR.NONE;
+ if (result > Int36.MAXVAL) {
+ result %= Int36.BIT36;
+ if (result > Int36.MAXVAL) result -= Int36.BIT36;
+ this.error |= Int36.ERROR.OVERFLOW;
+ } else if (result < Int36.MINVAL) {
+ result %= Int36.BIT36;
+ if (result < Int36.MINVAL) result += Int36.BIT36;
+ this.error |= Int36.ERROR.UNDERFLOW;
+ }
+ return result;
+ }
+
+ /**
+ * add(i36)
+ *
+ * @param {Int36} i36
+ */
+ add(i36) {
+ this.value = this.truncate(this.value + i36.value);
+ }
+
+ /**
+ * addNum(num)
+ *
+ * @param {number} num
+ */
+ addNum(num) {
+ this.value = this.truncate(this.value + Int36.validate(num));
+ }
+
+ /**
+ * sub(i36)
+ *
+ * @param {Int36} i36
+ */
+ sub(i36) {
+ this.value = this.truncate(this.value - i36.value);
+ }
+
+ /**
+ * subNum(num)
+ *
+ * @param {number} num
+ */
+ subNum(num) {
+ this.value = this.truncate(this.value - Int36.validate(num));
+ }
+
+ /**
+ * mul(i36)
+ *
+ * TODO: Support multiplication results > 36 bits (ie, up to 72 bits) if an additional Int36
+ * parameter is provided.
+ *
+ * @param {Int36} i36
+ */
+ mul(i36) {
+ this.value = this.truncate(this.value * i36.value);
+ }
+
+ /**
+ * mulNum(num)
+ *
+ * @param {number} num
+ */
+ mulNum(num) {
+ this.value = this.truncate(this.value * Int36.validate(num));
+ }
+
+ /**
+ * div(i36)
+ *
+ * TODO: Support division of dividends > 36 bits (ie, up to 72 bits) if an additional Int36
+ * parameter is provided.
+ *
+ * WARNING: JavaScript division by zero returns Infinity (or -Infinity). For now, we simply record an error.
+ *
+ * @param {Int36} i36
+ */
+ div(i36) {
+ if (!i36.value) {
+ this.error |= Int36.ERROR.DIVZERO;
+ } else {
+ this.value = this.truncate(Math.trunc(this.value / i36.value));
+ }
+ }
+
+ /**
+ * divNum(num)
+ *
+ * WARNING: JavaScript division by zero returns Infinity (or -Infinity). For now, we simply record an error.
+ *
+ * @param {number} num
+ */
+ divNum(num) {
+ var divisor = Int36.validate(num);
+ if (!divisor) {
+ this.error |= Int36.ERROR.DIVZERO;
+ } else {
+ this.value = this.truncate(Math.trunc(this.value / divisor));
+ }
+ }
+}
+
+Int36.ERROR = {
+ NONE: 0x0,
+ OVERFLOW: 0x1,
+ UNDERFLOW: 0x2,
+ DIVZERO: 0x4
+};
+
+Int36.MASKLO = 0o777777; // 262,143
+
+Int36.BIT18 = Math.pow(2, 18); // 262,144
+Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736
+
+Int36.MAXVAL = Math.pow(2, 35) - 1; // 34,359,738,367
+Int36.MINVAL = -Math.pow(2, 35); // -34,359,738,368
+
+if (NODE) module.exports = Int36;