michael@0: // The nearest representable values to +1.0. michael@0: const ONE_PLUS_EPSILON = 1 + Math.pow(2, -52); // 0.9999999999999999 michael@0: const ONE_MINUS_EPSILON = 1 - Math.pow(2, -53); // 1.0000000000000002 michael@0: michael@0: { michael@0: var fail = function (msg) { michael@0: var exc = new Error(msg); michael@0: try { michael@0: // Try to improve on exc.fileName and .lineNumber; leave exc.stack michael@0: // alone. We skip two frames: fail() and its caller, an assertX() michael@0: // function. michael@0: var frames = exc.stack.trim().split("\n"); michael@0: if (frames.length > 2) { michael@0: var m = /@([^@:]*):([0-9]+)$/.exec(frames[2]); michael@0: if (m) { michael@0: exc.fileName = m[1]; michael@0: exc.lineNumber = +m[2]; michael@0: } michael@0: } michael@0: } catch (ignore) { throw ignore;} michael@0: throw exc; michael@0: }; michael@0: michael@0: var ENDIAN; // 0 for little-endian, 1 for big-endian. michael@0: michael@0: // Return the difference between the IEEE 754 bit-patterns for a and b. michael@0: // michael@0: // This is meaningful when a and b are both finite and have the same michael@0: // sign. Then the following hold: michael@0: // michael@0: // * If a === b, then diff(a, b) === 0. michael@0: // michael@0: // * If a !== b, then diff(a, b) === 1 + the number of representable values michael@0: // between a and b. michael@0: // michael@0: var f = new Float64Array([0, 0]); michael@0: var u = new Uint32Array(f.buffer); michael@0: var diff = function (a, b) { michael@0: f[0] = a; michael@0: f[1] = b; michael@0: //print(u[1].toString(16) + u[0].toString(16) + " " + u[3].toString(16) + u[2].toString(16)); michael@0: return Math.abs((u[3-ENDIAN] - u[1-ENDIAN]) * 0x100000000 + u[2+ENDIAN] - u[0+ENDIAN]); michael@0: }; michael@0: michael@0: // Set ENDIAN to the platform's endianness. michael@0: ENDIAN = 0; // try little-endian first michael@0: if (diff(2, 4) === 0x100000) // exact wrong answer we'll get on a big-endian platform michael@0: ENDIAN = 1; michael@0: assertEq(diff(2,4), 0x10000000000000); michael@0: assertEq(diff(0, Number.MIN_VALUE), 1); michael@0: assertEq(diff(1, ONE_PLUS_EPSILON), 1); michael@0: assertEq(diff(1, ONE_MINUS_EPSILON), 1); michael@0: michael@0: var assertNear = function assertNear(a, b, tolerance=1) { michael@0: if (!Number.isFinite(b)) { michael@0: fail("second argument to assertNear (expected value) must be a finite number"); michael@0: } else if (Number.isNaN(a)) { michael@0: fail("got NaN, expected a number near " + b); michael@0: } else if (!Number.isFinite(a)) { michael@0: if (b * Math.sign(a) < Number.MAX_VALUE) michael@0: fail("got " + a + ", expected a number near " + b); michael@0: } else { michael@0: // When the two arguments do not have the same sign bit, diff() michael@0: // returns some huge number. So if b is positive or negative 0, michael@0: // make target the zero that has the same sign bit as a. michael@0: var target = b === 0 ? a * 0 : b; michael@0: var err = diff(a, target); michael@0: if (err > tolerance) { michael@0: fail("got " + a + ", expected a number near " + b + michael@0: " (relative error: " + err + ")"); michael@0: } michael@0: } michael@0: }; michael@0: }