From f03a5686e5247eb26972b5fe84d8c893042ea63d Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Mon, 13 Feb 2017 11:46:58 -0800 Subject: [PATCH] Implemented 36-bit multiplication with support for (up to) 72-bit results --- modules/pcx86/lib/x86func.js | 3 + modules/shared/bin/int36 | 17 ++++-- modules/shared/lib/int36.js | 103 ++++++++++++++++++++++++++++++----- 3 files changed, 106 insertions(+), 17 deletions(-) diff --git a/modules/pcx86/lib/x86func.js b/modules/pcx86/lib/x86func.js index e91dcaeba..7345361b0 100644 --- a/modules/pcx86/lib/x86func.js +++ b/modules/pcx86/lib/x86func.js @@ -1773,6 +1773,9 @@ X86.fnMULb = function(dst, src) * * This sets regMDHi:regMDLo to the 64-bit result of dst * src, both of which are treated as unsigned. * + * The algorithm is based on the traditional "by hand" multiplication method, by treating the two inputs + * (dst and src) as two 2-digit numbers, where each digit is a base-65536 digit. + * * @this {X86CPU} * @param {number} dst (any 32-bit number, treated as unsigned) * @param {number} src (any 32-bit number, treated as unsigned) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 13ef785cf..412098589 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -44,7 +44,7 @@ var i36Reg = new Int36(); */ function dumpInt36(i36) { - return i36.toString() + " (" + i36.toString(8, true) + ")"; + return i36.toString() + " [" + i36.toString(8, true) + "]"; } /** @@ -93,7 +93,8 @@ function test(sCmd, fREPL) return false; } - console.log(sOp + (sNum1? (" " + dumpInt36(i36Op)) : "") + ": " + dumpInt36(i36Reg)); + console.log(sOp + " " + dumpInt36(i36Op)); + if (sOp != "set") console.log(" = " + dumpInt36(i36Reg)); return true; } @@ -115,12 +116,20 @@ var onCommand = function (cmd, context, filename, callback) }; test("set -34,359,738,368"); -test("add 1"); -test("sub 1"); test("sub 1"); test("add 1"); test("add 0"); +for (let i = 0; i <= 12; i++) { + test("set 34,000,000,000"); + test("mul " + Math.pow(8, i)); +} + +for (let i = 0; i <= 12; i++) { + test("set -34,000,000,000"); + test("mul " + Math.pow(8, i)); +} + repl.start({ prompt: "int36> ", input: process.stdin, diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 1518fe414..8935fb199 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -98,6 +98,12 @@ class Int36 { this.value -= Int36.BIT36; } } + /* + * The 'extended' property stores an additional 36 bits of data after a multiplication, and any + * remainder after a division. It's strictly an output-only property for those operations, and + * its value does not affect the result of ANY operation. + */ + this.extended = 0; this.error = Int36.ERROR.NONE; } @@ -119,26 +125,49 @@ class Int36 { return result; } + /** + * octal(value) + * + * @param {number} value + * @return {string} + */ + static octal(value) + { + if (value < 0) value += Int36.BIT36; + return ("00000000000" + value.toString(8)).slice(-12); + } + /** * toString(radix, fUnsigned) * * @param {number} [radix] (default is 10) - * @param {boolean} [fUnsigned] (default is signed) + * @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix) */ toString(radix = 10, fUnsigned) { var s; var value = this.value; - if (fUnsigned) { - if (value < 0) { - value += Int36.BIT36; + var extended = this.extended; + if (radix == 8) { + s = Int36.octal(value); + if (extended) { + s = Int36.octal(extended) + ',' + s; } - if (radix == 8) { - s = "0o" + ("00000000000" + value.toString(8)).slice(-12); - if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + return s; + } + if (radix != 10) fUnsigned = true; + if (fUnsigned || extended) { + if (value < 0) value += Int36.BIT36; + if (extended) { + if (fUnsigned) extended += Int36.BIT36; + /* + * TODO: Come up with a solution that won't overflow JavaScript's more limited precision. + */ + value = extended * Int36.BIT36 + value; } } - if (!s) s = value.toString(radix); + s = value.toString(radix); return s; } @@ -207,13 +236,10 @@ class Int36 { /** * 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); + this.mulExtended(i36.value); } /** @@ -222,7 +248,58 @@ class Int36 { * @param {number} num */ mulNum(num) { - this.value = this.truncate(this.value * Int36.validate(num)); + this.mulExtended(Int36.validate(num)); + } + + /** + * mulExtended(value) + * + * To support 72-bit results, we perform the multiplication process as you would "by hand", + * treating each of the operands to be multiplied as two 2-digit numbers, where each digit is + * an 18-bit number (base 2^18). Each individual multiplication of these 18-bit "digits" + * will produce a result within 2^36, well within JavaScript integer accuracy. + * + * @param {number} value + */ + mulExtended(value) { + var fNeg = false, extended; + var n1 = this.value, n2 = value; + + if (n1 < 0) { + n1 = -n1; + fNeg = !fNeg; + } + + if (n2 < 0) { + n2 = -n2; + fNeg = !fNeg; + } + + if (n1 < Int36.BIT18 && n2 < Int36.BIT18) { + value = n1 * n2; + extended = 0; + } + else { + var n1d1 = (n1 % Int36.BIT18); + var n1d2 = Math.trunc(n1 / Int36.BIT18); + var n2d1 = (n2 % Int36.BIT18); + var n2d2 = Math.trunc(n2 / Int36.BIT18); + + var m1d1 = n1d1 * n2d1; + var m1d2 = (n1d2 * n2d1) + Math.trunc(m1d1 / Int36.BIT18); + extended = Math.trunc(m1d2 / Int36.BIT18); + m1d2 = (m1d2 % Int36.BIT18) + (n1d1 * n2d2); + value = (m1d2 * Int36.BIT18) + (m1d1 % Int36.BIT18); + extended += Math.trunc(m1d2 / Int36.BIT18) + (n1d2 * n2d2); + } + + if (fNeg) { + value = -value; + extended = -extended - (value? 1 : 0); + } + + this.value = this.truncate(value); + this.extended = this.truncate(extended); } /**