From bfab39cf5a4993c070ea5681e20f105b65dec8b1 Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Sun, 12 Feb 2017 11:21:49 -0800 Subject: [PATCH] Skeleton of an Int36 class that may be used to support 36-bit integers in a PDP-10 simulation --- modules/shared/bin/int36 | 129 +++++++++++++++++ modules/shared/lib/int36.js | 278 ++++++++++++++++++++++++++++++++++++ 2 files changed, 407 insertions(+) create mode 100644 modules/shared/bin/int36 create mode 100644 modules/shared/lib/int36.js 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;