Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

- #338

Closed
wants to merge 4 commits into from
Closed

- #338

Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 48 additions & 41 deletions es6-shim.js
Original file line number Diff line number Diff line change
Expand Up @@ -1310,29 +1310,29 @@
var MathShims = {
acosh: function acosh(value) {
var x = Number(value);
if (Number.isNaN(x) || value < 1) { return NaN; }
if (x === 1) { return 0; }
if (x === Infinity) { return x; }
return Math.log(x / Math.E + Math.sqrt(x + 1) * Math.sqrt(x - 1) / Math.E) + 1;
if (Math.abs(x) > Math.sqrt(2 / Number.EPSILON)) {
return Math.log(x) + Math.LN2;
}
return Math.log1p(x - 1 + Math.sqrt(x - 1) * Math.sqrt(x + 1));
},

asinh: function asinh(value) {
var x = Number(value);
if (x === 0 || !globalIsFinite(x)) {
if (x === 0) {
return x;
}
return x < 0 ? -Math.asinh(-x) : Math.log(x + Math.sqrt(x * x + 1));
if (x < 0) {
return -Math.asinh(-x);
}
if (Math.abs(x) > Math.sqrt(2 / Number.EPSILON)) {
return Math.log(x) + Math.LN2;
}
return Math.log1p(x + Math.expm1(Math.log1p(x * x) / 2));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just about to post an issue recommending the use of a range-reduction method similar to that used for Math.acosh in the current shim:

if (x < 0) { return -Math.asinh(-x); }
var sqx = x * x;
if (sqx <= inverseEpsilon - 1) { return Math.log(x + Math.sqrt(sqx + 1)); }
if (sqx <= Number.MAX_VALUE - 1) {
  var sqrtEpsilon = Math.sqrt(Number.EPSILON);
  return Math.log(x * sqrtEpsilon + Math.sqrt(square(x * sqrtEpsilon) + Number.EPSILON)) - Math.log(sqrtEpsilon);
}
var sqrtMax = Math.sqrt(Number.MAX_VALUE);
return Math.log(x / sqrtMax + Math.sqrt(square(x / sqrtMax) + 1 / Number.MAX_VALUE)) + Math.log(Number.MAX_VALUE) / 2;

However, this solution looks less hackish (also, I'm not sure whether the function call to square() would be worth it if the argument contains an operator, compared to running the inner operations twice).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lewisje ,
Hi! Why is it better? Could you provide more details? Is Math.log(x) + Math.log(2) not accurate for "big" x ? I cannot find any x, for which your impl. produce too much different values.
Your implementation is not accurate for small values (Math.pow(2, -1021)),

What I do not like in my is : if (x * x + 1 === x * x) {. Possibly, I should use x > Math.sqrt(2 / Number.EPSILON) here.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, by "this" I meant the line I was commenting on; that is, your idea is better than mine and I hope it is merged in.

},

atanh: function atanh(value) {
var x = Number(value);
if (Number.isNaN(x) || x < -1 || x > 1) {
return NaN;
}
if (x === -1) { return -Infinity; }
if (x === 1) { return Infinity; }
if (x === 0) { return x; }
return 0.5 * Math.log((1 + x) / (1 - x));
return (Math.log1p(x) - Math.log1p(-x)) / 2;
},

cbrt: function cbrt(value) {
Expand Down Expand Up @@ -1362,12 +1362,10 @@

cosh: function cosh(value) {
var x = Number(value);
if (x === 0) { return 1; } // +0 or -0
if (Number.isNaN(x)) { return NaN; }
if (!globalIsFinite(x)) { return Infinity; }
if (x < 0) { x = -x; }
if (x > 21) { return Math.exp(x) / 2; }
return (Math.exp(x) + Math.exp(-x)) / 2;
if (x === 0) {
return 1;
}
return (Math.exp(x - 1) + Math.exp(-x - 1)) * (Math.E / 2);
},

expm1: function expm1(value) {
Expand Down Expand Up @@ -1447,24 +1445,20 @@

sinh: function sinh(value) {
var x = Number(value);
if (!globalIsFinite(x) || x === 0) { return x; }

if (Math.abs(x) < 1) {
return (Math.expm1(x) - Math.expm1(-x)) / 2;
}
return (Math.exp(x - 1) - Math.exp(-x - 1)) * Math.E / 2;
return (Math.exp(x - 1) - Math.exp(-x - 1)) * (Math.E / 2);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

brackets are important here - Math.sinh(710)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fine with adding these parentheses (brackets are [ ]), but mathematically it shouldn't make a difference.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mathematically - not, but from floating point perspective - should, because of overflows

Math.pow(2, 1023) * Math.E / 2 === Infinity
Math.pow(2, 1023) * (Math.E / 2) === 1.2216591454104522e+308

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah ha, good point :-)

},

tanh: function tanh(value) {
var x = Number(value);
if (Number.isNaN(x) || x === 0) { return x; }
if (x === Infinity) { return 1; }
if (x === -Infinity) { return -1; }
var a = Math.expm1(x);
var b = Math.expm1(-x);
if (a === Infinity) { return 1; }
if (b === Infinity) { return -1; }
return (a - b) / (Math.exp(x) + Math.exp(-x));
var a = Math.expm1(2 * x);
var b = Math.expm1(2 * x) + 2;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why am I calculating Math.expm1(2 * x) twice?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ha, good question

if (a === Infinity && b === Infinity) {
return 1;
}
return a / b;
},

trunc: function trunc(value) {
Expand Down Expand Up @@ -1505,21 +1499,34 @@
}
};
defineProperties(Math, MathShims);
// IE 11 TP has an imprecise log1p: reports Math.log1p(-1e-17) as 0
defineProperty(Math, 'log1p', MathShims.log1p, Math.log1p(-1e-17) !== -1e-17);

var BINARY_64_EPSILON = Math.pow(2, -52);
// https://code.google.com/p/v8/issues/detail?id=3468
var MATH_EXP_ERROR = Math.abs(1 - Math.exp(704.6589) / 1.0702171200481775e+306) / BINARY_64_EPSILON;
var isAlmostEqual = function (a, b) {
return Math.abs(1 - a / b) / BINARY_64_EPSILON < MATH_EXP_ERROR + 8;
};

// Chrome 40-43
defineProperty(Math, 'acosh', MathShims.acosh, !isAlmostEqual(Math.acosh(1.0000000000000009), 4.214684851089403e-8));
// IE 11 TP has an imprecise asinh: reports Math.asinh(-1e7) as not exactly equal to -Math.asinh(1e7)
defineProperty(Math, 'asinh', MathShims.asinh, Math.asinh(-1e7) !== -Math.asinh(1e7));
// Chrome 40 has an imprecise Math.tanh with very small numbers
defineProperty(Math, 'tanh', MathShims.tanh, Math.tanh(-2e-17) !== -2e-17);
// Chrome 40 loses Math.acosh precision with high numbers
defineProperty(Math, 'acosh', MathShims.acosh, Math.acosh(Number.MAX_VALUE) === Infinity);
// Chrome 40-43
defineProperty(Math, 'asinh', MathShims.asinh, !isAlmostEqual(Math.asinh(1.7976931348623157e+308), 710.475860073944));
// Chrome 40-43
defineProperty(Math, 'atanh', MathShims.atanh, !isAlmostEqual(Math.atanh(Math.pow(2, -1021)), Math.pow(2, -1021)));
// Firefox 38 on Windows
defineProperty(Math, 'cbrt', MathShims.cbrt, Math.abs(1 - Math.cbrt(1e-300) / 1e-100) / Number.EPSILON > 8);
// node 0.11 has an imprecise Math.sinh with very small numbers
defineProperty(Math, 'sinh', MathShims.sinh, Math.sinh(-2e-17) !== -2e-17);
defineProperty(Math, 'cbrt', MathShims.cbrt, !isAlmostEqual(Math.cbrt(1e-300), 1e-100));
// node 0.12
defineProperty(Math, 'cosh', MathShims.cosh, !isAlmostEqual(Math.cosh(710), 1.1169973830808555e+308));
// FF 35 on Linux reports 22025.465794806725 for Math.expm1(10)
var expm1OfTen = Math.expm1(10);
defineProperty(Math, 'expm1', MathShims.expm1, expm1OfTen > 22025.465794806719 || expm1OfTen < 22025.4657948067165168);
defineProperty(Math, 'expm1', MathShims.expm1, !isAlmostEqual(Math.expm1(10), 22025.465794806718));
// IE 11 TP has an imprecise log1p: reports Math.log1p(-1e-17) as 0
defineProperty(Math, 'log1p', MathShims.log1p, !isAlmostEqual(Math.log1p(-1e-17), -1e-17));
// node 0.11 has an imprecise Math.sinh with very small numbers
defineProperty(Math, 'sinh', MathShims.sinh, !isAlmostEqual(Math.sinh(-2e-17), -2e-17));
// Chrome 40 on Windows has an imprecise Math.tanh with very small numbers
defineProperty(Math, 'tanh', MathShims.tanh, !isAlmostEqual(Math.tanh(-2e-17), -2e-17));

var origMathRound = Math.round;
// breaks in e.g. Safari 8, Internet Explorer 11, Opera 12
Expand Down
63 changes: 41 additions & 22 deletions test/math.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
/*global describe, it, expect, require */

var Assertion = expect().constructor;
Assertion.prototype.almostEqual = function (obj, precision) {

var EPSILON = Math.pow(2, -52);
var MIN_VALUE = Math.pow(2, -1022);
var MAX_VALUE = Math.pow(2, 1023) * (2 - EPSILON);

var MATH_EXP_ERROR = Math.abs(1 - Math.exp(704.6589) / 1.0702171200481775e+306) / EPSILON;
Assertion.prototype.almostEqual = function (expected) {
'use strict';

var allowedDiff = precision || 1e-11;
return this.within(obj - allowedDiff, obj + allowedDiff);
var C = (expected < 0 ? -1 : (expected > 0 ? 1 : expected)) * (MATH_EXP_ERROR + 8) * EPSILON;
return this.within(expected * (1 - C), expected * (1 + C));
};
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is not ideal, I do not know how to access private "this.__flags.object" and I did not test it well.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this.flags('object') might work better

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb , I fixed by using this.within


describe('Math', function () {
Expand All @@ -27,7 +33,6 @@ describe('Math', function () {
};
var valueOfIsNaN = { valueOf: function () { return NaN; } };
var valueOfIsInfinity = { valueOf: function () { return Infinity; } };
var EPSILON = Number.EPSILON || 2.2204460492503130808472633361816e-16;

(typeof process !== 'undefined' && process.env.NO_ES6_SHIM ? it.skip : it)('is on the exported object', function () {
var exported = require('../');
Expand All @@ -50,15 +55,16 @@ describe('Math', function () {
it('should be correct', function () {
expect(numberIsNaN(Math.acosh(NaN))).to.equal(true);
expect(numberIsNaN(Math.acosh(0))).to.equal(true);
expect(numberIsNaN(Math.acosh(0.9999999))).to.equal(true);
expect(numberIsNaN(Math.acosh(1 - EPSILON / 2))).to.equal(true);
expect(numberIsNaN(Math.acosh(-1e300))).to.equal(true);
expect(Math.acosh(1e+99)).to.almostEqual(228.64907138697046);
expect(isPositiveZero(Math.acosh(1))).to.equal(true);
expect(Math.acosh(Infinity)).to.equal(Infinity);
expect(Math.acosh(1234)).to.almostEqual(7.811163220849231);
expect(Math.acosh(8.88)).to.almostEqual(2.8737631531629235);
expect(Math.acosh(1e160)).to.almostEqual(369.10676205960726);
expect(Math.acosh(Number.MAX_VALUE)).to.almostEqual(710.4758600739439);
expect(Math.acosh(MAX_VALUE)).to.almostEqual(710.4758600739439);
expect(Math.acosh(1 + EPSILON)).to.almostEqual(2.1073424255447017e-8);
});
});

Expand Down Expand Up @@ -95,6 +101,10 @@ describe('Math', function () {
expect(Math.asinh(1e150)).to.almostEqual(346.0809111296668);
expect(Math.asinh(1e7)).to.almostEqual(16.811242831518268);
expect(Math.asinh(-1e7)).to.almostEqual(-16.811242831518268);
expect(Math.asinh(MAX_VALUE)).to.almostEqual(710.4758600739439);
expect(Math.asinh(-MAX_VALUE)).to.almostEqual(-710.4758600739439);
expect(Math.asinh(MIN_VALUE * 2)).to.almostEqual(MIN_VALUE * 2);
expect(Math.asinh(-MIN_VALUE * 2)).to.almostEqual(-MIN_VALUE * 2);
});
});

Expand All @@ -113,14 +123,18 @@ describe('Math', function () {

it('should be correct', function () {
expect(numberIsNaN(Math.atanh(NaN))).to.equal(true);
expect(numberIsNaN(Math.atanh(-1.00000001))).to.equal(true);
expect(numberIsNaN(Math.atanh(1.00000001))).to.equal(true);
expect(numberIsNaN(Math.atanh(-1e300))).to.equal(true);
expect(numberIsNaN(Math.atanh(1e300))).to.equal(true);
expect(numberIsNaN(Math.atanh(-1 - EPSILON))).to.equal(true);
expect(numberIsNaN(Math.atanh(1 + EPSILON))).to.equal(true);
expect(numberIsNaN(Math.atanh(-MAX_VALUE))).to.equal(true);
expect(numberIsNaN(Math.atanh(MAX_VALUE))).to.equal(true);
expect(Math.atanh(-1)).to.equal(-Infinity);
expect(Math.atanh(1)).to.equal(Infinity);
expect(isPositiveZero(Math.atanh(+0))).to.equal(true);
expect(isNegativeZero(Math.atanh(-0))).to.equal(true);
expect(Math.atanh(-1 + EPSILON / 2)).to.almostEqual(-18.714973875118524);
expect(Math.atanh(1 - EPSILON / 2)).to.almostEqual(18.714973875118524);
expect(Math.atanh(MIN_VALUE * 2)).to.almostEqual(MIN_VALUE * 2);
expect(Math.atanh(-MIN_VALUE * 2)).to.almostEqual(-MIN_VALUE * 2);
expect(Math.atanh(0.5)).to.almostEqual(0.5493061443340549);
expect(Math.atanh(-0.5)).to.almostEqual(-0.5493061443340549);
expect(Math.atanh(-0.5)).to.almostEqual(-0.5493061443340549);
Expand Down Expand Up @@ -151,10 +165,10 @@ describe('Math', function () {
expect(Math.cbrt(8)).to.almostEqual(2);
expect(Math.cbrt(-1000)).to.almostEqual(-10);
expect(Math.cbrt(1000)).to.almostEqual(10);
expect(Math.cbrt(-1e-300)).to.almostEqual(-1e-100);
expect(Math.cbrt(1e-300)).to.almostEqual(1e-100);
expect(Math.cbrt(-1e+300)).to.almostEqual(-1e+100);
expect(Math.cbrt(1e+300)).to.almostEqual(1e+100);
expect(Math.cbrt(-MIN_VALUE)).to.almostEqual(-2.8126442852362615e-103);
expect(Math.cbrt(MIN_VALUE)).to.almostEqual(2.8126442852362615e-103);
expect(Math.cbrt(-MAX_VALUE)).to.almostEqual(-5.643803094122361e+102);
expect(Math.cbrt(MAX_VALUE)).to.almostEqual(5.643803094122361e+102);
});
});

Expand Down Expand Up @@ -260,13 +274,13 @@ describe('Math', function () {
});

it('should be correct', function () {
// Overridden precision values here are for Chrome, as of v25.0.1364.172
// Broadened slightly for Firefox 31
expect(Math.cosh(12)).to.almostEqual(81377.39571257407, 9e-11);
expect(Math.cosh(22)).to.almostEqual(1792456423.065795780980053377, 1e-5);
expect(Math.cosh(12)).to.almostEqual(81377.39571257407);
expect(Math.cosh(22)).to.almostEqual(1792456423.065795780980053377);
expect(Math.cosh(-10)).to.almostEqual(11013.23292010332313972137);
expect(Math.cosh(-23)).to.almostEqual(4872401723.1244513000, 1e-5);
expect(Math.cosh(-2e-17)).to.equal(1);
expect(Math.cosh(-23)).to.almostEqual(4872401723.1244513000);
expect(Math.cosh(-2e-17)).to.almostEqual(1);
expect(Math.cosh(710)).to.almostEqual(1.1169973830808555e+308);
expect(Math.cosh(-710)).to.almostEqual(1.1169973830808555e+308);
});
});

Expand Down Expand Up @@ -551,7 +565,11 @@ describe('Math', function () {
expect(Math.sinh(-Infinity)).to.equal(-Infinity);
expect(Math.sinh(-5)).to.almostEqual(-74.20321057778875);
expect(Math.sinh(2)).to.almostEqual(3.6268604078470186);
expect(Math.sinh(-2e-17)).to.equal(-2e-17);
expect(Math.sinh(-2e-17)).to.almostEqual(-2e-17);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which browsers is Math.sinh(-2e-17) not precisely equal to -2e-17?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ljharb in a browser of 2150 year, which have Number.EPSILON < 2e-17 * 2e-17 / 3

expect(Math.sinh(MIN_VALUE * 2)).to.almostEqual(MIN_VALUE * 2);
expect(Math.sinh(-MIN_VALUE * 2)).to.almostEqual(-MIN_VALUE * 2);
expect(Math.sinh(710)).to.almostEqual(1.1169973830808555e+308);
expect(Math.sinh(-710)).to.almostEqual(-1.1169973830808555e+308);
});
});

Expand Down Expand Up @@ -582,7 +600,8 @@ describe('Math', function () {
expect(Math.tanh(-Infinity)).to.equal(-1);
expect(Math.tanh(90)).to.almostEqual(1);
expect(Math.tanh(10)).to.almostEqual(0.9999999958776927);
expect(Math.tanh(-2e-17)).to.equal(-2e-17);
expect(Math.tanh(MIN_VALUE * 2)).to.almostEqual(MIN_VALUE * 2);
expect(Math.tanh(-MIN_VALUE * 2)).to.almostEqual(-MIN_VALUE * 2);
});
});

Expand Down