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.
*
* 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)

View file

@ -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,

View file

@ -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);
}
/**