Added Int36 overflow/underflow detection

This commit is contained in:
Jeff Parsons 2017-02-15 22:20:07 -08:00 committed by Jeff Parsons
commit 40311e82b2

View file

@ -38,7 +38,7 @@ var DEBUG = true;
* @property {number} error * @property {number} error
* *
* The 'value' property stores the 36-bit value as an unsigned integer. When the 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. * should be interpreted as a signed quantity, subtract BIT36 whenever value > MAXPOS.
* *
* The 'extended' property stores an additional 36 bits of data from a multiplication; * 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 * it must also be set prior to a division. Internally, it will be set to null whenever the
@ -65,7 +65,7 @@ class Int36 {
* 0 <= i <= Math.pow(2, 36) - 1 * 0 <= i <= Math.pow(2, 36) - 1
* *
* The lower bound is ZERO and the upper bound is Int36.MAXVAL. Whenever an Int36 value should be * The lower bound is ZERO and the upper bound is Int36.MAXVAL. Whenever an Int36 value should be
* interpreted as a signed value, values above Int36.MAXSIGNED (ie, values with bit 35 set) should be * interpreted as a signed value, values above Int36.MAXPOS (ie, values with bit 35 set) should be
* converted to their signed counterpart by subtracting the value from Int36.BIT36 (aka MAXVAL + 1); * converted to their signed counterpart by subtracting the value from Int36.BIT36 (aka MAXVAL + 1);
* the easiest way to do that is call isNegative() to check the sign bit and then call negate() as * the easiest way to do that is call isNegative() to check the sign bit and then call negate() as
* appropriate. * appropriate.
@ -204,7 +204,14 @@ class Int36 {
} }
/** /**
* truncate(result) * truncate(result, original)
*
* The range of valid results (0 - MAXVAL) is divided into two equal sub-ranges: 0 to MAXPOS,
* where the sign bit is zero (the bottom range), and MAXPOS+1 to MAXVAL (the top range), where
* the sign bit is one. During a single arithmetic operation, the result can "wrap around" from
* the bottom range to the top, or from the top range to the bottom, but it's an overflow/underflow
* condition ONLY if the result "wraps across" the midpoint between the two ranges, producing an
* unnaturally small delta (<= MAXPOS).
* *
* NOTE: This function's job is to truncate the result of an operation to 36-bit accuracy, * 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 * not to remove any fractional portion that might also exist. If an operation could have produced
@ -212,13 +219,15 @@ class Int36 {
* *
* @this {Int36} * @this {Int36}
* @param {number} result * @param {number} result
* @param {number} [original]
* @return {number} * @return {number}
*/ */
truncate(result) truncate(result, original)
{ {
if (DEBUG && result !== Math.trunc(result)) { if (DEBUG && result !== Math.trunc(result)) {
console.log("Int36.truncate(" + result + " is not an integer)"); console.log("Int36.truncate(" + result + " is not an integer)");
} }
this.extended = null; this.extended = null;
this.remainder = null; this.remainder = null;
this.error = Int36.ERROR.NONE; this.error = Int36.ERROR.NONE;
@ -226,7 +235,15 @@ class Int36 {
if (result < 0) { if (result < 0) {
result += Int36.BIT36; result += Int36.BIT36;
} }
result %= Int36.BIT36; result %= Int36.BIT36;
if (original !== undefined && (result > Int36.MAXPOS) != (original > Int36.MAXPOS)) {
var delta = result - original;
if (Math.abs(delta) <= Int36.MAXPOS) {
this.error |= (delta > 0? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW);
}
}
return result; return result;
} }
@ -238,7 +255,7 @@ class Int36 {
*/ */
add(i36) add(i36)
{ {
this.value = this.truncate(this.value + i36.value); this.value = this.truncate(this.value + i36.value, this.value);
} }
/** /**
@ -249,7 +266,7 @@ class Int36 {
*/ */
addNum(num) addNum(num)
{ {
this.value = this.truncate(this.value + Int36.validate(num)); this.value = this.truncate(this.value + Int36.validate(num), this.value);
} }
/** /**
@ -260,7 +277,7 @@ class Int36 {
*/ */
sub(i36) sub(i36)
{ {
this.value = this.truncate(this.value - i36.value); this.value = this.truncate(this.value - i36.value, this.value);
} }
/** /**
@ -271,7 +288,7 @@ class Int36 {
*/ */
subNum(num) subNum(num)
{ {
this.value = this.truncate(this.value - Int36.validate(num)); this.value = this.truncate(this.value - Int36.validate(num), this.value);
} }
/** /**
@ -312,12 +329,12 @@ class Int36 {
var fNeg = false, extended; var fNeg = false, extended;
var n1 = this.value, n2 = value; var n1 = this.value, n2 = value;
if (n1 > Int36.MAXSIGNED) { if (n1 > Int36.MAXPOS) {
n1 = Int36.BIT36 - n1; n1 = Int36.BIT36 - n1;
fNeg = !fNeg; fNeg = !fNeg;
} }
if (n2 > Int36.MAXSIGNED) { if (n2 > Int36.MAXPOS) {
n2 = Int36.BIT36 - n2; n2 = Int36.BIT36 - n2;
fNeg = !fNeg; fNeg = !fNeg;
} }
@ -391,7 +408,7 @@ class Int36 {
*/ */
var bNegLo = 0, bNegHi = 0; var bNegLo = 0, bNegHi = 0;
if (divisor > Int36.MAXSIGNED) { if (divisor > Int36.MAXPOS) {
divisor = Int36.BIT36 - divisor; divisor = Int36.BIT36 - divisor;
bNegLo = 1 - bNegLo; bNegLo = 1 - bNegLo;
} }
@ -437,10 +454,10 @@ class Int36 {
this.extended = null; this.extended = null;
this.remainder = bitsRem[0]; this.remainder = bitsRem[0];
if (bNegLo && this.value && this.value > Int36.MINSIGNED) { if (bNegLo && this.value) {
this.value = Int36.BIT36 - this.value; this.value = Int36.BIT36 - this.value;
} }
if (bNegHi && this.remainder && this.remainder > Int36.MINSIGNED) { if (bNegHi && this.remainder) {
this.remainder = Int36.BIT36 - this.remainder; this.remainder = Int36.BIT36 - this.remainder;
} }
} }
@ -454,7 +471,7 @@ class Int36 {
*/ */
isNegative() isNegative()
{ {
return (this.extended > Int36.MAXSIGNED || this.extended == null && this.value > Int36.MAXSIGNED); return (this.extended > Int36.MAXPOS || this.extended == null && this.value > Int36.MAXPOS);
} }
/** /**
@ -466,7 +483,7 @@ class Int36 {
/* /*
* Set extended to match the sign of the (negated) value. * Set extended to match the sign of the (negated) value.
*/ */
this.extended = (this.value > Int36.MAXSIGNED? 0 : Int36.MAXVAL); this.extended = (this.value > Int36.MAXPOS? 0 : Int36.MAXVAL);
} }
else if (this.value) { else if (this.value) {
/* /*
@ -590,7 +607,7 @@ class Int36 {
*/ */
static validate(num) static validate(num)
{ {
if (num < 0 && num >= Int36.MINSIGNED) { if (num < 0 && num >= Int36.MINNEG) {
num += Int36.BIT36; num += Int36.BIT36;
} }
var value = Math.trunc(Math.abs(num)) % Int36.BIT36; var value = Math.trunc(Math.abs(num)) % Int36.BIT36;
@ -611,8 +628,8 @@ Int36.ERROR = {
Int36.BIT18 = Math.pow(2, 18); // 262,144 Int36.BIT18 = Math.pow(2, 18); // 262,144
Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736 Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736
Int36.MAXSIGNED = Math.pow(2, 35) - 1; // 34,359,738,367 Int36.MAXPOS = Math.pow(2, 35) - 1; // 34,359,738,367
Int36.MINSIGNED = -Math.pow(2, 35); // -34,359,738,368 Int36.MINNEG = -Math.pow(2, 35); // -34,359,738,368
Int36.MAXVAL = Math.pow(2, 36) - 1; // 68,719,476,735 Int36.MAXVAL = Math.pow(2, 36) - 1; // 68,719,476,735