From 37870fcc0dd2257990d14bd9e84ea24eac9d1ba6 Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 9 Aug 2015 20:42:05 +0000 Subject: [PATCH 1/3] Added support for parentheses to Unit.parse --- docs/datatypes/units.md | 24 ++++++++++--- lib/type/unit/Unit.js | 71 +++++++++++++++++++++++++++++-------- test/type/unit/Unit.test.js | 64 ++++++++++++++++++++++----------- 3 files changed, 119 insertions(+), 40 deletions(-) diff --git a/docs/datatypes/units.md b/docs/datatypes/units.md index 32337679d..1a0325be6 100644 --- a/docs/datatypes/units.md +++ b/docs/datatypes/units.md @@ -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 diff --git a/lib/type/unit/Unit.js b/lib/type/unit/Unit.js index da7c272fa..6b6ad73a9 100644 --- a/lib/type/unit/Unit.js +++ b/lib/type/unit/Unit.js @@ -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 Date: Sun, 9 Aug 2015 20:58:26 +0000 Subject: [PATCH 2/3] Added unit test for toJSON -> fromJSON --- test/type/unit/Unit.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/type/unit/Unit.test.js b/test/type/unit/Unit.test.js index 4870029be..6cdb7d749 100644 --- a/test/type/unit/Unit.test.js +++ b/test/type/unit/Unit.test.js @@ -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 () { From 0964c3b627a1f92e29c31502bd027e189257fb6c Mon Sep 17 00:00:00 2001 From: Eric Date: Sun, 9 Aug 2015 21:01:40 +0000 Subject: [PATCH 3/3] Fixed tabs --- test/type/unit/Unit.test.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/type/unit/Unit.test.js b/test/type/unit/Unit.test.js index 6cdb7d749..bfd9d1b88 100644 --- a/test/type/unit/Unit.test.js +++ b/test/type/unit/Unit.test.js @@ -463,11 +463,11 @@ 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); - }); + 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); + }); });