From 656f7e6796cb115eabe3524e424b860606215e8e Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Tue, 14 Feb 2017 13:12:27 -0800 Subject: [PATCH] Numerous fixes to Int36 class, with improved support for 72-bit mul(), div(), and toString()/toDecimal() --- modules/shared/bin/int36 | 9 +- modules/shared/lib/int36.js | 191 +++++++++++++++++++++++++++++------- 2 files changed, 165 insertions(+), 35 deletions(-) diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index c61583558..c5a754de1 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -64,6 +64,8 @@ function test(sCmd, fREPL) var i36Op = new Int36(+sNum1); + if (sNum1 != null) console.log(sOp + " " + dumpInt36(i36Op)); + switch(sOp) { case "set": i36Reg = new Int36(+sNum1, +sNum2); @@ -85,6 +87,10 @@ function test(sCmd, fREPL) i36Reg.div(i36Op); break; + case "dec": + console.log("dec " + i36Reg.toDecimal()); + return true; + case "print": break; @@ -93,7 +99,6 @@ function test(sCmd, fREPL) return false; } - console.log(sOp + " " + dumpInt36(i36Op)); if (sOp != "set") console.log(" = " + dumpInt36(i36Reg)); return true; @@ -123,11 +128,13 @@ test("add 0"); for (let i = 0; i <= 12; i++) { test("set 34,000,000,000"); test("mul " + Math.pow(8, i)); + test("dec"); } for (let i = 0; i <= 12; i++) { test("set -34,000,000,000"); test("mul " + Math.pow(8, i)); + test("dec"); } test("set 100"); diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 2e19f7b24..170b0854c 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -33,7 +33,7 @@ var DEBUG = true; /** * @class Int36 * @property {number} value - * @property {number} extended + * @property {number|null} extended * @property {number} remainder * @property {number} error * @@ -88,6 +88,7 @@ class Int36 { * 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. * + * @this {Int36} * @param {Int36|number} [obj] (if omitted, the default is zero) * @param {number} [extended] */ @@ -101,6 +102,7 @@ class Int36 { /** * set(obj, extended) * + * @this {Int36} * @param {Int36|number} [obj] (if omitted, the default is zero) * @param {number} [extended] */ @@ -113,25 +115,63 @@ class Int36 { } else { this.value = Int36.validate(obj || 0); - this.extended = Int36.validate(extended || 0); + this.extended = null; + if (extended != null && !isNaN(extended)) { + this.extended = Int36.validate(extended); + } this.remainder = 0; } this.error = Int36.ERROR.NONE; } + /** + * toDecimal() + * + * @this {Int36} + * @return {string} + */ + toDecimal() + { + var s = "", fNeg = false; + var i36Div = new Int36(10000000000); + var i36Tmp = new Int36(this.value, this.extended); + if (i36Tmp.extended < 0 || i36Tmp.extended == null && i36Tmp.value < 0) { + i36Tmp.negExtended(); + fNeg = true; + } + var quotient = i36Tmp.div(i36Div); + i36Tmp.value = i36Tmp.remainder; + if (quotient) { + var nDigits = 10; + do { + i36Tmp.divNum(10); + s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; + } while (--nDigits); + i36Tmp.value = quotient; + } + do { + i36Tmp.divNum(10); + s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; + } while (i36Tmp.value); + if (fNeg) s = '-' + s; + return s; + } + /** * toString(radix, fUnsigned) * + * @this {Int36} * @param {number} [radix] (default is 10) * @param {boolean} [fUnsigned] (default is signed for radix 10, unsigned for any other radix) + * @return {string} */ toString(radix = 10, fUnsigned) { - var s; var value = this.value; var extended = this.extended; + if (radix == 8) { - s = Int36.octal(value); + var s = Int36.octal(value); if (extended) { s = Int36.octal(extended) + ',' + s; } @@ -141,7 +181,13 @@ class Int36 { if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); return s; } - if (radix != 10) fUnsigned = true; + + if (radix != 10) { + fUnsigned = true; + } else { + return this.toDecimal(); + } + if (fUnsigned || extended) { if (value < 0) value += Int36.BIT36; if (extended) { @@ -154,8 +200,7 @@ class Int36 { value = extended * Int36.BIT36 + value; } } - s = value.toString(radix); - return s; + return value.toString(radix); } /** @@ -165,12 +210,15 @@ class Int36 { * 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. * + * @this {Int36} * @param {number} result * @return {number} */ truncate(result) { - if (DEBUG && result !== Math.trunc(result)) console.log("Int36.truncate(" + result + " is not an integer)"); + 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; @@ -187,6 +235,7 @@ class Int36 { /** * add(i36) * + * @this {Int36} * @param {Int36} i36 */ add(i36) @@ -197,6 +246,7 @@ class Int36 { /** * addNum(num) * + * @this {Int36} * @param {number} num */ addNum(num) @@ -207,6 +257,7 @@ class Int36 { /** * sub(i36) * + * @this {Int36} * @param {Int36} i36 */ sub(i36) @@ -217,6 +268,7 @@ class Int36 { /** * subNum(num) * + * @this {Int36} * @param {number} num */ subNum(num) @@ -227,6 +279,7 @@ class Int36 { /** * mul(i36) * + * @this {Int36} * @param {Int36} i36 */ mul(i36) @@ -237,6 +290,7 @@ class Int36 { /** * mulNum(num) * + * @this {Int36} * @param {number} num */ mulNum(num) @@ -252,6 +306,7 @@ class Int36 { * 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. * + * @this {Int36} * @param {number} value */ mulExtended(value) @@ -260,12 +315,12 @@ class Int36 { var n1 = this.value, n2 = value; if (n1 < 0) { - n1 = -n1; + if (n1) n1 = -n1; fNeg = !fNeg; } if (n2 < 0) { - n2 = -n2; + if (n2) n2 = -n2; fNeg = !fNeg; } @@ -287,46 +342,45 @@ class Int36 { 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); + + if (fNeg) this.negExtended(); } /** * div(i36) * + * @this {Int36} * @param {Int36} i36 + * @return {number} (quotient) */ div(i36) { - this.divExtended(i36.value); + return this.divExtended(i36.value); } /** * divNum(num) * + * @this {Int36} * @param {number} num + * @return {number} (quotient) */ divNum(num) { - this.divExtended(Int36.validate(num)); + return this.divExtended(Int36.validate(num)); } /** * divExtended(divisor) * + * @this {Int36} * @param {number} divisor + * @return {number} (quotient) */ divExtended(divisor) { - var value = this.value; - var extended = this.extended; - - var bNegLo = 0, bNegHi = 0; /* * dividend divisor quotient remainder * -------- ------- -------- --------- @@ -335,16 +389,23 @@ class Int36 { * - + -> - - * - - -> + - */ - if (divisor < 0) { + var bNegLo = 0, bNegHi = 0; + + if (divisor < 0 && divisor > Int36.MINVAL) { divisor = -divisor; bNegLo = 1 - bNegLo; } - if (extended < 0) { - value = -value; - extended = -extended - (value? 1 : 0); - bNegHi = 1; - bNegLo = 1 - bNegLo; + if (this.extended < 0 || this.extended == null && this.value < 0) { + this.negExtended(); + bNegHi = 1; bNegLo = 1 - bNegLo; + } + + var value = this.value; + var extended = this.extended || 0; + + if (value < 0) { + value += Int36.BIT36; } if (!divisor) { @@ -372,14 +433,74 @@ class Int36 { bit /= 2; } while (bit >= 1); - if (DEBUG) console.assert(result < Int36.BIT36 && !bitsRem[1], "divExtended() assertion failure"); + if (DEBUG && !(result < Int36.BIT36 && !bitsRem[1])) { + console.log("divExtended() assertion failure"); + } this.value = result; this.extended = 0; this.remainder = bitsRem[0]; - if (bNegLo) this.value = -this.value; - if (bNegHi) this.remainder = -this.remainder; + if (bNegLo && this.value && this.value > Int36.MINVAL) { + this.value = -this.value; + } + if (bNegHi && this.remainder && this.remainder > Int36.MINVAL) { + this.remainder = -this.remainder; + } + } + return this.value; + } + + /** + * negExtended() + * + * Converts the current value to its two's complement. If we were dealing with 8-bit values: + * + * Original Two's One's + * ------- ----- ----- + * -128 -128 127 + * -127 127 126 + * ... ... ... + * -1 1 0 + * 0 0 -1 + * 1 -1 -2 + * ... ... ... + * 126 -126 -127 + * 127 -127 -128 + * + * So the one wrinkle is that, when performing two's complement, MINVAL and ZERO are not modified. + * + * However, in our world, since JavaScript numbers CAN represent both positive and negative MINVAL + * values, we don't need to exclude MINVAL from the process. + */ + negExtended() + { + this.error = Int36.ERROR.NONE; + /* + * Perform two's complement on the value. + */ + if (this.value /* && this.value > Int36.MINVAL */) { + this.value = -this.value; + } + if (this.extended == null) { + /* + * Set extended to match the sign of the value. + */ + this.extended = (this.value < 0? -1 : 0); + } + else if (this.value) { + /* + * Perform one's complement on the extended value. + */ + this.extended = -this.extended - 1; + } + else { + /* + * Perform two's complement on the extended value. + */ + if (this.extended /* && this.extended > Int36.MINVAL */) { + this.extended = -this.extended; + } } } @@ -492,7 +613,9 @@ class Int36 { } else if (value < Int36.MINVAL) { value += Int36.BIT36; } - if (DEBUG && num !== value) console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + if (DEBUG && num !== value) { + console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + } return value; } } @@ -504,10 +627,10 @@ Int36.ERROR = { DIVZERO: 0x4 }; -Int36.BIT18 = Math.pow(2, 18); // 262,144 -Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 +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.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;