Merge pull request #1060 from dakotablair/develop

Fixed #851: More consistent behavior of sqrt, nthRoot, and pow
This commit is contained in:
Jos de Jong 2018-03-24 11:52:44 +01:00 committed by GitHub
commit 138bebd655
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 5807 additions and 5570 deletions

View File

@ -1,5 +1,12 @@
# History
## 2017-03-17, version 4.0.1
- Fixed #1062: mathjs not working on ES5 browsers like IE11 and Safari 9.3.
- Fixed #1061: `math.unit` not accepting input like `1/s`.
## 2018-02-25, version 4.0.0
!!! BE CAREFUL: BREAKING CHANGES !!!

11043
dist/math.js vendored

File diff suppressed because it is too large Load Diff

8
dist/math.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/math.min.map vendored

File diff suppressed because one or more lines are too long

View File

@ -23,17 +23,23 @@ The following configuration options are available:
determined by the option `matrix`. In case of mixed matrix
inputs, a matrix will be returned always.
- `number`. The default type of numbers. This setting is used by functions
like `eval` which cannot determine the correct type of output from the
functions input. For most functions though, the type of output is determined
from the the input: a number as input will return a number as output,
- `number`. The type of numeric output for functions which cannot
determine the numeric type from the inputs. For most functions though,
the type of output is determined from the the input:
a number as input will return a number as output,
a BigNumber as input returns a BigNumber as output.
For example the functions `math.eval('2+3')`, `math.parse('2+3')`,
`math.range('1:10')`, and `math.unit('5cm')` use the `number` configuration
setting. But `math.sqrt(4)` will always return the number `2`
regardless of the `number` configuration, because the input is a number.
Available values are: `'number'` (default), `'BigNumber'`, or `'Fraction'`.
[BigNumbers](../datatypes/bignumbers.js) have higher precision than the default
numbers of JavaScript, and [`Fractions`](../datatypes/fractions.js) store
values in terms of a numerator and denominator.
- `precision`. The maximum number of significant digits for bigNumbers.
- `precision`. The maximum number of significant digits for BigNumbers.
This setting only applies to BigNumbers, not to numbers.
Default value is `64`.

View File

@ -36,7 +36,7 @@ math.isPrime(0); // returns false
math.isPrime(-0); // returns false
math.isPrime(0.5); // returns false
math.isPrime('2'); // returns true
math.isPrime([2, 17, 100]'); // returns [true, true, false]
math.isPrime([2, 17, 100]); // returns [true, true, false]
```

View File

@ -15,7 +15,8 @@ module.exports = {
'sqrt(9)'
],
'seealso': [
'sqrt',
'pow'
'nthRoots',
'pow',
'sqrt'
]
};
};

View File

@ -0,0 +1,23 @@
module.exports = {
'name': 'nthRoots',
'category': 'Arithmetic',
'syntax': [
'nthRoots(A)',
'nthRoots(A, root)'
],
'description': (''
+ 'Calculate the nth roots of a value. '
+ 'An nth root of a positive real number A, '
+ 'is a positive real solution of the equation "x^root = A". '
+ 'This function returns an array of complex values.'
),
'examples': [
'nthRoots(1) == [ { re: 1, im: 0 }, { re: -1, im: 0 } ]',
'nthRoots(1, 3) == [ { re: 1, im: 0 }, { re: -0.4999999999999998, im: 0.8660254037844387 }, { re: -0.5000000000000004, im: -0.8660254037844385 } ] ]'
],
'seealso': [
'sqrt',
'pow',
'nthRoot'
]
};

View File

@ -12,5 +12,10 @@ module.exports = {
'2*2*2',
'1 + e ^ (pi * i)'
],
'seealso': [ 'multiply' ]
'seealso': [
'multiply',
'nthRoot',
'nthRoots',
'sqrt'
]
};

View File

@ -13,6 +13,9 @@ module.exports = {
],
'seealso': [
'square',
'multiply'
'multiply',
'nthRoot',
'nthRoots',
'pow'
]
};

View File

@ -130,6 +130,7 @@ function factory (construction, config, load, typed) {
docs.multiply = require('./function/arithmetic/multiply');
docs.norm = require('./function/arithmetic/norm');
docs.nthRoot = require('./function/arithmetic/nthRoot');
docs.nthRoots = require('./function/arithmetic/nthRoots');
docs.pow = require('./function/arithmetic/pow');
docs.round = require('./function/arithmetic/round');
docs.sign = require('./function/arithmetic/sign');

View File

@ -21,6 +21,7 @@ module.exports = [
require('./multiply'),
require('./norm'),
require('./nthRoot'),
require('./nthRoots'),
require('./pow'),
require('./round'),
require('./sign'),

View File

@ -40,6 +40,10 @@ function factory (type, config, load, typed) {
* @param {number | BigNumber} [root=2] The root.
* @return {number | Complex | Array | Matrix} Returns the nth root of `a`
*/
var complex_err = (''
+ 'Complex number not supported in function nthRoot. '
+ 'Use nthRoots instead.'
);
var nthRoot = typed('nthRoot', {
'number': function (x) {
@ -51,9 +55,11 @@ function factory (type, config, load, typed) {
return _bigNthRoot(x, new type.BigNumber(2));
},
'Complex' : function(x) {
return _nthComplexRoot(x, 2);
throw new Error(complex_err);
},
'Complex, number' : _nthComplexRoot,
'Complex, number' : function(x, y) {
throw new Error(complex_err);
},
'BigNumber, BigNumber': _bigNthRoot,
'Array | Matrix': function (x) {
@ -245,25 +251,5 @@ function _nthRoot(a, root) {
*/
}
/**
* Calculate the nth root of a Complex Number a using De Moviers Theorem.
* @param {Complex} a
* @param {number} root
* @return {Array} array or n Complex Roots in Polar Form.
*/
function _nthComplexRoot(a, root) {
if (root < 0) throw new Error('Root must be greater than zero');
if (root === 0) throw new Error('Root must be non-zero');
if (root % 1 !== 0) throw new Error('Root must be an integer');
var arg = a.arg();
var abs = a.abs();
var roots = [];
var r = Math.pow(abs, 1/root);
for(var k = 0; k < root; k++) {
roots.push({r: r, phi: (arg + 2 * Math.PI * k)/root});
}
return roots;
}
exports.name = 'nthRoot';
exports.factory = factory;

View File

@ -0,0 +1,76 @@
'use strict';
var Complex = require('../../type/complex/Complex');
var typed = require('../../core/typed');
var complex = Complex.factory(
'Complex', {}, '', typed, {on: function(x, y){}}
);
function factory (type, config, load, typed) {
var nthRoots = typed('nthRoots', {
'Complex' : function(x) {
return _nthComplexRoots(x, 2);
},
'Complex, number' : _nthComplexRoots,
});
nthRoots.toTex = {2: '\\{y : $y^{args[1]} = {${args[0]}}\\}'};
return nthRoots;
}
/**
* Each function here returns a real multiple of i as a Complex value.
* @param {number} val
* @return {Complex} val, i*val, -val or -i*val for index 0, 1, 2, 3
*/
// This is used to fix float artifacts for zero-valued components.
var _calculateExactResult = [
function realPos(val){return complex(val);},
function imagPos(val){return complex(0, val);},
function realNeg(val){return complex(-val);},
function imagNeg(val){return complex(0, -val);}
];
/**
* Calculate the nth root of a Complex Number a using De Movire's Theorem.
* @param {Complex} a
* @param {number} root
* @return {Array} array of n Complex Roots
*/
function _nthComplexRoots(a, root) {
if (root < 0) throw new Error('Root must be greater than zero');
if (root === 0) throw new Error('Root must be non-zero');
if (root % 1 !== 0) throw new Error('Root must be an integer');
if (a === 0 || a.abs() === 0) return [complex(0)];
var aIsNumeric = typeof(a) === 'number';
var offset;
// determine the offset (argument of a)/(pi/2)
if (aIsNumeric || a.re === 0 || a.im === 0) {
if (aIsNumeric) {
offset = 2*(+(a < 0)); // numeric value on the real axis
} else if (a.im === 0) {
offset = 2*(+(a.re < 0)); // complex value on the real axis
} else {
offset = 2*(+(a.im < 0)) + 1; // complex value on the imaginary axis
}
}
var arg = a.arg();
var abs = a.abs();
var roots = [];
var r = Math.pow(abs, 1/root);
for(var k = 0; k < root; k++) {
var halfPiFactor = (offset + 4*k)/root;
/**
* If (offset + 4*k)/root is an integral multiple of pi/2
* then we can produce a more exact result.
*/
if (halfPiFactor === Math.round(halfPiFactor)) {
roots.push(_calculateExactResult[halfPiFactor % 4](r));
continue;
}
roots.push(complex({r: r, phi: (arg + 2 * Math.PI * k)/root}));
}
return roots;
}
exports.name = 'nthRoots';
exports.factory = factory;

View File

@ -247,8 +247,12 @@ function factory (type, config, load, typed, math) {
var unit = new Unit();
unit.units = [];
var powerMultiplierCurrent = 1;
var expectingUnit = false;
// A unit should follow this pattern:
// [number]unit[^number] [unit[^number]]...[/unit[^number] [unit[^number]]]
// [number] ...[ [*/] unit[^number] ]
// unit[^number] ... [ [*/] unit[^number] ]
// Rules:
// number is any floating point number.
@ -262,6 +266,7 @@ function factory (type, config, load, typed, math) {
next();
skipWhitespace();
// Optional number at the start of the string
var valueStr = parseNumber();
var value = null;
@ -275,12 +280,19 @@ function factory (type, config, load, typed, math) {
else { // number
value = parseFloat(valueStr);
}
}
skipWhitespace(); // Whitespace is not required here
// Next, we read any number of unit[^number]
var powerMultiplierCurrent = 1;
var expectingUnit = false;
skipWhitespace(); // Whitespace is not required here
// handle multiplication or division right after the value, like '1/s'
if (parseCharacter('*')) {
powerMultiplierCurrent = 1;
expectingUnit = true;
}
else if (parseCharacter('/')) {
powerMultiplierCurrent = -1;
expectingUnit = true;
}
}
// Stack to keep track of powerMultipliers applied to each parentheses group
var powerMultiplierStack = [];

View File

@ -1,3 +1,3 @@
module.exports = '4.0.0';
module.exports = '4.0.1';
// Note: This file is automatically generated when building math.js.
// Changes made in this file will be overwritten.

8
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "mathjs",
"version": "4.0.0",
"version": "4.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -5812,9 +5812,9 @@
}
},
"typed-function": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.0.1.tgz",
"integrity": "sha512-Ie5d+HS39FU+sKj5nzcSV9pucMOtHsomaZPaxX9CWnxeqcdBkGl0cGKx1xd5v+b1czUd1iVa/RMZbsN8wnfGPg=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-function/-/typed-function-1.0.3.tgz",
"integrity": "sha512-sVC/1pm70oELDFMdYtFXMFqyawenLoaDiAXA3QvOAwKF/WvFNTSJN23cY2lFNL8iP0kh3T0PPKewrboO8XUVGQ=="
},
"typedarray-pool": {
"version": "1.1.0",

View File

@ -1,6 +1,6 @@
{
"name": "mathjs",
"version": "4.0.0",
"version": "4.0.1",
"description": "Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with different data types like numbers, big numbers, complex numbers, fractions, units, and matrices.",
"author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)",
"contributors": [
@ -98,7 +98,7 @@
"javascript-natural-sort": "0.7.1",
"seed-random": "2.2.0",
"tiny-emitter": "2.0.2",
"typed-function": "1.0.1"
"typed-function": "1.0.3"
},
"devDependencies": {
"benchmark": "2.1.4",

View File

@ -110,21 +110,8 @@ describe('nthRoot', function() {
assert.deepEqual(nthRoot(big(Infinity), big(-3)), big(0));
});
it('should return an array of Complex Roots in Polar form', function() {
var roots = nthRoot(complex("-1"), 6);
var roots1 = [
{r: 1, phi: Math.PI/6},
{r: 1, phi: Math.PI/2},
{r: 1, phi: (5 * Math.PI)/6},
{r: 1, phi: (7 * Math.PI)/6},
{r: 1, phi: (9 * Math.PI)/6},
{r: 1, phi: (11 * Math.PI)/6}
];
roots.forEach(function (value, index, array) {
assert.equal(value.r, roots1[index].r);
assert.equal(value.phi, roots1[index].phi);
});
it('should throw an error when used with a complex number', function() {
assert.throws(function () {nthRoot(complex("-8"), 3);});
});
it('should throw an error when used with a complex number and root is less than 0', function() {

View File

@ -0,0 +1,74 @@
// test nthRoots
var assert = require('assert');
var math = require('../../../index');
var complex = math.complex;
var nthRoots = math.nthRoots;
describe('nthRoots', function() {
it('should return an array of Complex roots', function() {
var roots = nthRoots(complex("-1"), 6);
var roots1 = [
complex({r: 1, phi: Math.PI/6}),
complex(0, 1),
complex({r: 1, phi: (5 * Math.PI)/6}),
complex({r: 1, phi: (7 * Math.PI)/6}),
complex(0, -1),
complex({r: 1, phi: (11 * Math.PI)/6})
];
roots.forEach(function (value, index, array) {
assert.deepEqual(value, roots1[index]);
});
});
it('should return the correct answer for Complex values', function() {
var roots = nthRoots(complex(3, 4), 2);
var roots1 = [
{ re: 2, im: 1 },
{ re: -2.0000000000000004, im: -0.9999999999999999}
];
roots.forEach(function (value, index, array) {
assert.deepEqual(value, roots1[index]);
});
});
var twos = [
complex(2, 0),
complex(0, 2),
complex(-2, 0),
complex(0, -2),
];
it('should return pure roots without artifacts', function() {
var roots = nthRoots(complex("16"), 4);
roots.forEach(function (value, index, array) {
assert.deepEqual(value, twos[index]);
});
});
it('should return roots for numeric arguments', function() {
var roots = nthRoots(16, 4);
roots.forEach(function (value, index, array) {
assert.deepEqual(value, twos[index]);
});
});
it('should return roots for string arguments', function() {
var roots = nthRoots("16", 4);
roots.forEach(function (value, index, array) {
assert.deepEqual(value, twos[index]);
});
});
it('should return zero exactly once', function() {
var roots2 = nthRoots(0);
var roots4 = nthRoots(0, 4);
var roots8 = nthRoots(0, 8);
assert.deepEqual(roots2, [complex(0)]);
assert.deepEqual(roots4, [complex(0)]);
assert.deepEqual(roots8, [complex(0)]);
});
});

View File

@ -28,12 +28,14 @@ describe('pow', function() {
it('should exponentiate a negative number to a non-integer power', function() {
approx.deepEqual(pow(-2,1.5), complex(0, -2.82842712474619));
approx.deepEqual(pow(-8, 1/3), complex(1, 1.732050807568877));
});
it('should exponentiate a negative number to a non-integer power with predictable:true', function() {
var res = mathPredictable.pow(-2,1.5);
assert.equal(typeof res, 'number');
assert(isNaN(res));
assert.strictEqual(mathPredictable.pow(-8, 1/3), -2);
});
it('should return a real-valued root if one exists with predictable:true', function() {
@ -130,7 +132,7 @@ describe('pow', function() {
assert.throws(function () {pow(null, 2);}, /TypeError: Unexpected type of argument/);
});
it('should handle infitie exponents', function() {
it('should handle infinite exponents', function() {
var Ptbl = mathPredictable;
// TODO replace isNaN with complexInfinity when complex.js updates

View File

@ -861,6 +861,21 @@ describe('Unit', function() {
unit1 = Unit.parse('5exabytes');
approx.equal(unit1.value, 4e19);
assert.equal(unit1.units[0].unit.name, 'bytes');
unit1 = Unit.parse('1 / s');
approx.equal(unit1.value, 1);
assert.equal(unit1.units[0].unit.name, 's');
assert.equal(unit1.units[0].power, -1);
unit1 = Unit.parse('1/s');
approx.equal(unit1.value, 1);
assert.equal(unit1.units[0].unit.name, 's');
assert.equal(unit1.units[0].power, -1);
unit1 = Unit.parse('1 * s');
approx.equal(unit1.value, 1);
assert.equal(unit1.units[0].unit.name, 's');
assert.equal(unit1.units[0].power, 1);
});
it('should parse expressions with nested parentheses correctly', function() {
@ -921,6 +936,7 @@ describe('Unit', function() {
assert.throws(function () {Unit.parse('meter.')}, /Unexpected "\."/);
assert.throws(function () {Unit.parse('meter/')}, /Trailing characters/);
assert.throws(function () {Unit.parse('/meter')}, /Unexpected "\/"/);
assert.throws(function () {Unit.parse('1 */ s')}, /Unexpected "\/"/);
assert.throws(function () {Unit.parse('45 kg 34 m')}, /Unexpected "3"/);
});