diff --git a/lib/function/bitwise/bitAnd.js b/lib/function/bitwise/bitAnd.js index 42247e87d..ed0fe1be8 100644 --- a/lib/function/bitwise/bitAnd.js +++ b/lib/function/bitwise/bitAnd.js @@ -11,7 +11,9 @@ module.exports = function (math, config) { isBoolean = util['boolean'].isBoolean, isNumber = util.number.isNumber, isString = util.string.isString, - isCollection = collection.isCollection; + isCollection = collection.isCollection, + + bigBitAnd = util.bignumber.and; /** * Bitwise AND two values, `x & y`. @@ -67,8 +69,13 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - if (isNumber(y) || y instanceof BigNumber) { - return x.and(y); + // try to convert to big number + if (isNumber(y)) { + y = BigNumber.convert(y); + } + + if (y instanceof BigNumber) { + return bigBitAnd(x, y); } // downgrade to Number @@ -81,7 +88,7 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - return x.and(y); + return bigBitAnd(x, y); } // downgrade to Number diff --git a/lib/function/bitwise/bitNot.js b/lib/function/bitwise/bitNot.js index 4c8ce3774..03e1617d4 100644 --- a/lib/function/bitwise/bitNot.js +++ b/lib/function/bitwise/bitNot.js @@ -11,7 +11,9 @@ module.exports = function (math, config) { isBoolean = util['boolean'].isBoolean, isNumber = util.number.isNumber, isString = util.string.isString, - isCollection = collection.isCollection; + isCollection = collection.isCollection, + + bigBitNot = util.bignumber.not; /** * Bitwise NOT value, `~x`. @@ -45,7 +47,7 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - return x.not(); + return bigBitNot(x); } if (isCollection(x)) { diff --git a/lib/function/bitwise/bitOr.js b/lib/function/bitwise/bitOr.js index 88fcd94f8..564cdb1d2 100644 --- a/lib/function/bitwise/bitOr.js +++ b/lib/function/bitwise/bitOr.js @@ -11,7 +11,9 @@ module.exports = function (math, config) { isBoolean = util['boolean'].isBoolean, isNumber = util.number.isNumber, isString = util.string.isString, - isCollection = collection.isCollection; + isCollection = collection.isCollection, + + bigBitOr = util.bignumber.or; /** * Bitwise OR two values, `x | y`. @@ -68,8 +70,13 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - if (isNumber(y) || y instanceof BigNumber) { - return x.or(y); + // try to convert to big number + if (isNumber(y)) { + y = BigNumber.convert(y); + } + + if (y instanceof BigNumber) { + return bigBitOr(x, y); } // downgrade to Number @@ -82,7 +89,7 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - return x.or(y); + return bigBitOr(x, y); } // downgrade to Number diff --git a/lib/function/bitwise/bitXor.js b/lib/function/bitwise/bitXor.js index 93b119fb6..27339a96a 100644 --- a/lib/function/bitwise/bitXor.js +++ b/lib/function/bitwise/bitXor.js @@ -12,7 +12,9 @@ module.exports = function (math, config) { isNumber = util.number.isNumber, isString = util.string.isString, isUnit = Unit.isUnit, - isCollection = collection.isCollection; + isCollection = collection.isCollection, + + bigBitXor = util.bignumber.xor; /** * Bitwise XOR two values, `x ^ y`. @@ -68,8 +70,13 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - if (isNumber(y) || y instanceof BigNumber) { - return x.xor(y); + // try to convert to big number + if (isNumber(y)) { + y = BigNumber.convert(y); + } + + if (y instanceof BigNumber) { + return bigBitXor(x, y); } // downgrade to Number @@ -82,7 +89,7 @@ module.exports = function (math, config) { } if (x instanceof BigNumber) { - return x.xor(y); + return bigBitXor(x, y); } // downgrade to Number diff --git a/lib/function/bitwise/leftShift.js b/lib/function/bitwise/leftShift.js index 959e5ef47..de591f37b 100644 --- a/lib/function/bitwise/leftShift.js +++ b/lib/function/bitwise/leftShift.js @@ -11,7 +11,9 @@ module.exports = function (math, config) { isBoolean = util['boolean'].isBoolean, isNumber = util.number.isNumber, isString = util.string.isString, - isCollection = collection.isCollection; + isCollection = collection.isCollection, + + bigLeftShift = util.bignumber.leftShift; /** * Bitwise left logical shift one value by another values, `x << y`. @@ -69,20 +71,15 @@ module.exports = function (math, config) { if (x instanceof BigNumber) { if (isNumber(y) || y instanceof BigNumber) { - return x.leftShift(y); + return bigLeftShift(x, y); } // downgrade to Number return leftShift(x.toNumber(), y); } if (y instanceof BigNumber) { - // try to convert to big number if (isNumber(x)) { - x = BigNumber.convert(x); - } - - if (x instanceof BigNumber) { - return x.leftShift(y); + return bigLeftShift(x, y); } // downgrade to Number diff --git a/lib/function/bitwise/rightArithShift.js b/lib/function/bitwise/rightArithShift.js index b9c9ea951..8750cfc1b 100644 --- a/lib/function/bitwise/rightArithShift.js +++ b/lib/function/bitwise/rightArithShift.js @@ -11,7 +11,9 @@ module.exports = function (math, config) { isBoolean = util['boolean'].isBoolean, isNumber = util.number.isNumber, isString = util.string.isString, - isCollection = collection.isCollection; + isCollection = collection.isCollection, + + bigRightShift = util.bignumber.rightShift; /** * Bitwise right arithmetic shift one value by another values, `x >> y`. @@ -69,7 +71,7 @@ module.exports = function (math, config) { if (x instanceof BigNumber) { if (isNumber(y) || y instanceof BigNumber) { - return x.rightShift(y); + return bigRightShift(x, y); } // downgrade to Number @@ -78,11 +80,7 @@ module.exports = function (math, config) { if (y instanceof BigNumber) { // try to convert to big number if (isNumber(x)) { - x = BigNumber.convert(x); - } - - if (x instanceof BigNumber) { - return x.rightShift(y); + return bigRightShift(x, y); } // downgrade to Number diff --git a/lib/util/bignumber.js b/lib/util/bignumber.js index 483670256..5ed7b98e5 100644 --- a/lib/util/bignumber.js +++ b/lib/util/bignumber.js @@ -13,6 +13,10 @@ exports.isBigNumber = function (value) { return (value instanceof BigNumber); }; + +/* BigNumber constants. */ + + /** * Calculate BigNumber e * @param {Number} precision @@ -98,6 +102,367 @@ exports.tau = function (precision) { return new Big(2).times(pi); }; + +/* BigNumber functions. */ + + +/* + * Special Cases: + * N & n = N + * n & 0 = 0 + * n & -1 = n + * n & n = n + * I & I = I + * -I & -I = -I + * I & -I = 0 + * I & n = n + * I & -n = I + * -I & n = 0 + * -I & -n = -I + * + * @param {BigNumber} value + * @param {BigNumber} value + * @return {BigNumber} Result of `x` & `y`, is fully precise + * + */ +exports.and = function(x, y) { + var BigNumber = x['constructor']; + if (x.isNaN() || y.isNaN()) { + return new BigNumber(NaN); + } + + x = x.trunc(); + y = y.trunc(); + if (x.isZero() || y.eq(-1) || x.eq(y)) { + return x; + } + if (y.isZero() || x.eq(-1)) { + return y; + } + + if (!x.isFinite() || !y.isFinite()) { + if (!x.isFinite() && !y.isFinite()) { + if (x.isNegative() == y.isNegtive()) { + return x; + } + return new BigNumber(0); + } + if (!x.isFinite()) { + if (y.isNegative()) { + return x; + } + if (x.isNegative()) { + return new BigNumber(0); + } + return y; + } + if (!y.isFinite()) { + if (x.isNegative()) { + return y; + } + if (y.isNegative()) { + return new BigNumber(0); + } + return x; + } + } + return bitwise(x, y, function (a, b) { return a & b }); +}; + +/* + * Special Cases: + * n << -n = N + * n << N = N + * N << n = N + * n << 0 = n + * 0 << n = 0 + * I << I = N + * I << n = I + * n << I = I + * + * @param {Number | BigNumber} value + * @param {Number | BigNumber} value + * @return {BigNumber} Result of `x` << `y` + * + */ +exports.leftShift = function (x, y) { + var BigNumber, truncY; + if (isNumber(x)) { + BigNumber = y['constructor']; + + if (y.isZero() && y.isNegative()) { + y['s'] = -y['s']; + } + + truncY = y.trunc(); + if (truncY.isNegative() && !truncY.isZero()) { + return new BigNumber(NaN); + } + + x = new BigNumber(x > 0 + ? Math.floor(x) + : Math.ceil(x)); + } else { + BigNumber = x['constructor']; + x = x.trunc(); + } + + if (isNumber(y)) { + y = y > 0 + ? Math.floor(y) + : Math.ceil(y); + + if (y < 0) { + return new BigNumber(NaN); + } + if (y == 0 || x == 0) { + return x; + } + if (y == Infinity && !x.isFinite()) { + return new BigNumber(NaN); + } + if (y < 55) { + return x.times(Math.pow(2, y) + ''); + } + return x.times(new BigNumber(2).pow(y)); + } + + if (!truncY) { + if (y.isNegative() && y.isZero()) { + y['s'] = -y['s']; + } + + truncY = y.trunc(); + if (truncY.isNegative() && !truncY.isZero()) { + return new BigNumber(NaN); + } + } + + if (x.isZero() || truncY.isZero()) { + return x; + } + if (!x.isFinite() && !truncY.isFinite()) { + return new BigNumber(NaN); + } + if (truncY.lt(55)) { + return x.times(Math.pow(2, truncY.toNumber()) + ''); + } + return x.times(new BigNumber(2).pow(truncY)); +}; + +/* + * @param {BigNumber} value + * @return {BigNumber} Result of ~`x`, fully precise + * + */ +exports.not = function (x) { + var BigNumber = x['constructor']; + var prevPrec = BigNumber['precision']; + BigNumber['precision'] = 1E9; + + var x = x.trunc().plus(BigNumber['ONE']); + x['s'] = -x['s'] || null; + + BigNumber['precision'] = prevPrec; + return x; +}; + +/* + * Special Cases: + * N | n = N + * n | 0 = n + * n | -1 = -1 + * n | n = n + * I | I = I + * -I | -I = -I + * I | -n = -1 + * I | -I = -1 + * I | n = I + * -I | n = -I + * -I | -n = -n + * + * @param {BigNumber} value + * @param {BigNumber} value + * @return {BigNumber} Result of `x` | `y`, fully precise + * + */ +exports.or = function (x, y) { + var BigNumber = x['constructor']; + if (x.isNaN() || y.isNaN()) { + return new BigNumber(NaN); + } + + x = x.trunc(); + y = y.trunc(); + if (x.isZero() || y.eq(-1) || x.eq(y)) { + return y; + } + if (y.isZero() || x.eq(-1)) { + return x; + } + + if (!x.isFinite() || !y.isFinite()) { + if ((!x.isFinite() && !x.isNegative() && y.isNegative()) || + (x.isNegative() && !y.isNegative() && !y.isFinite())) { + return new BigNumber(-1); + } + if (x.isNegative() && y.isNegative()) { + return x.isFinite() + ? x + : y; + } + return x.isFinite() + ? y + : x; + } + return bitwise(x, y, function (a, b) { return a | b }); +}; + +/* + * Special Cases: + * n >> -n = N + * n >> N = N + * N >> n = N + * I >> I = N + * n >> 0 = n + * I >> n = I + * -I >> n = -I + * -I >> I = -I + * n >> I = I + * -n >> I = -1 + * 0 >> n = 0 + * + * @param {Number | BigNumber} value + * @param {Number | BigNumber} value + * @return {BigNumber} Result of `x` >> `y` + * + */ +exports.rightShift = function (x, y) { + var BigNumber, truncY; + if (isNumber(x)) { + BigNumber = y['constructor']; + + if (y.isZero() && y.isNegative()) { + y['s'] = -y['s']; + } + + truncY = y.trunc(); + if (truncY.isNegative()) { + return new BigNumber(NaN); + } + + x = new BigNumber(x > 0 + ? Math.floor(x) + : Math.ceil(x)); + } else { + BigNumber = x['constructor']; + x = x.trunc(); + } + + if (isNumber(y)) { + y = y > 0 + ? Math.floor(y) + : Math.ceil(y); + + if (y < 0) { + return new BigNumber(NaN); + } + if (y == 0 || x == 0) { + return x; + } + if (x < 0 && y == Infinity) { + return new BigNumber(-1); + } + if (y == Infinity && !x.isFinite()) { + return new BigNumber(NaN); + } + if (y < 55) { + return x.div(Math.pow(2, y) + '').floor(); + } + return x.div(new BigNumber(2).pow(y)).floor(); + } + + if (!truncY) { + if (y.isNegative() && y.isZero()) { + y['s'] = -y['s']; + } + + truncY = y.trunc(); + if (truncY.isNegative()) { + return new BigNumber(NaN); + } + } + + if (x.isZero() || truncY.isZero()) { + return x; + } + if (x.isNegative() && !truncY.isFinite()) { + return new BigNumber(-1); + } + if (!x.isFinite() && !truncY.isFinite()) { + return new BigNumber(NaN); + } + if (truncY.lt(55)) { + return x.div(Math.pow(2, truncY.toNumber()) + '').floor(); + } + return x.div(new BigNumber(2).pow(truncY)).floor(); +}; + +/* + * Special Cases: + * N ^ n = N + * n ^ 0 = n + * n ^ n = 0 + * n ^ -1 = ~n + * I ^ n = I + * I ^ -n = -I + * I ^ -I = -1 + * -I ^ n = -I + * -I ^ -n = I + * + * @param {BigNumber} value + * @param {BigNumber} value + * @return {BigNumber} Result of `x` ^ `y`, fully precise + * + */ +exports.xor = function (x, y) { + var BigNumber = x['constructor']; + if (x.isNaN() || y.isNaN()) { + return new BigNumber(NaN); + } + + x = x.trunc(); + y = y.trunc(); + if (x.isZero()) { + return y; + } + if (y.isZero()) { + return x; + } + + if (x.eq(y)) { + return new BigNumber(0); + } + + if (x.eq(-1)) { + return exports.not(y); + } + if (y.eq(-1)) { + return exports.not(x); + } + + if (!x.isFinite() || !y.isFinite()) { + if (!x.isFinite() && !y.isFinite()) { + return new BigNumber(-1); + } + return new BigNumber(x.isNegative() == y.isNegative() + ? Infinity + : -Infinity); + } + return bitwise(x, y, function (a, b) { return a ^ b }); +}; + + /** * Convert a number to a formatted string representation. * @@ -281,3 +646,127 @@ exports.toFixed = function(value, precision) { // Note: the (precision || 0) is needed as the toFixed of BigNumber has an // undefined default precision instead of 0. }; + + +/* Private functions. */ + + +function bitwise(x, y, func) { + var BigNumber = x['constructor']; + + var xBits, yBits; + var xSign = +(x['s'] < 0); + var ySign = +(y['s'] < 0); + if (xSign) { + xBits = decToBinary(coefficientToString(exports.not(x))); + for (var i = 0; i < xBits.length; ++i) { + xBits[i] ^= 1; + } + } else { + xBits = decToBinary(coefficientToString(x)); + } + if (ySign) { + yBits = decToBinary(coefficientToString(exports.not(y))); + for (var i = 0; i < yBits.length; ++i) { + yBits[i] ^= 1; + } + } else { + yBits = decToBinary(coefficientToString(y)); + } + + var minBits, maxBits, minSign; + if (xBits.length <= yBits.length) { + minBits = xBits; + maxBits = yBits; + minSign = xSign; + } else { + minBits = yBits; + maxBits = xBits; + minSign = ySign; + } + + var shortLen = minBits.length; + var longLen = maxBits.length; + var expFuncVal = func(xSign, ySign) ^ 1; + var outVal = new BigNumber(expFuncVal ^ 1); + var twoPower = BigNumber['ONE']; + var two = new BigNumber(2); + + var prevPrec = BigNumber['precision']; + BigNumber['precision'] = 1E9; + + while (shortLen > 0) { + if (func(minBits[--shortLen], maxBits[--longLen]) == expFuncVal) { + outVal = outVal.plus(twoPower); + } + twoPower = twoPower.times(two); + } + while (longLen > 0) { + if (func(minSign, maxBits[--longLen]) == expFuncVal) { + outVal = outVal.plus(twoPower); + } + twoPower = twoPower.times(two); + } + + BigNumber['precision'] = prevPrec; + + if (expFuncVal == 0) { + outVal['s'] = -outVal['s']; + } + return outVal; +} + + +/* Private functions extracted from decimal.js, and edited to specialize. */ + + +function coefficientToString(x) { + var a = x['c']; + var r = a[0] + ''; + + for (var i = 1; i < a.length; ++i) { + var s = a[i] + ''; + for (var z = 7 - s.length; z--; ) { + s = '0' + s; + } + + r += s; + } + + var j; + for (j = r.length - 1; r.charAt(j) == '0'; --j); + + var xe = x['e']; + var str = r.slice(0, j + 1 || 1); + var strL = str.length; + if (xe > 0) { + if (++xe > strL) { + // Append zeros. + for (xe -= strL; xe--; str += '0'); + } else if (xe < strL) { + str = str.slice(0, xe) + '.' + str.slice(xe); + } + } + return str; +} + +function decToBinary(str) { + var arr = [0]; + for (var i = 0; i < str.length; ) { + for (var arrL = arr.length; arrL--; arr[arrL] *= 10); + + arr[0] += str.charAt(i++) << 0; // convert to int + for (var j = 0; j < arr.length; ++j) { + if (arr[j] > 1) { + if (arr[j + 1] == null) { + arr[j + 1] = 0; + } + + arr[j + 1] += arr[j] >> 1; + arr[j] &= 1; + } + } + } + + return arr.reverse(); +} diff --git a/test/function/bitwise/bitAnd.test.js b/test/function/bitwise/bitAnd.test.js index 9490baaa1..a15ed0a05 100644 --- a/test/function/bitwise/bitAnd.test.js +++ b/test/function/bitwise/bitAnd.test.js @@ -41,6 +41,7 @@ describe('bitAnd', function () { assert.deepEqual(bitAnd(bignumber('1.0e+31'), bignumber('1.0e+32')), bignumber('8726602014714682917961003433984')); assert.deepEqual(bitAnd(bignumber('-1.0e+31'), bignumber('1.0e+32')), bignumber('91273397985285317082038996566016')); assert.deepEqual(bitAnd(bignumber('1.0e+31'), bignumber('-1.0e+32')), bignumber('1273397985285317082036849082368')); + assert.deepEqual(bitAnd(bignumber('2.1877409333271352879E+75'), bignumber('-3.220131224058161211554E+42')), bignumber('2187740933327135287899999999999996863578490213829130431270426161710498840576')); }); it('should bitwise and mixed numbers and bignumbers', function () { diff --git a/test/function/bitwise/bitNot.test.js b/test/function/bitwise/bitNot.test.js index c79034e27..d354e932d 100644 --- a/test/function/bitwise/bitNot.test.js +++ b/test/function/bitwise/bitNot.test.js @@ -21,21 +21,16 @@ describe('bitNot', function () { assert.equal(bitNot('-86e2'), 8599); }); - it('should return bignumber bitwise not on a string', function() { - assert.deepEqual(bitNot(bignumber('2')), bignumber(-3)); - assert.deepEqual(bitNot(bignumber('-2')), bignumber(1)); - assert.deepEqual(bitNot(bignumber('1.2345e30')), bignumber('-1234500000000000000000000000001')); - }); - it('should perform bitwise not of a number', function () { assert.deepEqual(bitNot(2), -3); assert.deepEqual(bitNot(-2), 1); assert.deepEqual(bitNot(0), -1); }); - it('should perform bitwise not of a big number', function() { + it('should perform bitwise not of a bignumber', function() { assert.deepEqual(bitNot(bignumber(2)), bignumber(-3)); assert.deepEqual(bitNot(bignumber(-2)), bignumber(1)); + assert.deepEqual(bitNot(bignumber('1.2345e30')), bignumber('-1234500000000000000000000000001')); }); it('should throw an error if used with a unit', function() { diff --git a/test/function/bitwise/leftShift.test.js b/test/function/bitwise/leftShift.test.js index 0f487b50f..eaed4fb10 100644 --- a/test/function/bitwise/leftShift.test.js +++ b/test/function/bitwise/leftShift.test.js @@ -1,5 +1,6 @@ // test leftShift var assert = require('assert'), + approx = require('../../../tools/approx'), error = require('../../../lib/error/index'), math = require('../../../index'), bignumber = math.bignumber, @@ -41,13 +42,22 @@ describe('leftShift', function () { assert.deepEqual(leftShift(bignumber(2), bignumber(3)), bignumber(16)); assert.deepEqual(leftShift(bignumber(500), bignumber(100)), bignumber('633825300114114700748351602688000')); assert.deepEqual(leftShift(bignumber(-1), bignumber(2)), bignumber(-4)); + assert.deepEqual(leftShift(bignumber(-1.9), bignumber(2.9)), bignumber(-4)); + assert.deepEqual(leftShift(bignumber(0), bignumber(-2)).toString(), 'NaN'); + assert.deepEqual(leftShift(bignumber(Infinity), bignumber(2)), bignumber(Infinity)); + assert.deepEqual(leftShift(bignumber(Infinity), bignumber(Infinity)).toString(), 'NaN'); }); it('should left shift mixed numbers and bignumbers', function () { assert.deepEqual(leftShift(bignumber(2), 3), bignumber(16)); + assert.deepEqual(leftShift(bignumber(500), 100), bignumber('633825300114114700748351602688000')); assert.deepEqual(leftShift(2, bignumber(3)), bignumber(16)); assert.deepEqual(leftShift(-1, bignumber(2)), bignumber(-4)); + assert.deepEqual(leftShift(-1.9, bignumber(2.9)), bignumber(-4)); assert.deepEqual(leftShift(bignumber(-1), 2), bignumber(-4)); + assert.deepEqual(leftShift(bignumber(-1.9), 2.9), bignumber(-4)); + assert.deepEqual(leftShift(bignumber(0), -2).toString(), 'NaN'); + assert.deepEqual(leftShift(bignumber(Infinity), Infinity).toString(), 'NaN'); }); it('should left shift mixed booleans and bignumbers', function () { diff --git a/test/function/bitwise/rightArithShift.test.js b/test/function/bitwise/rightArithShift.test.js index 86c9817b6..241085582 100644 --- a/test/function/bitwise/rightArithShift.test.js +++ b/test/function/bitwise/rightArithShift.test.js @@ -51,13 +51,21 @@ describe('rightArithShift', function () { assert.deepEqual(rightArithShift(bignumber(17), bignumber(3)), bignumber(2)); assert.deepEqual(rightArithShift(bignumber('633825300114114700748351602688000'), bignumber(100)), bignumber(500)); assert.deepEqual(rightArithShift(bignumber(-17), bignumber(3)), bignumber(-3)); + assert.deepEqual(rightArithShift(bignumber(-17), bignumber(-3)).toString(), 'NaN'); + assert.deepEqual(rightArithShift(bignumber(Infinity), bignumber(Infinity)).toString(), 'NaN'); + assert.deepEqual(rightArithShift(bignumber(-Infinity), bignumber(Infinity)), bignumber(-1)); }); it('should right arithmetically shift mixed numbers and bignumbers', function () { assert.deepEqual(rightArithShift(bignumber(17), 3), bignumber(2)); assert.deepEqual(rightArithShift(bignumber('-633825300114114700748351602688000'), 100), bignumber(-500)); + assert.deepEqual(rightArithShift(bignumber(-17), -3).toString(), 'NaN'); assert.deepEqual(rightArithShift(17, bignumber(3)), bignumber(2)); assert.deepEqual(rightArithShift(-17, bignumber(3)), bignumber(-3)); + assert.deepEqual(rightArithShift(-3, bignumber(-17)).toString(), 'NaN'); + assert.deepEqual(rightArithShift(bignumber(-Infinity), Infinity), bignumber(-1)); + assert.deepEqual(rightArithShift(bignumber(Infinity), Infinity).toString(), 'NaN'); + assert.deepEqual(rightArithShift(Infinity, bignumber(Infinity)).toString(), 'NaN'); }); it('should right arithmetically shift mixed booleans and bignumbers', function () {