Implemented 36-bit multiplication with support for (up to) 72-bit results

This commit is contained in:
Jeff Parsons 2017-02-13 11:46:58 -08:00 • committed by Jeff Parsons
commit f03a5686e5
3 changed files with 106 additions and 17 deletions

View file

@ -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. * 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} * @this {X86CPU}
* @param {number} dst (any 32-bit number, treated as unsigned) * @param {number} dst (any 32-bit number, treated as unsigned)
* @param {number} src (any 32-bit number, treated as unsigned) * @param {number} src (any 32-bit number, treated as unsigned)

View file

@ -44,7 +44,7 @@ var i36Reg = new Int36();
*/ */
function dumpInt36(i36) 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; return false;
} }
console.log(sOp + (sNum1? (" " + dumpInt36(i36Op)) : "") + ": " + dumpInt36(i36Reg)); console.log(sOp + " " + dumpInt36(i36Op));
if (sOp != "set") console.log(" = " + dumpInt36(i36Reg));
return true; return true;
} }
@ -115,12 +116,20 @@ var onCommand = function (cmd, context, filename, callback)
}; };
test("set -34,359,738,368"); test("set -34,359,738,368");
test("add 1");
test("sub 1");
test("sub 1"); test("sub 1");
test("add 1"); test("add 1");
test("add 0"); 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({ repl.start({
prompt: "int36> ", prompt: "int36> ",
input: process.stdin, input: process.stdin,

View file

@ -98,6 +98,12 @@ class Int36 {
this.value -= Int36.BIT36; 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; this.error = Int36.ERROR.NONE;
} }
@ -119,26 +125,49 @@ class Int36 {
return result; 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) * toString(radix, fUnsigned)
* *
* @param {number} [radix] (default is 10) * @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) toString(radix = 10, fUnsigned)
{ {
var s; var s;
var value = this.value; var value = this.value;
if (fUnsigned) { var extended = this.extended;
if (value < 0) { if (radix == 8) {
value += Int36.BIT36; s = Int36.octal(value);
if (extended) {
s = Int36.octal(extended) + ',' + s;
} }
if (radix == 8) { if (DEBUG && this.error) s += " error 0x" + this.error.toString(16);
s = "0o" + ("00000000000" + value.toString(8)).slice(-12); return s;
if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); }
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; return s;
} }
@ -207,13 +236,10 @@ class Int36 {
/** /**
* mul(i36) * mul(i36)
* *
* TODO: Support multiplication results > 36 bits (ie, up to 72 bits) if an additional Int36
* parameter is provided.
*
* @param {Int36} i36 * @param {Int36} i36
*/ */
mul(i36) { mul(i36) {
this.value = this.truncate(this.value * i36.value); this.mulExtended(i36.value);
} }
/** /**
@ -222,7 +248,58 @@ class Int36 {
* @param {number} num * @param {number} num
*/ */
mulNum(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);
} }
/** /**