Repository: josdejong/mathjs
Pull request #266 (2015-01-12 21:09:35)

Cleanup and factorial fix.
I cleaned up the bignumber syntax, and fixed what was causing a bug on my local machine (also made the precisions match-up between `tau` and `pi` in order to take advantage of the memiozation).

I also realized that there was a precision carry-over error in factorial, and this has been fixed as well.


Patch:
diff --git a/lib/function/probability/factorial.js b/lib/function/probability/factorial.js
index 26f05fc75..b56bda806 100644
--- a/lib/function/probability/factorial.js
+++ b/lib/function/probability/factorial.js
@@ -34,7 +34,7 @@ module.exports = function (math) {
    * @return {Number | BigNumber | Array | Matrix}    The factorial of `n`
    */
   math.factorial = function factorial (n) {
-    var value, res;
+    var value, res, preciseFacs;
 
     if (arguments.length != 1) {
       throw new math.error.ArgumentsError('factorial', arguments.length, 1);
@@ -53,23 +53,33 @@ module.exports = function (math) {
         return new BigNumber(n);
       }
 
-      n = n.toNumber();
-      if (n < fac.length) {
-        return (n < 21)
-          ? new BigNumber(fac[n])
-          : fac[n];
+      n = n.toNumber();   // Should definitely be below Number.MAX_VALUE  
+      if (n < smallBigFacs.length) {
+        return BigNumber.convert(smallBigFacs[n]).toSD(math.precision);
+      }
+
+      // adjust n do align with the precision specific tables
+      n -= smallBigFacs.length;
+      if (preciseFacs = bigBigFacs[math.precision]) {
+        if (preciseFacs[n]) {
+          return preciseFacs[n];
+        }
+        res = preciseFacs[preciseFacs.length-1];
+      } else {
+        preciseFacs = [];
+        res = BigNumber.convert(smallBigFacs[smallBigFacs.length-1])
+          .toSD(math.precision);
       }
 
       var one = new BigNumber(1);
-      value = new BigNumber(fac.length);
-      res = fac[fac.length - 1];
-      for (var i = fac.length; i < n; ++i) {
+      value = new BigNumber(preciseFacs.length + smallBigFacs.length);
+      for (var i = preciseFacs.length; i < n; ++i) {
         res = res.times(value);
         value = value.plus(one);
-        fac[i] = res;
+        preciseFacs[i] = res;
       }
 
-      return fac[n] = res.times(value);
+      return preciseFacs[n] = res.times(value);
     }
 
     if (isBoolean(n) || n === null) {
@@ -92,8 +102,11 @@ module.exports = function (math) {
     return n.isInteger() && (!n.isNegative() || n.isZero());
   };
 
-  // 0-21! values
-  var fac = [
+  // 21! >= values for each precision
+  var bigBigFacs = [];
+
+  // 0-20! values
+  var smallBigFacs = [
     1,
     1,
     2,
@@ -114,7 +127,6 @@ module.exports = function (math) {
     355687428096000,
     6402373705728000,
     121645100408832000,
-    2432902008176640000,
-    new BigNumber('51090942171709440000')
+    2432902008176640000
   ]
 };
diff --git a/lib/util/bignumber.js b/lib/util/bignumber.js
index b54fb67ce..a26a72592 100644
--- a/lib/util/bignumber.js
+++ b/lib/util/bignumber.js
@@ -104,7 +104,7 @@ exports.and = function(x, y) {
     throw new Error('Parameters in function bitAnd must be integer numbers');
   }
 
-  var BigNumber = x['constructor'];
+  var BigNumber = x.constructor;
   if (x.isNaN() || y.isNaN()) {
     return new BigNumber(NaN);
   }
@@ -193,7 +193,7 @@ exports.leftShift = function (x, y) {
     throw new Error('Parameters in function leftShift must be integer numbers');
   }
 
-  var BigNumber = x['constructor'];
+  var BigNumber = x.constructor;
   if (x.isNaN() || y.isNaN() || (y.isNegative() && !y.isZero())) {
     return new BigNumber(NaN);
   }
@@ -221,14 +221,14 @@ exports.not = function (x) {
     throw new Error('Parameter in function bitNot must be integer numbers');
   }
 
-  var BigNumber = x['constructor'];
-  var prevPrec = BigNumber['precision'];
-  BigNumber['precision'] = 1E9;
+  var BigNumber = x.constructor;
+  var prevPrec = BigNumber.precision;
+  BigNumber.config({precision: 1E9});
 
-  var x = x.plus(BigNumber['ONE']);
-  x['s'] = -x['s'] || null;
+  var x = x.plus(BigNumber.ONE);
+  x.s = -x.s || null;
 
-  BigNumber['precision'] = prevPrec;
+  BigNumber.config({precision: prevPrec});
   return x;
 };
 
@@ -256,7 +256,7 @@ exports.or = function (x, y) {
     throw new Error('Parameters in function bitOr must be integer numbers');
   }
 
-  var BigNumber = x['constructor'];
+  var BigNumber = x.constructor;
   if (x.isNaN() || y.isNaN()) {
     return new BigNumber(NaN);
   }
@@ -294,21 +294,22 @@ exports.or = function (x, y) {
  * @returns {BigNumber} sine of x
  */
 exports.sin = function (x) {
-  var BigNumber = x['constructor'];
-  var precision = BigNumber['precision'];
+  var BigNumber = x.constructor;
+  var precision = BigNumber.precision;
   if (x.isNaN() || !x.isFinite()) {
     return new BigNumber(NaN);
   }
 
   // sin(-x) == -sin(x)
-  var isNeg;
-  if (isNeg = x.isNegative()) {
-    x['s'] = -x['s'];
+  var isNeg = x.isNegative();
+  if (isNeg) {
+    x.s = -x.s;
   }
 
   // Get offset within the period of sin (-pi, pi] w/ gaurd digits
-  var pi = exports.pi(precision + ~~(3*Math.log(precision)) + 1);
-  var tau = pi.times(2);
+  var prec_plus_guard_digits = precision + (3*Math.log(precision) | 0) + 1;
+  var pi = exports.pi(prec_plus_guard_digits + 2);
+  var tau = exports.tau(prec_plus_guard_digits);
 
   // Catch if tau multiple using pi's precision
   if (x.div(pi.toDP(x.dp(), 1)).toNumber() % 2 == 0) {
@@ -327,7 +328,7 @@ exports.sin = function (x) {
   var yPrev = NaN;
   var y2 = y.times(y);
   var num = y;
-  var den = BigNumber['ONE'];
+  var den = BigNumber.ONE;
   var add = true;
   for (var k = 1; !y.equals(yPrev); k += 2) {
     num = num.times(y2);
@@ -339,9 +340,9 @@ exports.sin = function (x) {
   }
 
   if (isNeg) {
-    y['s'] = -y['s'];
+    y.s = -y.s;
   }
-  y['constructor']['precision'] = precision;
+  y.constructor.config({precision: precision});
   return y.toDP(precision - 1);
 };
 
@@ -369,7 +370,7 @@ exports.rightShift = function (x, y) {
     throw new Error('Parameters in function rightArithShift must be integer numbers');
   }
 
-  var BigNumber = x['constructor'];
+  var BigNumber = x.constructor;
   if (x.isNaN() || y.isNaN() || (y.isNegative() && !y.isZero())) {
     return new BigNumber(NaN);
   }
@@ -415,7 +416,7 @@ exports.xor = function (x, y) {
     throw new Error('Parameters in function bitXor must be integer numbers');
   }
 
-  var BigNumber = x['constructor'];
+  var BigNumber = x.constructor;
   if (x.isNaN() || y.isNaN()) {
     return new BigNumber(NaN);
   }
@@ -639,11 +640,11 @@ exports.toFixed = function(value, precision) {
 
 
 function bitwise(x, y, func) {
-  var BigNumber = x['constructor'];
+  var BigNumber = x.constructor;
 
   var xBits, yBits;
-  var xSign = +(x['s'] < 0);
-  var ySign = +(y['s'] < 0);
+  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) {
@@ -676,11 +677,11 @@ function bitwise(x, y, func) {
   var longLen = maxBits.length;
   var expFuncVal = func(xSign, ySign) ^ 1;
   var outVal = new BigNumber(expFuncVal ^ 1);
-  var twoPower = BigNumber['ONE'];
+  var twoPower = BigNumber.ONE;
   var two = new BigNumber(2);
 
-  var prevPrec = BigNumber['precision'];
-  BigNumber['precision'] = 1E9;
+  var prevPrec = BigNumber.precision;
+  BigNumber.config({precision: 1E9});
 
   while (shortLen > 0) {
     if (func(minBits[--shortLen], maxBits[--longLen]) == expFuncVal) {
@@ -695,10 +696,10 @@ function bitwise(x, y, func) {
     twoPower = twoPower.times(two);
   }
 
-  BigNumber['precision'] = prevPrec;
+  BigNumber.config({precision: prevPrec});
 
   if (expFuncVal == 0) {
-    outVal['s'] = -outVal['s'];
+    outVal.s = -outVal.s;
   }
   return outVal;
 }
@@ -708,7 +709,7 @@ function bitwise(x, y, func) {
 
 
 function coefficientToString(x) {
-  var a = x['c'];
+  var a = x.c;
   var r = a[0] + '';
 
   for (var i = 1; i < a.length; ++i) {
@@ -723,7 +724,7 @@ function coefficientToString(x) {
   var j;
   for (j = r.length - 1; r.charAt(j) == '0'; --j);
 
-  var xe = x['e'];
+  var xe = x.e;
   var str = r.slice(0, j + 1 || 1);
   var strL = str.length;
   if (xe > 0) {


Test patch:
diff --git a/test/function/bitwise/leftShift.test.js b/test/function/bitwise/leftShift.test.js
index 88f6a2290..c516154dc 100644
--- a/test/function/bitwise/leftShift.test.js
+++ b/test/function/bitwise/leftShift.test.js
@@ -42,9 +42,9 @@ 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(0), bignumber(-2)).toString(), 'NaN');
+    assert.equal(leftShift(bignumber(0), bignumber(-2)).isNaN(), true);
     assert.deepEqual(leftShift(bignumber(Infinity), bignumber(2)), bignumber(Infinity));
-    assert.deepEqual(leftShift(bignumber(Infinity), bignumber(Infinity)).toString(), 'NaN');
+    assert.equal(leftShift(bignumber(Infinity), bignumber(Infinity)).isNaN(), true);
   });
 
   it('should left shift mixed numbers and bignumbers', function () {
@@ -53,8 +53,8 @@ describe('leftShift', function () {
     assert.deepEqual(leftShift(2, bignumber(3)), bignumber(16));
     assert.deepEqual(leftShift(-1, bignumber(2)), bignumber(-4));
     assert.deepEqual(leftShift(bignumber(-1), 2), bignumber(-4));
-    assert.deepEqual(leftShift(bignumber(0), -2).toString(), 'NaN');
-    assert.deepEqual(leftShift(bignumber(Infinity), Infinity).toString(), 'NaN');
+    assert.equal(leftShift(bignumber(0), -2).isNaN(), true);
+    assert.equal(leftShift(bignumber(Infinity), Infinity).isNaN(), true);
   });
 
   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 4c9d418d4..e2acb2f87 100644
--- a/test/function/bitwise/rightArithShift.test.js
+++ b/test/function/bitwise/rightArithShift.test.js
@@ -43,21 +43,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.equal(rightArithShift(bignumber(-17), bignumber(-3)).isNaN(), true);
+    assert.equal(rightArithShift(bignumber(Infinity), bignumber(Infinity)).isNaN(), true);
     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.equal(rightArithShift(bignumber(-17), -3).isNaN(), true);
     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.equal(rightArithShift(-3, bignumber(-17)).isNaN(), true);
     assert.deepEqual(rightArithShift(bignumber(-Infinity), Infinity), bignumber(-1));
-    assert.deepEqual(rightArithShift(bignumber(Infinity), Infinity).toString(), 'NaN');
-    assert.deepEqual(rightArithShift(Infinity, bignumber(Infinity)).toString(), 'NaN');
+    assert.equal(rightArithShift(bignumber(Infinity), Infinity).isNaN(), true);
+    assert.equal(rightArithShift(Infinity, bignumber(Infinity)).isNaN(), true);
   });
 
   it('should right arithmetically shift mixed booleans and bignumbers', function () {
diff --git a/test/function/probability/factorial.test.js b/test/function/probability/factorial.test.js
index e9e89e1fc..e98a70e1b 100644
--- a/test/function/probability/factorial.test.js
+++ b/test/function/probability/factorial.test.js
@@ -16,17 +16,27 @@ describe('factorial', function() {
   });
 
   it('should calculate the factorial of a bignumber', function() {
-    assert.deepEqual(factorial(math.bignumber(0)), math.bignumber(1));
-    assert.deepEqual(factorial(math.bignumber(1)), math.bignumber(1));
-    assert.deepEqual(factorial(math.bignumber(2)), math.bignumber(2));
-    assert.deepEqual(factorial(math.bignumber(3)), math.bignumber(6));
-    assert.deepEqual(factorial(math.bignumber(4)), math.bignumber(24));
-    assert.deepEqual(factorial(math.bignumber(5)), math.bignumber(120));
-    assert.deepEqual(factorial(math.bignumber(20)), math.bignumber('2432902008176640000'));
-    assert.deepEqual(factorial(math.bignumber(21)), math.bignumber('51090942171709440000'));
-    assert.deepEqual(factorial(math.bignumber(25)), math.bignumber('1.5511210043330985984e+25'));
-    assert.deepEqual(factorial(math.bignumber(24)), math.bignumber('6.2044840173323943936e+23'));
-    assert.deepEqual(factorial(math.bignumber(22)), math.bignumber('1124000727777607680000'));
+    var bigmath = math.create({precision: 5});
+    var bigfactorial = bigmath.factorial;
+    var bignumber = bigmath.bignumber;
+
+    assert.deepEqual(bigfactorial(bignumber(11)), bignumber(39917000));
+    assert.deepEqual(bigfactorial(bignumber(22)), bignumber(1.124e+21));
+
+    bigmath.config({precision: 20});
+    assert.deepEqual(bigfactorial(bignumber(5)), bignumber(120));
+    assert.deepEqual(bigfactorial(bignumber(19)), bignumber(121645100408832000));
+    assert.deepEqual(bigfactorial(bignumber(20)), bignumber(2432902008176640000));
+    assert.deepEqual(bigfactorial(bignumber(21)), bignumber('51090942171709440000'));
+    assert.deepEqual(bigfactorial(bignumber(25)), bignumber('1.5511210043330985984e+25'));
+    assert.deepEqual(bigfactorial(bignumber(24)), bignumber('6.2044840173323943936e+23'));
+    assert.deepEqual(bigfactorial(bignumber(22)), bignumber('1124000727777607680000'));
+
+    bigmath.config({precision: 5});
+    assert.deepEqual(bigfactorial(bignumber(11)), bignumber(39917000));
+    assert.deepEqual(bigfactorial(bignumber(22)), bignumber(1.124e+21));
+    assert.deepEqual(bigfactorial(bignumber(24)), bignumber(6.2045e+23));
+    assert.deepEqual(bigfactorial(bignumber(21)), bignumber(5.1091e+19));
   });
 
   it('should calculate the factorial of a boolean', function() {
diff --git a/test/function/trigonometry/sin.test.js b/test/function/trigonometry/sin.test.js
index 1d17335ee..5949c1026 100644
--- a/test/function/trigonometry/sin.test.js
+++ b/test/function/trigonometry/sin.test.js
@@ -36,28 +36,32 @@ describe('sin', function() {
     assert.deepEqual(bigmath.sin(bigmath.bignumber(0)), bigmath.bignumber(0));
     
     // 103.64 % tau = 3.109... <- pretty close to the pi boundary
-    assert.deepEqual(bigmath.sin(bigmath.bignumber(103.64)).toString(), '0.032551816956616158442731315994267213051204459121689332893471030' +
-                                                                          '714804383298805501395839512341888732261080924779366105855493575' +
-                                                                          '835362891900420559398509489530577719840860106717522689249606121' +
-                                                                          '2602629134186583352145117086874446046421403346033616');
-    assert.deepEqual(bigmath.sin(bigmath.bignumber(-103.64)).toString(), '-0.0325518169566161584427313159942672130512044591216893328934710' +
-                                                                            '3071480438329880550139583951234188873226108092477936610585549' +
-                                                                            '3575835362891900420559398509489530577719840860106717522689249' +
-                                                                            '6061212602629134186583352145117086874446046421403346033616');
+    var result_val = bigmath.sin(bigmath.bignumber(103.64));
+    assert.equal(result_val.constructor.precision, 242);
+    assert.deepEqual(result_val.toString(), '0.032551816956616158442731315994267213051204459121689332893471030' +
+                                              '714804383298805501395839512341888732261080924779366105855493575' +
+                                              '835362891900420559398509489530577719840860106717522689249606121' +
+                                              '2602629134186583352145117086874446046421403346033616');
+    result_val = bigmath.sin(bigmath.bignumber(-103.64));
+    assert.equal(result_val.constructor.precision, 242);
+    assert.deepEqual(result_val.toString(), '-0.0325518169566161584427313159942672130512044591216893328934710' +
+                                               '3071480438329880550139583951234188873226108092477936610585549' +
+                                               '3575835362891900420559398509489530577719840860106717522689249' +
+                                               '6061212602629134186583352145117086874446046421403346033616');
     bigmath.config({precision: 15});
 
     var bigPi = bigmath.pi;
-    var result = bigmath.SQRT2.div(2).toString();
-    assert.deepEqual(bigmath.sin(bigPi.div(4)).toString(), result);
+    result_val = bigmath.SQRT2.div(2).toString();
+    assert.deepEqual(bigmath.sin(bigPi.div(4)).toString(), result_val);
     assert.deepEqual(bigmath.sin(bigPi.div(2)).toString(), '1');
-    assert.deepEqual(bigmath.sin(bigPi.times(3).div(4)).toString(), result);
-    assert.deepEqual(bigmath.sin(bigPi).toString(), '0');
-    assert.deepEqual(bigmath.sin(bigPi.times(5).div(4)).toString(), '-'+result);
+    assert.deepEqual(bigmath.sin(bigPi.times(3).div(4)).toString(), result_val);
+    assert.equal(bigmath.sin(bigPi).isZero(), true);
+    assert.deepEqual(bigmath.sin(bigPi.times(5).div(4)).toString(), '-'+result_val);
     assert.deepEqual(bigmath.sin(bigPi.times(3).div(2)).toString(), '-1');
-    assert.deepEqual(bigmath.sin(bigPi.times(7).div(4)).toString(), '-'+result);
-    assert.deepEqual(bigmath.sin(bigPi.times(2)).toString(), '0');
-    assert.deepEqual(bigmath.sin(bigmath.tau).toString(), '0');
-    assert.deepEqual(bigmath.sin(bigmath.tau.times(2)).toString(), '0');
+    assert.deepEqual(bigmath.sin(bigPi.times(7).div(4)).toString(), '-'+result_val);
+    assert.equal(bigmath.sin(bigPi.times(2)).isZero(), true);
+    assert.equal(bigmath.sin(bigmath.tau).isZero(), true);
+    assert.equal(bigmath.sin(bigmath.tau.times(2)).isZero(), true);
   });
 
   it('should return the sine of a complex number', function() {