From 22ac9377c62ff9b731586c708b7b9b09a8f1e3cd Mon Sep 17 00:00:00 2001 From: Michael Mclaughlin Date: Tue, 27 Jun 2017 22:07:37 +0100 Subject: [PATCH] Bugfix: #58 pow sometimes throws when result is Infinity --- decimal.es6.js | 67 ++++++++++++++++++++++++------------------- decimal.js | 70 +++++++++++++++++++++++++-------------------- test/modules/pow.js | 10 +++++++ 3 files changed, 87 insertions(+), 60 deletions(-) diff --git a/decimal.es6.js b/decimal.es6.js index a684aaf..da50dbe 100644 --- a/decimal.es6.js +++ b/decimal.es6.js @@ -1,6 +1,6 @@ /* * - * decimal.js v7.2.2 + * decimal.js v7.2.3 * An arbitrary-precision Decimal type for JavaScript. * https://github.com/MikeMcl/decimal.js * Copyright (c) 2017 Michael Mclaughlin @@ -2247,13 +2247,13 @@ P.toOctal = function (sd, rm) { * */ P.toPower = P.pow = function (y) { - var e, k, pr, r, rm, sign, yIsInt, + var e, k, pr, r, rm, s, x = this, Ctor = x.constructor, yn = +(y = new Ctor(y)); // Either ±Infinity, NaN or ±0? - if (!x.d || !y.d || !x.d[0] || !y.d[0]) return new Ctor(mathpow(+x, yn)); + if (!x.d || !y.d || !x.d[0] || !y.d[0]) return new Ctor(mathpow(+x, yn)); x = new Ctor(x); @@ -2264,26 +2264,31 @@ P.toPower = P.pow = function (y) { if (y.eq(1)) return finalise(x, pr, rm); + // y exponent e = mathfloor(y.e / LOG_BASE); - k = y.d.length - 1; - yIsInt = e >= k; - sign = x.s; - - if (!yIsInt) { - if (sign < 0) return new Ctor(NaN); // If y is a small integer use the 'exponentiation by squaring' algorithm. - } else if ((k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) { + if (e >= y.d.length - 1 && (k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) { r = intPow(Ctor, x, k, pr); return y.s < 0 ? new Ctor(1).div(r) : finalise(r, pr, rm); } - // Result is negative if x is negative and the last digit of integer y is odd. - sign = sign < 0 && y.d[Math.max(e, k)] & 1 ? -1 : 1; + s = x.s; - if (x.eq(-1)) { - x.s = sign; - return x; + // if x is negative + if (s < 0) { + + // if y is not an integer + if (e < y.d.length - 1) return new Ctor(NaN); + + // Result is positive if x is negative and the last digit of integer y is even. + if ((y.d[e] & 1) == 0) s = 1; + + // if x.eq(-1) + if (x.e == 0 && x.d[0] == 1 && x.d.length == 1) { + x.s = s; + return x; + } } // Estimate result exponent. @@ -2295,10 +2300,10 @@ P.toPower = P.pow = function (y) { ? mathfloor(yn * (Math.log('0.' + digitsToString(x.d)) / Math.LN10 + x.e + 1)) : new Ctor(k + '').e; - // Estimate may be incorrect e.g. x: 0.999999999999999999, y: 2.29, e: 0, r.e: -1. + // Exponent estimate may be incorrect e.g. x: 0.999999999999999999, y: 2.29, e: 0, r.e: -1. // Overflow/underflow? - if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) return new Ctor(e > 0 ? sign / 0 : 0); + if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) return new Ctor(e > 0 ? s / 0 : 0); external = false; Ctor.rounding = x.s = 1; @@ -2312,24 +2317,28 @@ P.toPower = P.pow = function (y) { // r = x^y = exp(y*ln(x)) r = naturalExponential(y.times(naturalLogarithm(x, pr + k)), pr); - // Truncate to the required precision plus five rounding digits. - r = finalise(r, pr + 5, 1); + // r may be Infinity, e.g. (0.9999999999999999).pow(-1e+40) + if (r.d) { - // If the rounding digits are [49]9999 or [50]0000 increase the precision by 10 and recalculate - // the result. - if (checkRoundingDigits(r.d, pr, rm)) { - e = pr + 10; + // Truncate to the required precision plus five rounding digits. + r = finalise(r, pr + 5, 1); - // Truncate to the increased precision plus five rounding digits. - r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1); + // If the rounding digits are [49]9999 or [50]0000 increase the precision by 10 and recalculate + // the result. + if (checkRoundingDigits(r.d, pr, rm)) { + e = pr + 10; - // Check for 14 nines from the 2nd rounding digit (the first rounding digit may be 4 or 9). - if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 1e14) { - r = finalise(r, pr + 1, 0); + // Truncate to the increased precision plus five rounding digits. + r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1); + + // Check for 14 nines from the 2nd rounding digit (the first rounding digit may be 4 or 9). + if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 1e14) { + r = finalise(r, pr + 1, 0); + } } } - r.s = sign; + r.s = s; external = true; Ctor.rounding = rm; diff --git a/decimal.js b/decimal.js index 2c1291f..2103dab 100644 --- a/decimal.js +++ b/decimal.js @@ -1,10 +1,10 @@ -/*! decimal.js v7.2.2 https://github.com/MikeMcl/decimal.js/LICENCE */ +/*! decimal.js v7.2.3 https://github.com/MikeMcl/decimal.js/LICENCE */ ;(function (globalScope) { 'use strict'; /* - * decimal.js v7.2.2 + * decimal.js v7.2.3 * An arbitrary-precision Decimal type for JavaScript. * https://github.com/MikeMcl/decimal.js * Copyright (c) 2017 Michael Mclaughlin @@ -2249,13 +2249,13 @@ * */ P.toPower = P.pow = function (y) { - var e, k, pr, r, rm, sign, yIsInt, + var e, k, pr, r, rm, s, x = this, Ctor = x.constructor, yn = +(y = new Ctor(y)); // Either ±Infinity, NaN or ±0? - if (!x.d || !y.d || !x.d[0] || !y.d[0]) return new Ctor(mathpow(+x, yn)); + if (!x.d || !y.d || !x.d[0] || !y.d[0]) return new Ctor(mathpow(+x, yn)); x = new Ctor(x); @@ -2266,26 +2266,31 @@ if (y.eq(1)) return finalise(x, pr, rm); + // y exponent e = mathfloor(y.e / LOG_BASE); - k = y.d.length - 1; - yIsInt = e >= k; - sign = x.s; - - if (!yIsInt) { - if (sign < 0) return new Ctor(NaN); // If y is a small integer use the 'exponentiation by squaring' algorithm. - } else if ((k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) { + if (e >= y.d.length - 1 && (k = yn < 0 ? -yn : yn) <= MAX_SAFE_INTEGER) { r = intPow(Ctor, x, k, pr); return y.s < 0 ? new Ctor(1).div(r) : finalise(r, pr, rm); } - // Result is negative if x is negative and the last digit of integer y is odd. - sign = sign < 0 && y.d[Math.max(e, k)] & 1 ? -1 : 1; + s = x.s; - if (x.eq(-1)) { - x.s = sign; - return x; + // if x is negative + if (s < 0) { + + // if y is not an integer + if (e < y.d.length - 1) return new Ctor(NaN); + + // Result is positive if x is negative and the last digit of integer y is even. + if ((y.d[e] & 1) == 0) s = 1; + + // if x.eq(-1) + if (x.e == 0 && x.d[0] == 1 && x.d.length == 1) { + x.s = s; + return x; + } } // Estimate result exponent. @@ -2293,15 +2298,14 @@ // log10(x) = log10(x_significand) + x_exponent // log10(x_significand) = ln(x_significand) / ln(10) k = mathpow(+x, yn); - e = k == 0 || !isFinite(k) ? mathfloor(yn * (Math.log('0.' + digitsToString(x.d)) / Math.LN10 + x.e + 1)) : new Ctor(k + '').e; - // Estimate may be incorrect e.g. x: 0.999999999999999999, y: 2.29, e: 0, r.e: -1. + // Exponent estimate may be incorrect e.g. x: 0.999999999999999999, y: 2.29, e: 0, r.e: -1. // Overflow/underflow? - if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) return new Ctor(e > 0 ? sign / 0 : 0); + if (e > Ctor.maxE + 1 || e < Ctor.minE - 1) return new Ctor(e > 0 ? s / 0 : 0); external = false; Ctor.rounding = x.s = 1; @@ -2315,24 +2319,28 @@ // r = x^y = exp(y*ln(x)) r = naturalExponential(y.times(naturalLogarithm(x, pr + k)), pr); - // Truncate to the required precision plus five rounding digits. - r = finalise(r, pr + 5, 1); + // r may be Infinity, e.g. (0.9999999999999999).pow(-1e+40) + if (r.d) { - // If the rounding digits are [49]9999 or [50]0000 increase the precision by 10 and recalculate - // the result. - if (checkRoundingDigits(r.d, pr, rm)) { - e = pr + 10; + // Truncate to the required precision plus five rounding digits. + r = finalise(r, pr + 5, 1); - // Truncate to the increased precision plus five rounding digits. - r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1); + // If the rounding digits are [49]9999 or [50]0000 increase the precision by 10 and recalculate + // the result. + if (checkRoundingDigits(r.d, pr, rm)) { + e = pr + 10; - // Check for 14 nines from the 2nd rounding digit (the first rounding digit may be 4 or 9). - if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 1e14) { - r = finalise(r, pr + 1, 0); + // Truncate to the increased precision plus five rounding digits. + r = finalise(naturalExponential(y.times(naturalLogarithm(x, e + k)), e), e + 5, 1); + + // Check for 14 nines from the 2nd rounding digit (the first rounding digit may be 4 or 9). + if (+digitsToString(r.d).slice(pr + 1, pr + 15) + 1 == 1e14) { + r = finalise(r, pr + 1, 0); + } } } - r.s = sign; + r.s = s; external = true; Ctor.rounding = rm; diff --git a/test/modules/pow.js b/test/modules/pow.js index 3e46acb..fc23f89 100644 --- a/test/modules/pow.js +++ b/test/modules/pow.js @@ -109,6 +109,11 @@ T('pow', function () { t('8.97', '-1', '0.111482720178', 12, 3); t('61766796871807246.3278075', '-1', '0.00000000000000001618993', 7, 0); + t('-1', '101', '-1', 100, 1); + t('-1', '9999999999999999999999999999999999999999999999999999999999999999999999999', '-1', 100, 1); + t('-1', '1e307', '1', 100, 1); + t('-1', '1e309', '1', 100, 1); + Decimal.toExpNeg = Decimal.toExpPos = 0; t('9.9999999999999', '2220.75', '5.623413251778e+2220', 13, 1); @@ -126,4 +131,9 @@ T('pow', function () { t('908948247.896330216349750387912923575076135766138', '11.38907521122213262858256836', '1.0702278292293091784680297675223031e+102', 35, 3); t('4.485925762349120387154391E+47', '1677945.16766265206929939', '8.53959030215133943e+79957194', 18, 5); t('2.8448989811706207675566E+89', '2.368592228588521845032068137267440272102614', '7.58940197453762187722508511706932e+211', 33, 5); + + t('0.9999999999999999', '-1e+30', '1.530863912e+43429448190325', 10, 1); + t('0.9999999999999999999999999999999999999999999999999', '-1e+32', '1.00000000000000001000000000000000005e+0', 36, 1); + t('0.9999999999999999', '-1e+50', 'Infinity', 40, 1); + t('0.9999999999999999999999999999999899999999999999994403269002375809806554775739676251993670310626872684', '-1.49181945463118148622657269735650603014891811120124843379694396257337810020127409048127397077199569e+271', 'Infinity', 100, 1); });