diff --git a/bignumber.js b/bignumber.js index c4a96c3..d7ebaf3 100644 --- a/bignumber.js +++ b/bignumber.js @@ -26,8 +26,10 @@ * isLessThanOrEqualTo lte | minimum min * isNaN | random * isNegative | sum - * isPositive | + * isPositive | euler e * isZero | + * logBase log | + * naturalLog ln | * minus | * modulo mod | * multipliedBy times | @@ -74,7 +76,7 @@ * Create and return a BigNumber constructor. */ function clone(configObject) { - var div, convertBase, parseNumeric, + var div, log, convertBase, parseNumeric, P = BigNumber.prototype = { constructor: BigNumber, toString: null, valueOf: null }, ONE = new BigNumber(1), @@ -808,6 +810,27 @@ return sum; }; + /* + * Returns e to dp decimal places + * Runtime could be improved with binary splitting at expense of storage + * Runtime of 5,000 digits was ~3s + */ + + BigNumber.e = BigNumber.euler = function (dp, rm) { + let oldDp = DECIMAL_PLACES + dp = dp | oldDp + BigNumber.set({DECIMAL_PLACES: dp + 1}) + let e = BigNumber('1') + let divisor = BigNumber('1') + let one = BigNumber('1') + for (let i = 2; i < dp * 10; i++) { + e = e.plus(one.div(divisor)) + divisor = divisor.multipliedBy(BigNumber(i)) + } + e = BigNumber(round(e, dp, rm, true).toString()) + BigNumber.set({DECIMAL_PLACES: oldDp}) + return BigNumber(round(e, dp, rm, true).toString()) + } // PRIVATE FUNCTIONS @@ -1223,6 +1246,44 @@ })(); + // Perform logorithm using the method by Daniel Shanks https://www.ams.org/journals/mcom/1954-08-046/S0025-5718-1954-0061464-9/S0025-5718-1954-0061464-9.pdf + log = (function () { + // x: base, y: number + return function (x, y, dp, rm) { + if (x.comparedTo(0) + y.comparedTo(0) !== 2 || !x.isFinite() || !y.isFinite() || y.valueOf() === '1') { + return BigNumber(NaN) + } + var A = y + var B = x + var C = BigNumber(1) + var D = BigNumber(0) + var E = BigNumber(0) + var F = BigNumber(1) + var s = 1 + var one = BigNumber('1') + if (A.comparedTo(one) === -1) { + A = one.div(A) + s *= -1 + } + if (B.comparedTo(one) === -1) { + B = one.div(B) + s *= -1 + } + for (let i = 0; i < dp * 1.1 + 5; i) { + if (A.comparedTo(B) > -1 && B.valueOf() !== '1') { + [A, C, D] = [A.div(B), C.plus(E), D.plus(F)] + } else if (B.valueOf() === '1') { + break + } else { + [A, B, C, D, E, F] = [B, A, E, F, C, D] + i++ + } + + } + return round(E.div(F).multipliedBy(s), dp, rm) + }; + })(); + /* * Return a string representing the value of BigNumber n in fixed-point or exponential * notation rounded to the specified decimal places or significant digits. @@ -1669,6 +1730,18 @@ }; + /* + * Return a new BigNumber whose value is the value of log with base this BigNumber of the value + * BigNumber(y, b), rounded according to DECIMAL_PLACES and ROUNDING_MODE + */ + P.logBase = P.log = function (y, b) { + return log(this, new BigNumber(y, b), DECIMAL_PLACES, ROUNDING_MODE) + } + + P.naturalLog = P.ln = function () { + return log(this, BigNumber.euler(DECIMAL_PLACES), DECIMAL_PLACES, ROUNDING_MODE) + } + /* * Return a BigNumber whose value is the value of this BigNumber exponentiated by n. * diff --git a/test/methods/euler.js b/test/methods/euler.js new file mode 100644 index 0000000..4217a8f --- /dev/null +++ b/test/methods/euler.js @@ -0,0 +1,28 @@ +if (typeof Test === 'undefined') require('../tester'); + +Test('euler', function () { + function t(digits, expected) { + BigNumber.set({DECIMAL_PLACES: digits}) + Test.areEqual(String(expected), String(new BigNumber.euler())); + } + + Test.areEqual(BigNumber.e, BigNumber.euler); + + BigNumber.config({ + DECIMAL_PLACES: 20, + ROUNDING_MODE: 4, + EXPONENTIAL_AT: [-7, 21], + RANGE: 1E9 + }); + + t(1, '3') + t(2, '2.7') + t(10,'2.718281828') + t(20,'2.7182818284590452354') + t(30,'2.71828182845904523536028747135') + t(37, "2.718281828459045235360287471352662498") + t(130, "2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466391932003059921817413597") + t(306, "2.7182818284590452353602874713526624977572470936999595749669676277240766303535475945713821785251664274274663919320030599218174135966290435729003342952605956307381323286279434907632338298807531952510190115738341879307021540891499348841675092447614606680822648001684774118537423454424371075390777449920695517") + // Performance drops significantly at around 1000+ digits + t(1000, "2.718281828459045235360287471352662497757247093699959574966967627724076630353547594571382178525166427427466391932003059921817413596629043572900334295260595630738132328627943490763233829880753195251019011573834187930702154089149934884167509244761460668082264800168477411853742345442437107539077744992069551702761838606261331384583000752044933826560297606737113200709328709127443747047230696977209310141692836819025515108657463772111252389784425056953696770785449969967946864454905987931636889230098793127736178215424999229576351482208269895193668033182528869398496465105820939239829488793320362509443117301238197068416140397019837679320683282376464804295311802328782509819455815301756717361332069811250996181881593041690351598888519345807273866738589422879228499892086805825749279610484198444363463244968487560233624827041978623209002160990235304369941849146314093431738143640546253152096183690888707016768396424378140592714563549061303107208510383750510115747704171898610687396965521267154688957035035") +}); diff --git a/test/methods/logarithm.js b/test/methods/logarithm.js new file mode 100644 index 0000000..4f8d9fd --- /dev/null +++ b/test/methods/logarithm.js @@ -0,0 +1,51 @@ +if (typeof Test === 'undefined') require('../tester'); + +Test('logarithm', function () { + var n = 'null', + N = 'NaN', + I = 'Infinity'; + + function t(number, base, expected) { + Test.areEqual(String(expected), String(new BigNumber(number).log(base))); + } + + Test.areEqual(BigNumber.prototype.log, BigNumber.prototype.logBase); + + BigNumber.config({ + DECIMAL_PLACES: 20, + ROUNDING_MODE: 4, + EXPONENTIAL_AT: [-7, 21], + RANGE: 1E9 + }); + + // Edge case tests + t(I, I, N) + t(I, '-' + I, N) + t(0, I, N) + t(-0, I, N) + t(I, -0, N) + t(I, 0, N) + t(0, 0, N) + t(1, 0, N) + t(0, 1, N) + t(-1, 1, N) + t(1, -1, N) + t(I, 1, N) + + // Value Tests + t(10, 2, '3.3219280948873623479') + t('10.332323234', '0.434345', '-2.8003741336205112144') + t(100, 2, '6.6438561897747246957') + t(1000, 2, '9.9657842846620870436') + t(10, 4, '1.6609640474436811739') + t('3.3454545656', '.32764737467657', '-1.0822583142747469393') + t('9', '3', '2') + t('4', '2', '2') + t('95367431640625', '5', '20') + t(new BigNumber(1).div('95367431640625'), '5', '-20') + t(new BigNumber(1).div('95367431640625'), '.2', '20') + t('.47892384765743865096789478675847699', '.83426478236576437584768549685496', '4.0628897799653394675') + t('47892384765743865096789478675847699', '83426478236576437584768549685496', '1.0864301333053086708') + t('1', new BigNumber.euler(), '0') + t(new BigNumber.euler(), new BigNumber.euler(), '1') +}); diff --git a/test/test.js b/test/test.js index c47c37d..34aad68 100644 --- a/test/test.js +++ b/test/test.js @@ -13,9 +13,11 @@ console.log('\n Testing bignumber.js\n'); 'dividedBy', 'dividedToIntegerBy', 'decimalPlaces', + 'euler', 'exponentiatedBy', 'integerValue', 'isBigNumber', + 'logarithm', 'minmax', 'minus', 'modulo',