Skip to content

Commit

Permalink
fix a bug with computation of cos/sin near zero;
Browse files Browse the repository at this point in the history
fix exp to work faster for large positive or negative values;
fix argument reduction in sin/cos;
  • Loading branch information
Yaffle committed Aug 30, 2020
1 parent 051f8d3 commit 437fa0a
Show file tree
Hide file tree
Showing 5 changed files with 159 additions and 87 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/node_modules/
36 changes: 19 additions & 17 deletions BigDecimal.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function naturalLogarithm(n) {
var s = n.toString(16);
var p = Math.floor(Math.log((Number.MAX_SAFE_INTEGER + 1) / 32 + 0.5) / Math.log(2));
var l = Math.floor(p / 4);
return Math.log(Number('0x' + s.slice(0, l)) / Math.pow(2, 4 * l)) + 4 * Math.log(2) * s.length;
return Math.log(Number("0x" + s.slice(0, l)) / Math.pow(2, 4 * l)) + 4 * Math.log(2) * s.length;
}
function digits(a) { // floor(log10(abs(a.significand))) + 1
var n = bigIntMax(bigIntAbs(a.significand), 1n);
Expand Down Expand Up @@ -327,7 +327,7 @@ function abs(a) {
}

function sign(x) {
return BigDecimal.lessThan(x, BigDecimal.BigDecimal(0)) ? BigDecimal.BigDecimal(-1) : (BigDecimal.greaterThan(x, BigDecimal.BigDecimal(0)) ? BigDecimal.BigDecimal(1) : BigDecimal.BigDecimal(0));
return BigDecimal.lessThan(x, BigDecimal.BigDecimal(0)) ? BigDecimal.BigDecimal(-1) : (BigDecimal.greaterThan(x, BigDecimal.BigDecimal(0)) ? BigDecimal.BigDecimal(+1) : BigDecimal.BigDecimal(0));
}

function significandDigits(a) {
Expand Down Expand Up @@ -361,6 +361,9 @@ function tryToMakeCorrectlyRounded(specialValue, f, name) {
var i = 0;
var error = BigDecimal.BigDecimal(0);
do {
if (i > 4 && rounding.maximumSignificantDigits != null && rounding.roundingMode === "half-even" && name !== "sin" && name !== "cos") {
throw new Error();
}
i += 1;
var internalRounding = {
maximumSignificantDigits: Math.ceil(Math.max(rounding.maximumSignificantDigits || (rounding.maximumFractionDigits + 1 + getExpectedResultIntegerDigits(x) - 1), significandDigits(x)) * Math.cbrt(Math.pow(2, i - 1))) + 2,
Expand All @@ -372,12 +375,9 @@ function tryToMakeCorrectlyRounded(specialValue, f, name) {
//if (i > 0) {
//console.log(i, f.name, x + "", result + "", error + "", BigDecimal.round(BigDecimal.subtract(result, error), rounding) + "", BigDecimal.round(BigDecimal.add(result, error), rounding) + "");
//}
if (i > 10 && rounding.maximumSignificantDigits != null) {
throw new Error();
}
} while (!BigDecimal.equal(BigDecimal.round(BigDecimal.subtract(result, error), rounding), BigDecimal.round(BigDecimal.add(result, error), rounding)));
if (i > 1) {
console.log(i);
//console.debug(i, name);
}
return BigDecimal.round(result, rounding);
};
Expand All @@ -389,7 +389,7 @@ function sqrt(x, rounding) {
var result = BigDecimal.divide(x, BigDecimal.BigDecimal(2));
while (BigDecimal.lessThan(result, lastResult)) {
lastResult = result;
result = BigDecimal.divide(BigDecimal.add(BigDecimal.divide(BigDecimal.BigDecimal(x), result, rounding), result), BigDecimal.BigDecimal(2), rounding);
result = BigDecimal.divide(BigDecimal.add(BigDecimal.divide(x, result, rounding), result), BigDecimal.BigDecimal(2), rounding);
}
return result;
}
Expand Down Expand Up @@ -417,7 +417,7 @@ BigDecimal.log = tryToMakeCorrectlyRounded(1, function log(x, rounding) {
f = BigDecimal.multiply(f, BigDecimal.BigDecimal(10));
}
if (k !== 0n) {
return BigDecimal.add(BigDecimal.log(f, internalRounding), BigDecimal.multiply(BigDecimal.BigDecimal(2n * k), BigDecimal.log(BigDecimal.BigDecimal(sqrt(BigDecimal.BigDecimal(10), internalRounding)), internalRounding)));
return BigDecimal.add(BigDecimal.log(f, internalRounding), BigDecimal.multiply(BigDecimal.BigDecimal(2n * k), BigDecimal.log(sqrt(BigDecimal.BigDecimal(10), internalRounding), internalRounding)));
}
}
//! log(x) = log((1 + g) / (1 - g)) = 2*(g + g**3/3 + g**5/5 + ...)
Expand All @@ -441,13 +441,15 @@ BigDecimal.log = tryToMakeCorrectlyRounded(1, function log(x, rounding) {
BigDecimal.exp = tryToMakeCorrectlyRounded(0, function exp(x, rounding) {
//! k = round(x / ln(10));
//! exp(x) = exp(x - k * ln(10) + k * ln(10)) = exp(x - k * ln(10)) * 10**k
var k = BigDecimal.divide(x, BigDecimal.BigDecimal(BigDecimal.divide(BigDecimal.BigDecimal(2302585092994046), BigDecimal.BigDecimal(1000000000000000))), {maximumFractionDigits: 0, roundingMode: "half-even"});
var k = BigDecimal.divide(x, BigDecimal.divide(BigDecimal.BigDecimal(2302585092994046), BigDecimal.BigDecimal(1000000000000000)), {maximumFractionDigits: 0, roundingMode: "half-even"});
var internalRounding = {
maximumSignificantDigits: rounding.maximumSignificantDigits + Math.ceil(Math.log(rounding.maximumSignificantDigits + 0.5) / Math.log(10)),
roundingMode: "half-even"
};
if (!BigDecimal.equal(k, BigDecimal.BigDecimal(0))) {
var r = BigDecimal.subtract(x, BigDecimal.multiply(k, BigDecimal.log(BigDecimal.BigDecimal(10), {maximumSignificantDigits: internalRounding.maximumSignificantDigits + Number(getCountOfDigits(k)), roundingMode: "half-even"})));
var log10 = BigDecimal.log(BigDecimal.BigDecimal(10), {maximumSignificantDigits: internalRounding.maximumSignificantDigits + Number(getCountOfDigits(k)), roundingMode: "half-even"});
k = BigDecimal.divide(x, log10, {maximumFractionDigits: 0, roundingMode: "half-even"});
var r = BigDecimal.subtract(x, BigDecimal.multiply(k, log10));
return BigDecimal.multiply(BigDecimal.exp(r, internalRounding), exponentiate(BigDecimal.BigDecimal(10), BigDecimal.toBigInt(k)));
}
// https://en.wikipedia.org/wiki/Exponential_function#Computation
Expand All @@ -470,7 +472,7 @@ function divideByHalfOfPI(x, rounding) { // x = k*pi/2 + r + 2*pi*n, where |r| <
throw new RangeError();
}
if (BigDecimal.greaterThan(x, BigDecimal.divide(BigDecimal.BigDecimal(785398163397448), BigDecimal.BigDecimal(1000000000000000)))) {
var halfOfPi = BigDecimal.multiply(BigDecimal.BigDecimal(2), BigDecimal.atan(BigDecimal.BigDecimal(1), {maximumSignificantDigits: rounding.maximumSignificantDigits + Number(getCountOfDigits(x)), roundingMode: "half-even"}));
var halfOfPi = BigDecimal.multiply(BigDecimal.BigDecimal(2), BigDecimal.atan(BigDecimal.BigDecimal(1), {maximumSignificantDigits: rounding.maximumSignificantDigits + Number(getCountOfDigits(x)) + 1, roundingMode: "half-even"}));
var i = BigDecimal.divide(x, halfOfPi, {maximumFractionDigits: 0, roundingMode: "half-even"});
var remainder = BigDecimal.subtract(x, BigDecimal.multiply(i, halfOfPi));
return {remainder: remainder, k: (Number(BigDecimal.toBigInt(i) % 4n) + 4) % 4};
Expand Down Expand Up @@ -511,7 +513,7 @@ BigDecimal.sin = tryToMakeCorrectlyRounded(0, function (x, rounding) {
sum = BigDecimal.add(sum, term, internalRounding);
}
return BigDecimal.multiply(a, sum);
});
}, "sin");

BigDecimal.cos = tryToMakeCorrectlyRounded(0, function (x, rounding) {
if (BigDecimal.lessThan(x, BigDecimal.BigDecimal(0))) {
Expand Down Expand Up @@ -546,14 +548,14 @@ BigDecimal.cos = tryToMakeCorrectlyRounded(0, function (x, rounding) {
sum = BigDecimal.add(sum, term, internalRounding);
}
return sum;
});
}, "cos");

BigDecimal.atan = tryToMakeCorrectlyRounded(0, function (x, rounding) {
if (BigDecimal.greaterThan(abs(x), BigDecimal.BigDecimal(1))) {
var halfOfPi = BigDecimal.multiply(BigDecimal.atan(BigDecimal.BigDecimal(1), rounding), BigDecimal.BigDecimal(2));
return BigDecimal.multiply(sign(x), BigDecimal.subtract(halfOfPi, BigDecimal.atan(BigDecimal.divide(BigDecimal.BigDecimal(1), abs(x), rounding), rounding)));
}
// https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#Infinite_series
// https://en.wikipedia.org/wiki/Inverse_trigonometric_functions#:~:text=Alternatively,%20this%20can%20be%20expressed%20as
var internalRounding = {
maximumSignificantDigits: rounding.maximumSignificantDigits + Math.ceil(Math.log(rounding.maximumSignificantDigits + 0.5) / Math.log(10)),
roundingMode: "half-even"
Expand All @@ -564,14 +566,14 @@ BigDecimal.atan = tryToMakeCorrectlyRounded(0, function (x, rounding) {
var lastSum = BigDecimal.BigDecimal(0);
while (!BigDecimal.equal(lastSum, sum)) {
n += 1;
term = BigDecimal.multiply(term, BigDecimal.BigDecimal((2 * n) * (2 * n)));
term = BigDecimal.divide(term, BigDecimal.BigDecimal((2 * n) * (2 * n + 1)), internalRounding);
term = BigDecimal.multiply(term, BigDecimal.BigDecimal(2 * n));
term = BigDecimal.divide(term, BigDecimal.BigDecimal(2 * n + 1), internalRounding);
term = BigDecimal.multiply(term, BigDecimal.multiply(x, x));
term = BigDecimal.divide(term, BigDecimal.add(BigDecimal.BigDecimal(1), BigDecimal.multiply(x, x)), internalRounding);
lastSum = sum;
sum = BigDecimal.add(sum, term, internalRounding);
}
return BigDecimal.multiply(x, sum);
});
}, "atan");

export default BigDecimal;
112 changes: 112 additions & 0 deletions BigDecimalByDecimal.js.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@

import Decimal from './node_modules/decimal.js/decimal.mjs';

function rm(roundingMode) {
if (roundingMode === 'floor') {
return Decimal.ROUND_FLOOR;
}
if (roundingMode === 'ceil') {
return Decimal.ROUND_CEIL;
}
if (roundingMode === 'half-even') {
return Decimal.ROUND_HALF_EVEN;
}
if (roundingMode === 'half-up') {
return Decimal.ROUND_HALF_UP;
}
if (roundingMode === 'half-down') {
return Decimal.ROUND_HALF_DOWN;
}
throw new RangeError('rounding mode: ' + roundingMode);
}

function setRounding(rounding) {
if (rounding != null) {
if (rounding.maximumFractionDigits != undefined) {
throw new RangeError("rounding to maximumFractionDigits is not supported");
}
Decimal.precision = rounding.maximumSignificantDigits;
Decimal.rounding = rm(rounding.roundingMode);
} else {
Decimal.precision = 1e+9;
}
}

function BigDecimal() {
}
BigDecimal.BigDecimal = function (value) {
return Decimal(typeof value === 'number' ? value : value.toString());
};
BigDecimal.toBigInt = function (a) {
return BigInt(a.toFixed(0));
};
BigDecimal.toNumber = function (a) {
return a.toNumber();
};
BigDecimal.unaryMinus = function (a) {
return a.negated();
};
BigDecimal.add = function (a, b, rounding) {
setRounding(rounding);
return a.add(b);
};
BigDecimal.subtract = function (a, b, rounding) {
setRounding(rounding);
return a.sub(b);
};
BigDecimal.multiply = function (a, b, rounding) {
setRounding(rounding);
return a.mul(b);
};
BigDecimal.divide = function (a, b, rounding) {
if (rounding == null) {
//throw new RangeError();
setRounding(null); // it is working for exact divisions and throws an exception for inexact, seems
} else {
setRounding(rounding);
}
return a.div(b);
};

BigDecimal.lessThan = function (a, b) {
return a.lt(b);
};
BigDecimal.greaterThan = function (a, b) {
return a.gt(b);
};
BigDecimal.equal = function (a, b) {
return a.eq(b);
};

BigDecimal.cos = function (a, rounding) {
setRounding(rounding);
return a.cos();
};
BigDecimal.sin = function (a, rounding) {
setRounding(rounding);
return a.sin();
};
BigDecimal.exp = function (a, rounding) {
setRounding(rounding);
return a.exp();
};
BigDecimal.log = function (a, rounding) {
setRounding(rounding);
return a.ln();
};
BigDecimal.atan = function (a, rounding) {
setRounding(rounding);
return a.atan();
};

BigDecimal.round = function (a, rounding) {
if (rounding.maximumFractionDigits != undefined) {
return a.toDecimalPlaces(rounding.maximumFractionDigits, rm(rounding.roundingMode));
}
if (rounding.maximumSignificantDigits == undefined) {
throw new RangeError('rounding without maximumSignificantDigits is not supported');
}
return a.toSignificantDigits(rounding.maximumSignificantDigits, rm(rounding.roundingMode));
};

export default BigDecimal;
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@yaffle/bigdecimal",
"version": "1.0.2",
"version": "1.0.3",
"description": "Arbitrary precision decimal arithmetic library. Polyfill for decimal proposal. Implemented on the top of BigInt.",
"main": "BigDecimal.js",
"scripts": {
Expand All @@ -26,5 +26,8 @@
"url": "https://github.com/Yaffle/BigDecimal/issues"
},
"homepage": "https://github.com/Yaffle/BigDecimal#readme",
"type": "module"
"type": "module",
"devDependencies": {
"decimal.js": "^10.2.0"
}
}
Loading

0 comments on commit 437fa0a

Please sign in to comment.