mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-25 15:07:57 +00:00
Merge pull request #425 from ericman314/unit-parse-parentheses
Added support for parentheses to Unit.parse
This commit is contained in:
commit
af0b0960c0
@ -23,10 +23,11 @@ math.unit(unit: Unit) : Unit
|
||||
Example usage:
|
||||
|
||||
```js
|
||||
var a = math.unit(45, 'cm'); // Unit 450 mm
|
||||
var b = math.unit('0.1 kilogram'); // Unit 100 gram
|
||||
var c = math.unit('2 inch'); // Unit 2 inch
|
||||
var d = math.unit('90 km/h'); // Unit 90 km/h
|
||||
var a = math.unit(45, 'cm'); // Unit 450 mm
|
||||
var b = math.unit('0.1 kilogram'); // Unit 100 gram
|
||||
var c = math.unit('2 inch'); // Unit 2 inch
|
||||
var d = math.unit('90 km/h'); // Unit 90 km/h
|
||||
var e = math.unit('101325 kg/(m s^2)'); // Unit 101325 kg / (m s^2)
|
||||
```
|
||||
|
||||
A `Unit` contains the following functions:
|
||||
@ -70,6 +71,19 @@ c.equalBase(b); // false
|
||||
d.toString(); // String "5.08 cm"
|
||||
```
|
||||
|
||||
Use care when creating a unit with multiple terms in the denominator. Implicit multiplication has the same operator precedence as explicit multiplication and division, which means these three expressions are identical:
|
||||
|
||||
```js
|
||||
// These three are identical
|
||||
var correct1 = math.unit('8.314 m^3 Pa / mol / K'); // Unit 8.314 (m^3 Pa) / (mol K)
|
||||
var correct2 = math.unit('8.314 (m^3 Pa) / (mol K)'); // Unit 8.314 (m^3 Pa) / (mol K)
|
||||
var correct3 = math.unit('8.314 (m^3 * Pa) / (mol * K)'); // Unit 8.314 (m^3 Pa) / (mol K)
|
||||
```
|
||||
But this expression, which omits the second `/` between `mol` and `K`, results in the wrong value:
|
||||
```js
|
||||
// Missing the second '/' between 'mol' and 'K'
|
||||
var incorrect = math.unit('8.314 m^3 Pa / mol K'); // Unit 8.314 (m^3 Pa K) / mol
|
||||
```
|
||||
|
||||
## Calculations
|
||||
|
||||
@ -87,7 +101,7 @@ var c = math.unit(45, 'deg'); // Unit 45 deg
|
||||
math.cos(c); // Number 0.7071067811865476
|
||||
|
||||
// Kinetic energy of average sedan on highway
|
||||
var d = math.unit('80 mi/h') // Unit 80 mi/h
|
||||
var d = math.unit('80 mi/h') // Unit 80 mi/h
|
||||
var e = math.unit('2 tonne') // Unit 2 tonne
|
||||
var f = math.multiply(0.5, math.multipy(math.pow(d, 2), e));
|
||||
// 1.2790064742399996 MJ
|
||||
|
||||
@ -208,10 +208,10 @@ function factory (type, config, load, typed) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string into a unit. Returns null if the provided string does not
|
||||
* contain a valid unit.
|
||||
* Parse a string into a unit. Throws an exception if the provided string does not
|
||||
* contain a valid unit or cannot be parsed.
|
||||
* @param {string} str A string like "5.2 inch", "4e2 cm/s^2"
|
||||
* @return {Unit | null} unit
|
||||
* @return {Unit} unit
|
||||
*/
|
||||
Unit.parse = function (str) {
|
||||
text = str;
|
||||
@ -249,28 +249,49 @@ function factory (type, config, load, typed) {
|
||||
skipWhitespace(); // Whitespace is not required here
|
||||
|
||||
// Next, we read any number of unit[^number]
|
||||
var powerMultiplier = 1;
|
||||
var powerMultiplierCurrent = 1;
|
||||
var expectingUnit = false;
|
||||
|
||||
// Stack to keep track of powerMultipliers applied to each parentheses group
|
||||
var powerMultiplierStack = [];
|
||||
|
||||
// Running product of all elements in powerMultiplierStack
|
||||
var powerMultiplierStackProduct = 1;
|
||||
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
|
||||
// Check for and consume opening parentheses, pushing powerMultiplierCurrent to the stack
|
||||
// A '(' will always appear directly before a unit.
|
||||
while (c === '(') {
|
||||
powerMultiplierStack.push(powerMultiplierCurrent);
|
||||
powerMultiplierStackProduct *= powerMultiplierCurrent;
|
||||
powerMultiplierCurrent = 1;
|
||||
next();
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
// Is there something here?
|
||||
if(c) {
|
||||
var oldC = c;
|
||||
var uStr = parseUnit();
|
||||
if(uStr == null) {
|
||||
// No more units.
|
||||
throw new SyntaxError('Could not parse');
|
||||
throw new SyntaxError('Unexpected "' + oldC + '" in "' + text + '" at index ' + index.toString());
|
||||
}
|
||||
}
|
||||
else {
|
||||
// End of input.
|
||||
break;
|
||||
}
|
||||
|
||||
// Verify the unit exists and get the prefix (if any)
|
||||
var res = _findUnit(uStr);
|
||||
if(res == null) {
|
||||
// Unit not found.
|
||||
//return null;
|
||||
throw new SyntaxError('Unit "' + uStr + '" not found.');
|
||||
}
|
||||
var power = powerMultiplier;
|
||||
|
||||
var power = powerMultiplierCurrent * powerMultiplierStackProduct;
|
||||
// Is there a "^ number"?
|
||||
skipWhitespace();
|
||||
if (parseCharacter('^')) {
|
||||
@ -280,9 +301,10 @@ function factory (type, config, load, typed) {
|
||||
// No valid number found for the power!
|
||||
throw new SyntaxError('In "' + str + '", "^" must be followed by a floating-point number');
|
||||
}
|
||||
power = p * powerMultiplier;
|
||||
power *= p;
|
||||
}
|
||||
// Add the unit
|
||||
|
||||
// Add the unit to the list
|
||||
unit.units.push( {
|
||||
unit: res.unit,
|
||||
prefix: res.prefix,
|
||||
@ -291,23 +313,36 @@ function factory (type, config, load, typed) {
|
||||
for(var i=0; i<BASE_DIMENSIONS.length; i++) {
|
||||
unit.dimensions[i] += res.unit.dimensions[i] * power;
|
||||
}
|
||||
// Is there a forward slash? If so, all remaining units are in the denominator.
|
||||
expectingUnit = false;
|
||||
|
||||
// Check for and consume closing parentheses, popping from the stack.
|
||||
// A ')' will always follow a unit.
|
||||
skipWhitespace();
|
||||
while (c === ')') {
|
||||
if(powerMultiplierStack.length === 0) {
|
||||
throw new SyntaxError('Unmatched ")" in "' + text + '" at index ' + index.toString());
|
||||
}
|
||||
powerMultiplierStackProduct /= powerMultiplierStack.pop();
|
||||
next();
|
||||
skipWhitespace();
|
||||
}
|
||||
|
||||
// "*" and "/" should mean we are expecting something to come next.
|
||||
// Is there a forward slash? If so, negate powerMultiplierCurrent. The next unit or paren group is in the denominator.
|
||||
expectingUnit = false;
|
||||
|
||||
if (parseCharacter('*')) {
|
||||
// explicit multiplication
|
||||
powerMultiplier = 1;
|
||||
powerMultiplierCurrent = 1;
|
||||
expectingUnit = true;
|
||||
}
|
||||
else if (parseCharacter('/')) {
|
||||
// division
|
||||
powerMultiplier = -1;
|
||||
powerMultiplierCurrent = -1;
|
||||
expectingUnit = true;
|
||||
}
|
||||
else {
|
||||
// implicit multiplication
|
||||
powerMultiplier = 1;
|
||||
powerMultiplierCurrent = 1;
|
||||
}
|
||||
|
||||
// Replace the unit into the auto unit system
|
||||
@ -329,6 +364,12 @@ function factory (type, config, load, typed) {
|
||||
throw new SyntaxError('Trailing characters: "' + str + '"');
|
||||
}
|
||||
|
||||
// Is the parentheses stack empty?
|
||||
if(powerMultiplierStack.length !== 0) {
|
||||
throw new SyntaxError('Unmatched "(" in "' + text + '"');
|
||||
}
|
||||
|
||||
// Are there any units at all?
|
||||
if(unit.units.length == 0) {
|
||||
throw new SyntaxError('"' + str + '" contains no units');
|
||||
}
|
||||
|
||||
@ -364,7 +364,7 @@ describe('unit', function() {
|
||||
assert.equal(new Unit(500 ,'m').toString(), '500 m');
|
||||
assert.equal(new Unit(600 ,'m').toString(), '0.6 km');
|
||||
assert.equal(new Unit(1000 ,'m').toString(), '1 km');
|
||||
assert.equal(new Unit(1000 ,'ohm').toString(), '1 kohm');
|
||||
assert.equal(new Unit(1000 ,'ohm').toString(), '1 kohm');
|
||||
});
|
||||
|
||||
});
|
||||
@ -463,6 +463,12 @@ describe('unit', function() {
|
||||
assert.deepEqual(u5, u6);
|
||||
});
|
||||
|
||||
it('toJSON -> fromJSON should recover an "equal" unit', function() {
|
||||
var unit1 = Unit.parse('1.23(m/(s/(kg mol)/(lbm/h)K))');
|
||||
var unit2 = Unit.fromJSON(unit1.toJSON());
|
||||
assert.equal(unit1.equals(unit2), true);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('format', function () {
|
||||
@ -594,8 +600,43 @@ describe('unit', function() {
|
||||
assert.equal(unit1.units[0].unit.name, 'bytes');
|
||||
});
|
||||
|
||||
it('should parse expressions with nested parentheses correctly', function() {
|
||||
unit1 = Unit.parse('8.314 kg (m^2 / (s^2 / (K^-1 / mol)))');
|
||||
approx.equal(unit1.value, 8.314);
|
||||
assert.equal(unit1.units[0].unit.name, 'g');
|
||||
assert.equal(unit1.units[1].unit.name, 'm');
|
||||
assert.equal(unit1.units[2].unit.name, 's');
|
||||
assert.equal(unit1.units[3].unit.name, 'K');
|
||||
assert.equal(unit1.units[4].unit.name, 'mol');
|
||||
assert.equal(unit1.units[0].power, 1);
|
||||
assert.equal(unit1.units[1].power, 2);
|
||||
assert.equal(unit1.units[2].power, -2);
|
||||
assert.equal(unit1.units[3].power, -1);
|
||||
assert.equal(unit1.units[4].power, -1);
|
||||
assert.equal(unit1.units[0].prefix.name, 'k');
|
||||
|
||||
unit1 = Unit.parse('1 (m / ( s / ( kg mol ) / ( lbm / h ) K ) )');
|
||||
assert.equal(unit1.units[0].unit.name, 'm');
|
||||
assert.equal(unit1.units[1].unit.name, 's');
|
||||
assert.equal(unit1.units[2].unit.name, 'g');
|
||||
assert.equal(unit1.units[3].unit.name, 'mol');
|
||||
assert.equal(unit1.units[4].unit.name, 'lbm');
|
||||
assert.equal(unit1.units[5].unit.name, 'h');
|
||||
assert.equal(unit1.units[6].unit.name, 'K');
|
||||
assert.equal(unit1.units[0].power, 1);
|
||||
assert.equal(unit1.units[1].power, -1);
|
||||
assert.equal(unit1.units[2].power, 1);
|
||||
assert.equal(unit1.units[3].power, 1);
|
||||
assert.equal(unit1.units[4].power, 1);
|
||||
assert.equal(unit1.units[5].power, -1);
|
||||
assert.equal(unit1.units[6].power, -1);
|
||||
|
||||
unit2 = Unit.parse('1(m/(s/(kg mol)/(lbm/h)K))');
|
||||
assert.deepEqual(unit1, unit2);
|
||||
});
|
||||
|
||||
it('should parse units with correct precedence', function() {
|
||||
var unit1 = Unit.parse('1 m^3 / kg s^2'); // implicit multiplication
|
||||
var unit1 = Unit.parse('1 m^3 / kg s^2'); // implicit multiplication
|
||||
|
||||
approx.equal(unit1.value, 1);
|
||||
assert.equal(unit1.units[0].unit.name, 'm');
|
||||
@ -607,32 +648,21 @@ describe('unit', function() {
|
||||
assert.equal(unit1.units[0].prefix.name, '');
|
||||
});
|
||||
|
||||
it('should return null (update: throw exception --ericman314) when parsing an invalid unit', function() {
|
||||
// I'm worried something else will break if Unit.parse throws an exception instead of returning null???? --ericman314
|
||||
assert.throws(function () {Unit.parse('.meter')}, /Could not parse/);
|
||||
it('should throw an exception when parsing an invalid unit', function() {
|
||||
assert.throws(function () {Unit.parse('.meter')}, /Unexpected "\."/);
|
||||
assert.throws(function () {Unit.parse('5e')}, /Unit "e" not found/);
|
||||
assert.throws(function () {Unit.parse('5e.')}, /Unit "e" not found/);
|
||||
assert.throws(function () {Unit.parse('5e1.3')}, /Could not parse/);
|
||||
assert.throws(function () {Unit.parse('5e1.3')}, /Unexpected "\."/);
|
||||
assert.throws(function () {Unit.parse('5')}, /contains no units/);
|
||||
assert.throws(function () {Unit.parse('')}, /contains no units/);
|
||||
assert.throws(function () {Unit.parse('meter.')}, /Could not parse/);
|
||||
assert.throws(function () {Unit.parse('meter.')}, /Unexpected "\."/);
|
||||
assert.throws(function () {Unit.parse('meter/')}, /Trailing characters/);
|
||||
assert.throws(function () {Unit.parse('/meter')}, /Could not parse/);
|
||||
assert.throws(function () {Unit.parse('45 kg 34 m')}, /Could not parse/);
|
||||
// assert.equal(Unit.parse('.meter'), null);
|
||||
// assert.equal(Unit.parse('5e'), null);
|
||||
// assert.equal(Unit.parse('5e. meter'), null);
|
||||
// assert.equal(Unit.parse('5e1.3 meter'), null);
|
||||
// assert.equal(Unit.parse('5'), null);
|
||||
// assert.equal(Unit.parse(''), null);
|
||||
// assert.equal(Unit.parse('meter.'), null);
|
||||
// assert.equal(Unit.parse('meter/'), null);
|
||||
// assert.equal(Unit.parse('/meter'), null);
|
||||
assert.throws(function () {Unit.parse('/meter')}, /Unexpected "\/"/);
|
||||
assert.throws(function () {Unit.parse('45 kg 34 m')}, /Unexpected "3"/);
|
||||
});
|
||||
|
||||
it('should return null when parsing an invalid type of argument', function() {
|
||||
it('should throw an exception when parsing an invalid type of argument', function() {
|
||||
assert.throws(function () {Unit.parse(123)}, /Invalid argument in Unit.parse. Valid types are/);
|
||||
// assert.equal(Unit.parse(123), null);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user