Repository: josdejong/mathjs
Pull request #259 (2015-01-02 07:40:46)

Initial BigNumber sin support. Tests added as well.
It's suboptimal. However something that works, but is slow is better than nothing lol.

I played with the test cases, so that they match with the BigNumber ones (which saved me typing extra functions for already slow test cases). I also moved the `arctan` into the function section; I had mistakenly put it into the constants section of the file.

I try to catch both precisions of `2 * PI`, that of the `tau` elk and that of the `2*PI` elk. If you want me to only catch the `tau` ones I can remove lines 306 - 310.


Patch:
diff --git a/lib/function/trigonometry/sin.js b/lib/function/trigonometry/sin.js
index f35f7a47b..9b289fc32 100644
--- a/lib/function/trigonometry/sin.js
+++ b/lib/function/trigonometry/sin.js
@@ -12,7 +12,9 @@ module.exports = function (math) {
       isBoolean = util['boolean'].isBoolean,
       isComplex = Complex.isComplex,
       isUnit = Unit.isUnit,
-      isCollection = collection.isCollection;
+      isCollection = collection.isCollection,
+
+      bigSin = util.bignumber.sin;
 
   /**
    * Calculate the sine of a value.
@@ -37,8 +39,8 @@ module.exports = function (math) {
    *
    *    cos, tan
    *
-   * @param {Number | Boolean | Complex | Unit | Array | Matrix | null} x  Function input
-   * @return {Number | Complex | Array | Matrix} Sine of x
+   * @param {Number | BigNumber | Boolean | Complex | Unit | Array | Matrix | null} x  Function input
+   * @return {Number | BigNumber | Complex | Array | Matrix} Sine of x
    */
   math.sin = function sin(x) {
     if (arguments.length != 1) {
@@ -72,9 +74,8 @@ module.exports = function (math) {
     }
 
     if (x instanceof BigNumber) {
-      // TODO: implement BigNumber support
-      // downgrade to Number
-      return sin(x.toNumber());
+      // TODO: implement better BigNumber support
+      return bigSin(x);
     }
 
     throw new math.error.UnsupportedTypeError('sin', math['typeof'](x));
diff --git a/lib/util/bignumber.js b/lib/util/bignumber.js
index ec3b52590..e10d8f121 100644
--- a/lib/util/bignumber.js
+++ b/lib/util/bignumber.js
@@ -39,33 +39,6 @@ exports.phi = function (precision) {
   return new Big(1).plus(new Big(5).sqrt()).div(2);
 };
 
-/**
- * Calculate the arc tangent of x
- *
- * arctan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9 - ...
- *           = x - x^2*x^1/3 + x^2*x^3/5 - x^2*x^5/7 + x^2*x^7/9 - ...
- *
- * @param {BigNumber} x
- * @returns {BigNumber} arc tangent of x
- */
-exports.arctan = function (x) {
-  var y = x;
-  var yPrev = NaN;
-  var x2 = x.times(x);
-  var num = x;
-  var sign = -1;
-
-  for (var k = 3; !y.equals(yPrev); k += 2) {
-    num = num.times(x2);
-
-    yPrev = y;
-    y = (sign > 0) ? y.plus(num.div(k)) : y.minus(num.div(k));
-    sign = -sign;
-  }
-
-  return y;
-};
-
 /**
  * Calculate BigNumber pi.
  *
@@ -171,6 +144,33 @@ exports.and = function(x, y) {
   return bitwise(x, y, function (a, b) { return a & b });
 };
 
+/**
+ * Calculate the arc tangent of x
+ *
+ * arctan(x) = x - x^3/3 + x^5/5 - x^7/7 + x^9/9 - ...
+ *           = x - x^2*x^1/3 + x^2*x^3/5 - x^2*x^5/7 + x^2*x^7/9 - ...
+ *
+ * @param {BigNumber} x
+ * @returns {BigNumber} arc tangent of x
+ */
+exports.arctan = function (x) {
+  var y = x;
+  var yPrev = NaN;
+  var x2 = x.times(x);
+  var num = x;
+  var sign = -1;
+
+  for (var k = 3; !y.equals(yPrev); k += 2) {
+    num = num.times(x2);
+
+    yPrev = y;
+    y = (sign > 0) ? y.plus(num.div(k)) : y.minus(num.div(k));
+    sign = -sign;
+  }
+
+  return y;
+};
+
 /*
  * Special Cases:
  *  n << -n = N
@@ -281,6 +281,60 @@ exports.or = function (x, y) {
   return bitwise(x, y, function (a, b) { return a | b });
 };
 
+/**
+ * Calculate the sine of x using Taylor Series.
+ *
+ * sin(x) = x - x^3/3! + x^5/5! - x^7/7! + x^9/9! - ...
+ *        = x - x^2*x^1/3! + x^2*x^3/5! - x^2*x^5/7! + x^2*x^7/9! - ...
+ *
+ * TODO: Replace with Chebyshev approximation.
+ *
+ * @param {BigNumber} x
+ * @returns {BigNumber} sine of x
+ */
+exports.sin = function (x) {
+  var BigNumber = x['constructor'];
+  var precision = BigNumber['precision'];
+  if (x.isNaN() || !x.isFinite()) {
+    return new BigNumber(NaN);
+  }
+
+  // Get number's offset within the period of sin (tau)
+  var pi = exports.pi(2 * precision - 5);
+  var tau = pi.times(2);
+
+  // Catch if tau multiple using pi's precision
+  if (x.div(pi.toDP(x.dp(), 1)).toNumber() % 2 == 0) {
+    return new BigNumber(0);
+  }
+
+  var y = x.mod(tau);
+  // Catch if tau multiple with tau's precision
+  if (y.toDP(x.dp(), 1).isZero()) {
+    return new BigNumber(0);
+  }
+  if (y.gt(pi)) {
+    y = y.minus(tau);
+  }
+  y['constructor']['precision'] = precision;
+
+  var yPrev = NaN;
+  var y2 = y.times(y);
+  var num = y;
+  var den = BigNumber['ONE'];
+  var sign = -1;
+  for (var k = 1; !y.equals(yPrev); k += 2) {
+    num = num.times(y2);
+    den = den.times(k+1).times(k+2);
+
+    yPrev = y;
+    y = (sign > 0) ? y.plus(num.div(den)) : y.minus(num.div(den));
+    sign = -sign;
+  }
+
+  return y.toDP(precision - 1);
+};
+
 /*
  * Special Cases:
  *   n >> -n =  N


Test patch:
diff --git a/test/function/trigonometry/sin.test.js b/test/function/trigonometry/sin.test.js
index c13aebbbe..2fde59843 100644
--- a/test/function/trigonometry/sin.test.js
+++ b/test/function/trigonometry/sin.test.js
@@ -20,20 +20,36 @@ describe('sin', function() {
 
   it('should return the sine of a number', function() {
     approx.equal(sin(0), 0);
-    approx.equal(sin(pi*1/4), 0.707106781186548);
-    approx.equal(sin(pi*1/8), 0.382683432365090);
-    approx.equal(sin(pi*2/4), 1);
+    approx.equal(sin(pi/8), 0.382683432365090);
+    approx.equal(sin(pi/4), Math.SQRT2/2);
+    approx.equal(sin(pi/2), 1);
     approx.equal(sin(pi*3/4), 0.707106781186548);
-    approx.equal(sin(pi*4/4), 0);
+    approx.equal(sin(pi), 0);
     approx.equal(sin(pi*5/4), -0.707106781186548);
-    approx.equal(sin(pi*6/4), -1);
+    approx.equal(sin(pi*3/2), -1);
     approx.equal(sin(pi*7/4), -0.707106781186548);
-    approx.equal(sin(pi*8/4), 0);
-    approx.equal(sin(pi/4), math.sqrt(2)/2);
+    approx.equal(sin(pi*2), 0);
   });
 
-  it('should return the sine of a bignumber (downgrades to number)', function() {
-    approx.equal(sin(math.bignumber(1)), 0.841470984807897);
+  it('should return the sine of a bignumber', function() {
+    var bigmath = math.create({number: 'bignumber', precision: 60});
+    assert.deepEqual(sin(math.bignumber(0)), math.bignumber(0));
+    assert.deepEqual(sin(math.bignumber(1)), math.bignumber('0.84147098480789650665250232163029899962256306079837106567275171'));
+
+    bigmath.config({precision: 15});
+
+    var bigPi = bigmath.pi;
+    var result = bigmath.SQRT2.div(2).toString();
+    assert.deepEqual(bigmath.sin(bigPi.div(4)).toString(), result);
+    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(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');
   });
 
   it('should return the sine of a complex number', function() {