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 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() {