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

bugfix/804: Added handling of complex numbers negative bases and positive infinity exponent #2097

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions src/factoriesAny.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { createTyped } from './core/function/typed.js'
export { createResultSet } from './type/resultset/ResultSet.js'
export { createBigNumberClass } from './type/bignumber/BigNumber.js'
export { createComplexClass } from './type/complex/Complex.js'
export { createComplexInfinityClass } from './type/complex/ComplexInfinity.js'
export { createFractionClass } from './type/fraction/Fraction.js'
export { createRangeClass } from './type/matrix/Range.js'
export { createMatrixClass } from './type/matrix/Matrix.js'
Expand All @@ -22,6 +23,7 @@ export { createString } from './type/string.js'
export { createBoolean } from './type/boolean.js'
export { createBignumber } from './type/bignumber/function/bignumber.js'
export { createComplex } from './type/complex/function/complex.js'
export { createComplexInfinity } from './type/complex/function/complexInfinity.js'
export { createFraction } from './type/fraction/function/fraction.js'
export { createMatrix } from './type/matrix/function/matrix.js'
export { createSplitUnit } from './type/unit/function/splitUnit.js'
Expand Down Expand Up @@ -242,6 +244,7 @@ export { createDerivative } from './function/algebra/derivative.js'
export { createRationalize } from './function/algebra/rationalize.js'
export { createReviver } from './json/reviver.js'
export { createReplacer } from './json/replacer.js'
export { createIsInfinite } from './function/utils/isInfinite.js'
export {
createE,
createUppercaseE,
Expand Down
325 changes: 175 additions & 150 deletions src/function/arithmetic/pow.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,179 +12,204 @@ const dependencies = [
'matrix',
'fraction',
'number',
'Complex'
'Complex',
'ComplexInfinity'
]

export const createPow = /* #__PURE__ */ factory(name, dependencies, ({ typed, config, identity, multiply, matrix, number, fraction, Complex }) => {
/**
* Calculates the power of x to y, `x ^ y`.
* Matrix exponentiation is supported for square matrices `x`, and positive
* integer exponents `y`.
*
* For cubic roots of negative numbers, the function returns the principal
* root by default. In order to let the function return the real root,
* math.js can be configured with `math.config({predictable: true})`.
* To retrieve all cubic roots of a value, use `math.cbrt(x, true)`.
*
* Syntax:
*
* math.pow(x, y)
*
* Examples:
*
* math.pow(2, 3) // returns number 8
*
* const a = math.complex(2, 3)
* math.pow(a, 2) // returns Complex -5 + 12i
*
* const b = [[1, 2], [4, 3]]
* math.pow(b, 2) // returns Array [[9, 8], [16, 17]]
*
* See also:
*
* multiply, sqrt, cbrt, nthRoot
*
* @param {number | BigNumber | Complex | Unit | Array | Matrix} x The base
* @param {number | BigNumber | Complex} y The exponent
* @return {number | BigNumber | Complex | Array | Matrix} The value of `x` to the power `y`
*/
return typed(name, {
'number, number': _pow,

'Complex, Complex': function (x, y) {
return x.pow(y)
},

'BigNumber, BigNumber': function (x, y) {
if (y.isInteger() || x >= 0 || config.predictable) {
export const createPow = /* #__PURE__ */ factory(
name,
dependencies,
({
typed,
config,
identity,
multiply,
matrix,
number,
fraction,
Complex,
ComplexInfinity
}) => {
/**
* Calculates the power of x to y, `x ^ y`.
* Matrix exponentiation is supported for square matrices `x`, and positive
* integer exponents `y`.
*
* For cubic roots of negative numbers, the function returns the principal
* root by default. In order to let the function return the real root,
* math.js can be configured with `math.config({predictable: true})`.
* To retrieve all cubic roots of a value, use `math.cbrt(x, true)`.
*
* Syntax:
*
* math.pow(x, y)
*
* Examples:
*
* math.pow(2, 3) // returns number 8
*
* const a = math.complex(2, 3)
* math.pow(a, 2) // returns Complex -5 + 12i
*
* const b = [[1, 2], [4, 3]]
* math.pow(b, 2) // returns Array [[9, 8], [16, 17]]
*
* See also:
*
* multiply, sqrt, cbrt, nthRoot
*
* @param {number | BigNumber | Complex | Unit | Array | Matrix} x The base
* @param {number | BigNumber | Complex} y The exponent
* @return {number | BigNumber | Complex | Array | Matrix} The value of `x` to the power `y`
*/
return typed(name, {
'number, number': _pow,

'Complex, Complex': function (x, y) {
if (x.isInfinite() || y.isInfinite()) {
return new ComplexInfinity()
}
return x.pow(y)
} else {
return new Complex(x.toNumber(), 0).pow(y.toNumber(), 0)
}
},
},

'Fraction, Fraction': function (x, y) {
if (y.d !== 1) {
if (config.predictable) {
throw new Error('Function pow does not support non-integer exponents for fractions.')
'BigNumber, BigNumber': function (x, y) {
if (y.isInteger() || x >= 0 || config.predictable) {
return x.pow(y)
} else {
return _pow(x.valueOf(), y.valueOf())
return new Complex(x.toNumber(), 0).pow(y.toNumber(), 0)
}
} else {
return x.pow(y)
}
},

'Array, number': _powArray,
},

'Fraction, Fraction': function (x, y) {
if (y.d !== 1) {
if (config.predictable) {
throw new Error(
'Function pow does not support non-integer exponents for fractions.'
)
} else {
return _pow(x.valueOf(), y.valueOf())
}
} else {
return x.pow(y)
}
},

'Array, BigNumber': function (x, y) {
return _powArray(x, y.toNumber())
},
'Array, number': _powArray,

'Matrix, number': _powMatrix,
'Array, BigNumber': function (x, y) {
return _powArray(x, y.toNumber())
},

'Matrix, BigNumber': function (x, y) {
return _powMatrix(x, y.toNumber())
},
'Matrix, number': _powMatrix,

'Unit, number | BigNumber': function (x, y) {
return x.pow(y)
}
'Matrix, BigNumber': function (x, y) {
return _powMatrix(x, y.toNumber())
},

})

/**
* Calculates the power of x to y, x^y, for two numbers.
* @param {number} x
* @param {number} y
* @return {number | Complex} res
* @private
*/
function _pow (x, y) {
// Alternatively could define a 'realmode' config option or something, but
// 'predictable' will work for now
if (config.predictable && !isInteger(y) && x < 0) {
// Check to see if y can be represented as a fraction
try {
const yFrac = fraction(y)
const yNum = number(yFrac)
if (y === yNum || Math.abs((y - yNum) / y) < 1e-14) {
if (yFrac.d % 2 === 1) {
return (yFrac.n % 2 === 0 ? 1 : -1) * Math.pow(-x, y)
'Unit, number | BigNumber': function (x, y) {
return x.pow(y)
}
})

/**
* Calculates the power of x to y, x^y, for two numbers.
* @param {number} x
* @param {number} y
* @return {number | Complex} res
* @private
*/
function _pow (x, y) {
// Alternatively could define a 'realmode' config option or something, but
// 'predictable' will work for now
if (config.predictable && !isInteger(y) && x < 0) {
// Check to see if y can be represented as a fraction
try {
const yFrac = fraction(y)
const yNum = number(yFrac)
if (y === yNum || Math.abs((y - yNum) / y) < 1e-14) {
if (yFrac.d % 2 === 1) {
return (yFrac.n % 2 === 0 ? 1 : -1) * Math.pow(-x, y)
}
}
} catch (ex) {
// fraction() throws an error if y is Infinity, etc.
}
} catch (ex) {
// fraction() throws an error if y is Infinity, etc.

// Unable to express y as a fraction, so continue on
}

// Unable to express y as a fraction, so continue on
}
// **for predictable mode** x^Infinity === NaN if x < -1
// N.B. this behavour is different from `Math.pow` which gives
// (-2)^Infinity === Infinity
if (
config.predictable &&
((x < -1 && y === Infinity) || (x > -1 && x < 0 && y === -Infinity))
) {
return NaN
}

// **for predictable mode** x^Infinity === NaN if x < -1
// N.B. this behavour is different from `Math.pow` which gives
// (-2)^Infinity === Infinity
if (config.predictable &&
((x < -1 && y === Infinity) ||
(x > -1 && x < 0 && y === -Infinity))) {
return NaN
}
if (isInteger(y) || x >= 0 || config.predictable) {
return powNumber(x, y)
} else {
// TODO: the following infinity checks are duplicated from powNumber. Deduplicate this somehow

if (isInteger(y) || x >= 0 || config.predictable) {
return powNumber(x, y)
} else {
// TODO: the following infinity checks are duplicated from powNumber. Deduplicate this somehow
// x^Infinity === 0 if -1 < x < 1
// A real number 0 is returned instead of complex(0)
if ((x * x < 1 && y === Infinity) || (x * x > 1 && y === -Infinity)) {
return 0
}

// x^Infinity === 0 if -1 < x < 1
// A real number 0 is returned instead of complex(0)
if ((x * x < 1 && y === Infinity) ||
(x * x > 1 && y === -Infinity)) {
return 0
return new Complex(x, 0).pow(y, 0)
}

return new Complex(x, 0).pow(y, 0)
}
}

/**
* Calculate the power of a 2d array
* @param {Array} x must be a 2 dimensional, square matrix
* @param {number} y a positive, integer value
* @returns {Array}
* @private
*/
function _powArray (x, y) {
if (!isInteger(y) || y < 0) {
throw new TypeError('For A^b, b must be a positive integer (value is ' + y + ')')
}
// verify that A is a 2 dimensional square matrix
const s = size(x)
if (s.length !== 2) {
throw new Error('For A^b, A must be 2 dimensional (A has ' + s.length + ' dimensions)')
}
if (s[0] !== s[1]) {
throw new Error('For A^b, A must be square (size is ' + s[0] + 'x' + s[1] + ')')
}
/**
* Calculate the power of a 2d array
* @param {Array} x must be a 2 dimensional, square matrix
* @param {number} y a positive, integer value
* @returns {Array}
* @private
*/
function _powArray (x, y) {
if (!isInteger(y) || y < 0) {
throw new TypeError(
'For A^b, b must be a positive integer (value is ' + y + ')'
)
}
// verify that A is a 2 dimensional square matrix
const s = size(x)
if (s.length !== 2) {
throw new Error(
'For A^b, A must be 2 dimensional (A has ' + s.length + ' dimensions)'
)
}
if (s[0] !== s[1]) {
throw new Error(
'For A^b, A must be square (size is ' + s[0] + 'x' + s[1] + ')'
)
}

let res = identity(s[0]).valueOf()
let px = x
while (y >= 1) {
if ((y & 1) === 1) {
res = multiply(px, res)
let res = identity(s[0]).valueOf()
let px = x
while (y >= 1) {
if ((y & 1) === 1) {
res = multiply(px, res)
}
y >>= 1
px = multiply(px, px)
}
y >>= 1
px = multiply(px, px)
return res
}
return res
}

/**
* Calculate the power of a 2d matrix
* @param {Matrix} x must be a 2 dimensional, square matrix
* @param {number} y a positive, integer value
* @returns {Matrix}
* @private
*/
function _powMatrix (x, y) {
return matrix(_powArray(x.valueOf(), y))
/**
* Calculate the power of a 2d matrix
* @param {Matrix} x must be a 2 dimensional, square matrix
* @param {number} y a positive, integer value
* @returns {Matrix}
* @private
*/
function _powMatrix (x, y) {
return matrix(_powArray(x.valueOf(), y))
}
}
})
)
Loading