From 6bc0d8ac49797eee9e3ad24b00258f8ff6fddb8c Mon Sep 17 00:00:00 2001 From: Jeff Parsons Date: Wed, 15 Feb 2017 16:16:32 -0800 Subject: [PATCH] Trying a second approach to the Int36 class that sticks to unsigned numbers internally (only downside so far is that the detecting arithmetic overflow/underflow is more work and hasn't been implemented yet) --- modules/shared/bin/int36 | 4 +- modules/shared/lib/int36.js | 154 ++++----- modules/shared/lib/int36s.js | 652 +++++++++++++++++++++++++++++++++++ 3 files changed, 713 insertions(+), 97 deletions(-) create mode 100644 modules/shared/lib/int36s.js diff --git a/modules/shared/bin/int36 b/modules/shared/bin/int36 index 103f3e0c8..2d2045612 100644 --- a/modules/shared/bin/int36 +++ b/modules/shared/bin/int36 @@ -31,8 +31,8 @@ "use strict"; var repl = require("repl"); -var Defines = require("../../shared/lib/defines"); -var Int36 = require("../../shared/lib/int36"); +var Defines = require("../lib/defines"); +var Int36 = require("../lib/int36"); var i36Reg = new Int36(); diff --git a/modules/shared/lib/int36.js b/modules/shared/lib/int36.js index 59b0796d3..04bc25499 100644 --- a/modules/shared/lib/int36.js +++ b/modules/shared/lib/int36.js @@ -1,5 +1,5 @@ /** - * @fileoverview Support for 36-bit integers + * @fileoverview Support for 36-bit integers (using unsigned JavaScript numbers) * @author Jeff Parsons (@jeffpar) * @copyright © Jeff Parsons 2012-2017 * @@ -37,11 +37,8 @@ var DEBUG = true; * @property {number|null} remainder * @property {number} error * - * The 'value' property stores the 36-bit value as a signed integer, meaning if the sign bit - * (bit 35) is set, we store the corresponding negative (two's complement) value. While there - * might be some slight benefits to always storing the 36-bit value as an unsigned quantity - * and negating it "on demand", I like having JavaScript's native representation match the - * emulated value. + * The 'value' property stores the 36-bit value as an unsigned integer. When the value + * should be interpreted as a signed quantity, subtract BIT36 whenever value > MAXSIGNED. * * The 'extended' property stores an additional 36 bits of data from a multiplication; * it must also be set prior to a division. Internally, it will be set to null whenever the @@ -60,15 +57,16 @@ class Int36 { * The constructor, which simply calls set(), creates an Int36 from either: * * 1) another Int36 - * 2) a single (signed) 36-bit value, with an optional 36-bit extension + * 2) a single 36-bit value, with an optional 36-bit extension * 3) nothing (initial value will be zero) * - * We guarantee that an Int36 value will be (and will always remain) a signed value within this range: + * We guarantee that an Int36 value will be (and always remain) an unsigned value within this range: * - * -Math.pow(2, 35) <= i <= Math.pow(2, 35) - 1 + * 0 <= i <= Math.pow(2, 36) - 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). + * The lower bound is ZERO and the upper bound is Int36.MAXVAL. Whenever an Int36 value should be + * interpreted as a signed value, call isNegative() to check the sign bit (bit 35) and call negate() + * as appropriate. * * 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 @@ -78,7 +76,7 @@ class Int36 { * * -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 + * it seems unwise to ever permit the internal value to creep outside the 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 @@ -134,17 +132,18 @@ class Int36 { } /** - * toDecimal() + * toDecimal(fUnsigned) * * @this {Int36} + * @param {boolean} fUnsigned * @return {string} */ - toDecimal() + toDecimal(fUnsigned) { var s = "", fNeg = false; var i36Div = new Int36(10000000000); var i36Tmp = new Int36(this.value, this.extended); - if (i36Tmp.isNegative()) { + if (!fUnsigned && i36Tmp.isNegative()) { i36Tmp.negate(); fNeg = true; } @@ -172,6 +171,10 @@ class Int36 { */ toString(radix = 10, fUnsigned) { + if (radix == 10) { + return this.toDecimal(fUnsigned); + } + var value = this.value; var extended = this.extended; @@ -187,23 +190,13 @@ class Int36 { return s; } - if (radix != 10) { - fUnsigned = true; - } else { - return this.toDecimal(); - } - - if (fUnsigned || extended) { - if (value < 0) value += Int36.BIT36; - if (extended != null) { - if (fUnsigned) extended += Int36.BIT36; - /* - * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, - * because after 52 bits, JavaScript will start dropping least-significant bits. Until - * then, you're better off sticking with octal (see above). - */ - value = extended * Int36.BIT36 + value; - } + if (extended != null) { + /* + * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, + * because after 52 bits, JavaScript will start dropping least-significant bits. Until + * then, you're better off sticking with octal (see above). + */ + value = extended * Int36.BIT36 + value; } return value.toString(radix); } @@ -227,15 +220,11 @@ class Int36 { this.extended = null; this.remainder = null; 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; + + if (result < 0) { + result += Int36.BIT36; } + result %= Int36.BIT36; return result; } @@ -321,13 +310,13 @@ class Int36 { var fNeg = false, extended; var n1 = this.value, n2 = value; - if (n1 < 0) { - if (n1) n1 = -n1; + if (n1 > Int36.MAXSIGNED) { + n1 = Int36.BIT36 - n1; fNeg = !fNeg; } - if (n2 < 0) { - if (n2) n2 = -n2; + if (n2 > Int36.MAXSIGNED) { + n2 = Int36.BIT36 - n2; fNeg = !fNeg; } @@ -352,7 +341,9 @@ class Int36 { this.value = this.truncate(value); this.extended = this.truncate(extended); - if (fNeg) this.negate(); + if (fNeg) { + this.negate(); + } } /** @@ -398,8 +389,8 @@ class Int36 { */ var bNegLo = 0, bNegHi = 0; - if (divisor < 0 && divisor > Int36.MINVAL) { - divisor = -divisor; + if (divisor > Int36.MAXSIGNED) { + divisor = Int36.BIT36 - divisor; bNegLo = 1 - bNegLo; } @@ -411,10 +402,6 @@ class Int36 { var value = this.value; var extended = this.extended || 0; - if (value < 0) { - value += Int36.BIT36; - } - if (!divisor) { this.error |= Int36.ERROR.DIVZERO; } @@ -448,11 +435,11 @@ class Int36 { this.extended = null; this.remainder = bitsRem[0]; - if (bNegLo && this.value && this.value > Int36.MINVAL) { - this.value = -this.value; + if (bNegLo && this.value && this.value > Int36.MINSIGNED) { + this.value = Int36.BIT36 - this.value; } - if (bNegHi && this.remainder && this.remainder > Int36.MINVAL) { - this.remainder = -this.remainder; + if (bNegHi && this.remainder && this.remainder > Int36.MINSIGNED) { + this.remainder = Int36.BIT36 - this.remainder; } } return this.value; @@ -465,60 +452,37 @@ class Int36 { */ isNegative() { - return (this.extended < 0 || this.extended == null && this.value < 0); + return (this.extended > Int36.MAXSIGNED || this.extended == null && this.value > Int36.MAXSIGNED); } /** * negate() - * - * 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 - * - * you can see that, when performing two's complement, MINVAL and ZERO are not modified. - * - * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative - * MINVAL values, we don't need to exclude MINVAL from the conversion. */ negate() { - 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. + * Set extended to match the sign of the (negated) value. */ - this.extended = (this.value < 0? -1 : 0); + this.extended = (this.value > Int36.MAXSIGNED? 0 : Int36.MAXVAL); } else if (this.value) { /* * Perform one's complement on the extended value. */ - this.extended = -this.extended - 1; + this.extended = Int36.MAXVAL - this.extended; } else { /* * Perform two's complement on the extended value. */ - if (this.extended /* && this.extended > Int36.MINVAL */) { - this.extended = -this.extended; - } + if (this.extended) this.extended = Int36.BIT36 - this.extended; } + /* + * Perform two's complement on the value. + */ + if (this.value) this.value = Int36.BIT36 - this.value; + this.error = Int36.ERROR.NONE; } /** @@ -624,12 +588,10 @@ class Int36 { */ static validate(num) { - var value = Math.trunc(num) % Int36.BIT36; - if (value > Int36.MAXVAL) { - value -= Int36.BIT36; - } else if (value < Int36.MINVAL) { - value += Int36.BIT36; + if (num < 0 && num >= Int36.MINSIGNED) { + num += Int36.BIT36; } + var value = Math.trunc(Math.abs(num)) % Int36.BIT36; if (DEBUG && num !== value) { console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); } @@ -647,7 +609,9 @@ Int36.ERROR = { 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 +Int36.MAXSIGNED = Math.pow(2, 35) - 1; // 34,359,738,367 +Int36.MINSIGNED = -Math.pow(2, 35); // -34,359,738,368 + +Int36.MAXVAL = Math.pow(2, 36) - 1; // 68,719,476,735 if (NODE) module.exports = Int36; diff --git a/modules/shared/lib/int36s.js b/modules/shared/lib/int36s.js new file mode 100644 index 000000000..a8b119d54 --- /dev/null +++ b/modules/shared/lib/int36s.js @@ -0,0 +1,652 @@ +/** + * @fileoverview Support for 36-bit integers (using signed JavaScript numbers) + * @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 + * @property {number} value + * @property {number|null} extended + * @property {number|null} remainder + * @property {number} error + * + * The 'value' property stores the 36-bit value as a signed integer, meaning if the sign bit + * (bit 35) is set, we store the corresponding negative (two's complement) value. While there + * might be some slight benefits to always storing the 36-bit value as an unsigned quantity + * and negating it "on demand", I like having JavaScript's native representation match the + * emulated value. + * + * The 'extended' property stores an additional 36 bits of data from a multiplication; + * it must also be set prior to a division. Internally, it will be set to null whenever the + * current value is not extended. + * + * The 'remainder' property stores the remainder from the last division. You should assume it + * will be set to null by any other operation. + * + * The 'error' property records any error(s) from the last operation. + */ + +class Int36 { + /** + * Int36(obj, extended) + * + * The constructor, which simply calls set(), creates an Int36 from either: + * + * 1) another Int36 + * 2) a single (signed) 36-bit value, with an optional 36-bit extension + * 3) 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. + * + * @this {Int36} + * @param {Int36|number} [obj] (if omitted, the default is zero) + * @param {number|null} [extended] + */ + constructor(obj, extended) + { + this.set(obj, extended); + this.bitsDiv = [0, 0]; + this.bitsRem = [0, 0]; + } + + /** + * set(obj, extended) + * + * @this {Int36} + * @param {Int36|number} [obj] (if omitted, the default is zero) + * @param {number|null} [extended] + */ + set(obj = 0, extended) + { + if (obj instanceof Int36) { + this.value = obj.value; + this.extended = obj.extended; + this.remainder = obj.remainder; + } + else { + this.value = Int36.validate(obj || 0); + this.extended = null; + /* + * NOTE: Surprisingly, isNaN(null) is false, whereas isNaN(undefined) is true. Go figure. + */ + if (extended != null && !isNaN(extended)) { + this.extended = Int36.validate(extended); + } + this.remainder = null; + } + this.error = Int36.ERROR.NONE; + } + + /** + * toDecimal(fUnsigned) + * + * @this {Int36} + * @param {boolean} fUnsigned + * @return {string} + */ + toDecimal(fUnsigned) + { + var s = "", fNeg = false; + var i36Div = new Int36(10000000000); + var i36Tmp = new Int36(this.value, this.extended); + if (!fUnsigned && i36Tmp.isNegative()) { + i36Tmp.negate(); + fNeg = true; + } + do { + var quotient = i36Tmp.div(i36Div); + var nMinDigits = (quotient? 10 : 1); + i36Tmp.value = i36Tmp.remainder; + do { + i36Tmp.divNum(10); + s = String.fromCharCode(0x30 + i36Tmp.remainder) + s; + } while (--nMinDigits > 0 || i36Tmp.value); + i36Tmp.value = quotient; + } 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) + { + if (radix == 10) { + return this.toDecimal(fUnsigned); + } + + var value = this.value; + var extended = this.extended; + + if (radix == 8) { + var s = Int36.octal(value); + if (extended) { + s = Int36.octal(extended) + ',' + s; + } + if (this.remainder) { + s += ':' + Int36.octal(this.remainder); + } + if (DEBUG && this.error) s += " error 0x" + this.error.toString(16); + return s; + } + + if (value < 0) { + value += Int36.BIT36; + } + if (extended != null) { + if (extended < 0) extended += Int36.BIT36; + /* + * TODO: Need a radix-independent solution for these extended (up to 72-bit) values, + * because after 52 bits, JavaScript will start dropping least-significant bits. Until + * then, you're better off sticking with octal (see above). + */ + value = extended * Int36.BIT36 + value; + } + return value.toString(radix); + } + + /** + * 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. + * + * @this {Int36} + * @param {number} result + * @return {number} + */ + truncate(result) + { + if (DEBUG && result !== Math.trunc(result)) { + console.log("Int36.truncate(" + result + " is not an integer)"); + } + this.extended = null; + this.remainder = null; + 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) + * + * @this {Int36} + * @param {Int36} i36 + */ + add(i36) + { + this.value = this.truncate(this.value + i36.value); + } + + /** + * addNum(num) + * + * @this {Int36} + * @param {number} num + */ + addNum(num) + { + this.value = this.truncate(this.value + Int36.validate(num)); + } + + /** + * sub(i36) + * + * @this {Int36} + * @param {Int36} i36 + */ + sub(i36) + { + this.value = this.truncate(this.value - i36.value); + } + + /** + * subNum(num) + * + * @this {Int36} + * @param {number} num + */ + subNum(num) + { + this.value = this.truncate(this.value - Int36.validate(num)); + } + + /** + * mul(i36) + * + * @this {Int36} + * @param {Int36} i36 + */ + mul(i36) + { + this.mulExtended(i36.value); + } + + /** + * mulNum(num) + * + * @this {Int36} + * @param {number} num + */ + mulNum(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. + * + * @this {Int36} + * @param {number} value + */ + mulExtended(value) + { + var fNeg = false, extended; + var n1 = this.value, n2 = value; + + if (n1 < 0) { + if (n1) n1 = -n1; + fNeg = !fNeg; + } + + if (n2 < 0) { + if (n2) 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); + } + + this.value = this.truncate(value); + this.extended = this.truncate(extended); + + if (fNeg) this.negate(); + } + + /** + * div(i36) + * + * @this {Int36} + * @param {Int36} i36 + * @return {number} (quotient) + */ + div(i36) + { + return this.divExtended(i36.value); + } + + /** + * divNum(num) + * + * @this {Int36} + * @param {number} num + * @return {number} (quotient) + */ + divNum(num) + { + return this.divExtended(Int36.validate(num)); + } + + /** + * divExtended(divisor) + * + * @this {Int36} + * @param {number} divisor + * @return {number} (quotient) + */ + divExtended(divisor) + { + /* + * dividend divisor quotient remainder + * -------- ------- -------- --------- + * + + -> + + + * + - -> - + + * - + -> - - + * - - -> + - + */ + var bNegLo = 0, bNegHi = 0; + + if (divisor < 0 && divisor > Int36.MINVAL) { + divisor = -divisor; + bNegLo = 1 - bNegLo; + } + + if (this.isNegative()) { + this.negate(); + bNegHi = 1; bNegLo = 1 - bNegLo; + } + + var value = this.value; + var extended = this.extended || 0; + + if (value < 0) { + value += Int36.BIT36; + } + + if (!divisor) { + this.error |= Int36.ERROR.DIVZERO; + } + else if (divisor <= extended) { + this.error |= Int36.ERROR.OVERFLOW; + } + else { + var result = 0, bit = 1; + var bitsDiv = Int36.setBits(this.bitsDiv, divisor, 0); + var bitsRem = Int36.setBits(this.bitsRem, value, extended); + + while (Int36.cmpBits(bitsRem, bitsDiv) > 0) { + Int36.addBits(bitsDiv, bitsDiv); + bit += bit; + } + + do { + if (Int36.cmpBits(bitsRem, bitsDiv) >= 0) { + Int36.subBits(bitsRem, bitsDiv); + result += bit; + } + Int36.shrBits(bitsDiv); + bit /= 2; + } while (bit >= 1); + + if (DEBUG && !(result < Int36.BIT36 && !bitsRem[1])) { + console.log("divExtended() assertion failure"); + } + + this.value = result; + this.extended = null; + this.remainder = bitsRem[0]; + + 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; + } + + /** + * isNegative() + * + * @return {boolean} + */ + isNegative() + { + return (this.extended < 0 || this.extended == null && this.value < 0); + } + + /** + * negate() + * + * 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 + * + * you can see that, when performing two's complement, MINVAL and ZERO are not modified. + * + * However, in our happy little world, since JavaScript numbers CAN represent both positive and negative + * MINVAL values, we don't need to exclude MINVAL from the conversion. + */ + negate() + { + 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; + } + } + } + + /** + * addBits(bitsDst, bitsSrc) + * + * Adds bitsSrc to bitsDst. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + */ + static addBits(bitsDst, bitsSrc) + { + bitsDst[0] += bitsSrc[0]; + bitsDst[1] += bitsSrc[1]; + if (bitsDst[0] >= Int36.BIT36) { + bitsDst[0] %= Int36.BIT36; + bitsDst[1]++; + } + } + + /** + * cmpBits(bitsDst, bitsSrc) + * + * Compares bitsDst to bitsSrc, by computing bitsDst - bitsSrc. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + * @return {number} > 0 if bitsDst > bitsSrc, == 0 if bitsDst == bitsSrc, < 0 if bitsDst < bitsSrc + */ + static cmpBits(bitsDst, bitsSrc) + { + var result = bitsDst[1] - bitsSrc[1]; + if (!result) result = bitsDst[0] - bitsSrc[0]; + return result; + } + + /** + * setBits(bits, lo, hi) + * + * @param {Array.} bits + * @param {number} lo + * @param {number} hi + * @return {Array.} + */ + static setBits(bits, lo, hi) + { + bits[0] = lo; + bits[1] = hi; + return bits; + } + + /** + * shrBits(bitsDst) + * + * Shifts bitsDst right one bit. + * + * @param {Array.} bitsDst + */ + static shrBits(bitsDst) + { + if (bitsDst[1] % 2) { + bitsDst[0] += Int36.BIT36; + } + bitsDst[0] = Math.trunc(bitsDst[0] / 2); + bitsDst[1] = Math.trunc(bitsDst[1] / 2); + } + + /** + * subBits(bitsDst, bitsSrc) + * + * Subtracts bitsSrc from bitsDst. + * + * @param {Array.} bitsDst + * @param {Array.} bitsSrc + */ + static subBits(bitsDst, bitsSrc) + { + bitsDst[0] -= bitsSrc[0]; + bitsDst[1] -= bitsSrc[1]; + if (bitsDst[0] < 0) { + bitsDst[0] += Int36.BIT36; + bitsDst[1]--; + } + } + + /** + * octal(value) + * + * @param {number} value + * @return {string} + */ + static octal(value) + { + if (value < 0) value += Int36.BIT36; + return ("00000000000" + value.toString(8)).slice(-12); + } + + /** + * validate(num) + * + * @param {number} num + * @return {number} + */ + static validate(num) + { + var value = Math.trunc(num) % Int36.BIT36; + if (value > Int36.MAXVAL) { + value -= Int36.BIT36; + } else if (value < Int36.MINVAL) { + value += Int36.BIT36; + } + if (DEBUG && num !== value) { + console.log("Int36.validate(" + num + " out of range, truncated to " + value + ")"); + } + return value; + } +} + +Int36.ERROR = { + NONE: 0x0, + OVERFLOW: 0x1, + UNDERFLOW: 0x2, + DIVZERO: 0x4 +}; + +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;