Added alternate Int36 addition/subtraction overflow/underflow detection logic as a verification check
This commit is contained in:
parent
048a10aac6
commit
4de50acdf1
2 changed files with 85 additions and 26 deletions
|
|
@ -139,18 +139,17 @@ test("set 4001");
|
||||||
test("div -5");
|
test("div -5");
|
||||||
|
|
||||||
test("set 0o037777777777 0o777777777777");
|
test("set 0o037777777777 0o777777777777");
|
||||||
|
test("set 0o047777777777 0o777777777777");
|
||||||
// test("set 0o047777777777 0o777777777777");
|
test("set 0o057777777777 0o777777777777");
|
||||||
// test("set 0o057777777777 0o777777777777");
|
test("set 0o067777777777 0o777777777777");
|
||||||
// test("set 0o067777777777 0o777777777777");
|
test("set 0o077777777777 0o777777777777");
|
||||||
// test("set 0o077777777777 0o777777777777");
|
test("set 0o177777777777 0o777777777777");
|
||||||
// test("set 0o177777777777 0o777777777777");
|
test("set 0o277777777777 0o777777777777");
|
||||||
// test("set 0o277777777777 0o777777777777");
|
test("set 0o377777777777 0o777777777777");
|
||||||
// test("set 0o377777777777 0o777777777777");
|
test("set 0o477777777777 0o777777777777");
|
||||||
// test("set 0o477777777777 0o777777777777");
|
test("set 0o577777777777 0o777777777777");
|
||||||
// test("set 0o577777777777 0o777777777777");
|
test("set 0o677777777777 0o777777777777");
|
||||||
// test("set 0o677777777777 0o777777777777");
|
test("set 0o777777777777 0o777777777777");
|
||||||
// test("set 0o777777777777 0o777777777777");
|
|
||||||
|
|
||||||
repl.start({
|
repl.start({
|
||||||
prompt: "int36> ",
|
prompt: "int36> ",
|
||||||
|
|
|
||||||
|
|
@ -78,11 +78,11 @@ class Int36 {
|
||||||
*
|
*
|
||||||
* -Math.pow(2, 53) <= i <= Math.pow(2, 53)
|
* -Math.pow(2, 53) <= i <= Math.pow(2, 53)
|
||||||
*
|
*
|
||||||
* it seems unwise to ever permit the internal value to creep outside the 36-bit range, because
|
* it seems unwise to ever permit our internal values to creep outside the 36-bit range, because
|
||||||
* floating-point operations will drop least-significant bits in favor of most-significant bits when a
|
* 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
|
* 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
|
* 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.
|
* least initially, I prefer to truncate() the results of all 36-bit arithmetic operations immediately.
|
||||||
*
|
*
|
||||||
* Most of the Int36 operations come in two flavors: those that accept numbers, and those that accept
|
* 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
|
* another Int36. The latter are more efficient, because (as just explained) an Int36's internal value
|
||||||
|
|
@ -209,25 +209,53 @@ class Int36 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* truncate(result, original)
|
* truncate(result, operand, fSub)
|
||||||
*
|
*
|
||||||
* The range of valid results (0 - MAXVAL) is divided into two equal sub-ranges: 0 to MAXPOS,
|
* 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
|
* 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 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
|
* 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
|
* error ONLY if the result "wraps across" the midpoint between the two ranges, producing an
|
||||||
* unnaturally small delta (<= MAXPOS).
|
* unnaturally small delta (<= MAXPOS).
|
||||||
*
|
*
|
||||||
|
* This can be confirmed independently by examining the sign bits (BIT35) of the original value
|
||||||
|
* (V), the operand (O), the result (R), as well as two intermediate calculations, VR = (V ^ R)
|
||||||
|
* and OR = (O ^ R), and a final calculation: E = (VR & OR). In the case of subtraction (fSub),
|
||||||
|
* OV replaces OR.
|
||||||
|
*
|
||||||
|
* V O R VR OR E
|
||||||
|
* - - - -- -- -
|
||||||
|
* 0 0 0 0 0 0
|
||||||
|
* 0 0 1 1 1 1 (adding positive to positive yielded negative: overflow)
|
||||||
|
* 0 1 0 0 1 0
|
||||||
|
* 0 1 1 1 0 0
|
||||||
|
* 1 0 0 1 0 0
|
||||||
|
* 1 0 1 0 1 0
|
||||||
|
* 1 1 0 1 1 1 (adding negative to negative yielded positive: underflow)
|
||||||
|
* 1 1 1 0 0 0
|
||||||
|
*
|
||||||
|
* V O R VR OV E
|
||||||
|
* - - - -- -- -
|
||||||
|
* 0 0 0 0 0 0
|
||||||
|
* 0 0 1 1 0 0
|
||||||
|
* 0 1 0 0 1 0
|
||||||
|
* 0 1 1 1 1 1 (subtracting negative from positive yielded negative: overflow)
|
||||||
|
* 1 0 0 1 1 1 (subtracting positive from negative yielded positive: underflow)
|
||||||
|
* 1 0 1 0 1 0
|
||||||
|
* 1 1 0 1 0 0
|
||||||
|
* 1 1 1 0 0 0
|
||||||
|
*
|
||||||
* 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
|
||||||
* a non-integer result (eg, div()), it's the caller's responsibility to deal with that first.
|
* a non-integer result (eg, div()), it's the caller's responsibility to deal with that first.
|
||||||
*
|
*
|
||||||
* @this {Int36}
|
* @this {Int36}
|
||||||
* @param {number} result
|
* @param {number} result
|
||||||
* @param {number} [original]
|
* @param {number} [operand]
|
||||||
|
* @param {boolean} [fSub] (true if operand was subtracted)
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
truncate(result, original)
|
truncate(result, operand, fSub)
|
||||||
{
|
{
|
||||||
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)");
|
||||||
|
|
@ -243,11 +271,33 @@ class Int36 {
|
||||||
|
|
||||||
result %= Int36.BIT36;
|
result %= Int36.BIT36;
|
||||||
|
|
||||||
if (original !== undefined && (result > Int36.MAXPOS) != (original > Int36.MAXPOS)) {
|
/*
|
||||||
var delta = result - original;
|
* We don't actually need to know what the operand was to determine overflow or underflow
|
||||||
if (Math.abs(delta) <= Int36.MAXPOS) {
|
* for addition or subtraction, just the original value (this.value) the new value (result).
|
||||||
this.error |= (delta > 0? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW);
|
*
|
||||||
|
* We do, however, need to know the operand if we want to confirm our error calculation using
|
||||||
|
* the truth table above, which requires examining the sign bits of all the inputs and outputs.
|
||||||
|
*/
|
||||||
|
if (operand !== undefined) {
|
||||||
|
/*
|
||||||
|
* Calculating V, R, O, and E as described above is somewhat tedious, because bits
|
||||||
|
* above bit 31 cannot be accessed directly; we shift all the sign bits down to bit 0
|
||||||
|
* using division first. We don't need to truncate the results, because the subsequent
|
||||||
|
* bit-wise operations perform truncation automatically.
|
||||||
|
*/
|
||||||
|
var e = 0;
|
||||||
|
if (DEBUG) {
|
||||||
|
var v = this.value / Int36.BIT35, r = result / Int36.BIT35, o = operand / Int36.BIT35;
|
||||||
|
e = ((v ^ r) & (o ^ (fSub? v : r))) & 1;
|
||||||
}
|
}
|
||||||
|
if ((result > Int36.MAXPOS) != (this.value > Int36.MAXPOS)) {
|
||||||
|
var delta = result - this.value;
|
||||||
|
if (Math.abs(delta) <= Int36.MAXPOS) {
|
||||||
|
this.error |= (delta > 0 ? Int36.ERROR.OVERFLOW : Int36.ERROR.UNDERFLOW);
|
||||||
|
if (DEBUG && (delta > 0) != !(e & v)) e = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (DEBUG && (!this.error) != (!e)) console.log("overflow mismatch");
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +310,7 @@ class Int36 {
|
||||||
*/
|
*/
|
||||||
add(i36)
|
add(i36)
|
||||||
{
|
{
|
||||||
this.value = this.truncate(this.value + i36.value, this.value);
|
this.value = this.truncate(this.value + i36.value, i36.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -271,7 +321,8 @@ class Int36 {
|
||||||
*/
|
*/
|
||||||
addNum(num)
|
addNum(num)
|
||||||
{
|
{
|
||||||
this.value = this.truncate(this.value + Int36.validate(num), this.value);
|
num = Int36.validate(num);
|
||||||
|
this.value = this.truncate(this.value + num, num);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -282,7 +333,7 @@ class Int36 {
|
||||||
*/
|
*/
|
||||||
sub(i36)
|
sub(i36)
|
||||||
{
|
{
|
||||||
this.value = this.truncate(this.value - i36.value, this.value);
|
this.value = this.truncate(this.value - i36.value, i36.value, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -293,7 +344,8 @@ class Int36 {
|
||||||
*/
|
*/
|
||||||
subNum(num)
|
subNum(num)
|
||||||
{
|
{
|
||||||
this.value = this.truncate(this.value - Int36.validate(num), this.value);
|
num = Int36.validate(num);
|
||||||
|
this.value = this.truncate(this.value - num, num, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -599,11 +651,18 @@ class Int36 {
|
||||||
/**
|
/**
|
||||||
* validate(num)
|
* validate(num)
|
||||||
*
|
*
|
||||||
|
* This ensures that any incoming (external) 36-bit values conform to our internal requirements.
|
||||||
|
*
|
||||||
* @param {number} num
|
* @param {number} num
|
||||||
* @return {number}
|
* @return {number}
|
||||||
*/
|
*/
|
||||||
static validate(num)
|
static validate(num)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
* Although it's expected that most callers will supply unsigned 36-bit values, we're nice about
|
||||||
|
* converting any signed values to their unsigned (two's complement) counterpart, provided they are
|
||||||
|
* within the acceptable range. Any signed values outside that range will be dealt with afterward.
|
||||||
|
*/
|
||||||
if (num < 0 && num >= Int36.MINNEG) {
|
if (num < 0 && num >= Int36.MINNEG) {
|
||||||
num += Int36.BIT36;
|
num += Int36.BIT36;
|
||||||
}
|
}
|
||||||
|
|
@ -623,6 +682,7 @@ Int36.ERROR = {
|
||||||
};
|
};
|
||||||
|
|
||||||
Int36.BIT18 = Math.pow(2, 18); // 262,144
|
Int36.BIT18 = Math.pow(2, 18); // 262,144
|
||||||
|
Int36.BIT35 = Math.pow(2, 35); // 34,359,738,368 (aka the sign bit)
|
||||||
Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736
|
Int36.BIT36 = Math.pow(2, 36); // 68,719,476,736
|
||||||
|
|
||||||
Int36.MAXPOS = Math.pow(2, 35) - 1; // 34,359,738,367
|
Int36.MAXPOS = Math.pow(2, 35) - 1; // 34,359,738,367
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue