diff --git a/docs/datatypes/units.md b/docs/datatypes/units.md index c7cb11e90..c63de0a78 100644 --- a/docs/datatypes/units.md +++ b/docs/datatypes/units.md @@ -88,9 +88,8 @@ var incorrect = math.unit('8.314 m^3 Pa / mol K'); // Unit 8.314 (m^3 P ## Calculations -Basic operations `add`, `subtract`, `multiply`, `divide`, and `pow` can be performed -on units. Trigonometric functions like `sin` support units with an angle as -argument. +The operations that support units are `add`, `subtract`, `multiply`, `divide`, `pow`, `abs`, `sqrt`, `square`, `cube`, and `sign`. +Trigonometric functions like `cos` are also supported when the argument is an angle. ```js var a = math.unit(45, 'cm'); // Unit 450 mm @@ -119,6 +118,22 @@ var q = math.eval('1 C'); // 1 C var F = math.multiply(q, math.cross(v, B)); // [0 N, 0 N, -1 N] ``` +All arithmetic operators act on the value of the unit as it is represented in SI units. +This may lead to surprising behavior when working with temperature scales like `celsius` (or `degC`) and `fahrenheit` (or `degF`). +In general you should avoid calculations using `celsius` and `fahrenheit`. Rather, use `kelvin` (or `K`) and `rankine` (or `R`) instead. +This example highlights some problems when using `celsius` and `fahrenheit` in calculations: + +```js +var T_14F = math.unit('14 degF'); // Unit 14 degF (263.15 K) +var T_28F = math.multiply(T1, 2); // Unit 487.67 degF (526.3 K), not 28 degF + +var Tnegative = math.unit(-13, 'degF'); // Unit -13 degF (248.15 K) +var Tpositive = math.abs(T1); // Unit -13 degF (248.15 K), not 13 degF + +var Trate1 = math.eval('5 (degC/hour)'); // Unit 5 degC/hour +var Trate2 = math.eval('(5 degC)/hour'); // Unit 278.15 degC/hour +``` + The expression parser supports units too. This is described in the section about units on the page [Syntax](../expressions/syntax.md#units). diff --git a/examples/units.js b/examples/units.js index 72ba7fa21..db19d5ca0 100644 --- a/examples/units.js +++ b/examples/units.js @@ -61,7 +61,7 @@ console.log(); console.log('compute speed of fluid flowing out of hole in a container'); var g = math.unit('9.81 m / s^2'); var h = math.unit('1 m'); -var v = math.pow(math.multiply(2, math.multiply(g, h)), 0.5); +var v = math.pow(math.multiply(2, math.multiply(g, h)), 0.5); // Can also use math.sqrt console.log('g = ' + format(g)); console.log('h = ' + format(h)); console.log('v = (2 g h) ^ 0.5 = ' + format(v)); // 4.429... m / s diff --git a/lib/function/arithmetic/abs.js b/lib/function/arithmetic/abs.js index 8a483cc0c..1f24749f1 100644 --- a/lib/function/arithmetic/abs.js +++ b/lib/function/arithmetic/abs.js @@ -22,9 +22,9 @@ function factory (type, config, load, typed) { * * sign * - * @param {number | BigNumber | Fraction | Complex | Array | Matrix} x + * @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x * A number or matrix for which to get the absolute value - * @return {number | BigNumber | Fraction | Complex | Array | Matrix} + * @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} * Absolute value of `x` */ var abs = typed('abs', { @@ -60,6 +60,14 @@ function factory (type, config, load, typed) { 'Array | Matrix': function (x) { // deep map collection, skip zeros since abs(0) = 0 return deepMap(x, abs, true); + }, + + 'Unit': function(x) { + // This gives correct, but unexpected, results for units with an offset. + // For example, abs(-283.15 degC) = -263.15 degC !!! + var ret = x.clone(); + ret.value = Math.abs(ret.value); + return ret; } }); diff --git a/lib/function/arithmetic/cube.js b/lib/function/arithmetic/cube.js index 90f01a31e..0c638abdd 100644 --- a/lib/function/arithmetic/cube.js +++ b/lib/function/arithmetic/cube.js @@ -26,8 +26,8 @@ function factory (type, config, load, typed) { * * multiply, square, pow * - * @param {number | BigNumber | Fraction | Complex | Array | Matrix} x Number for which to calculate the cube - * @return {number | BigNumber | Fraction | Complex | Array | Matrix} Cube of x + * @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x Number for which to calculate the cube + * @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} Cube of x */ var cube = typed('cube', { 'number': function (x) { @@ -49,6 +49,10 @@ function factory (type, config, load, typed) { 'Array | Matrix': function (x) { // deep map collection, skip zeros since cube(0) = 0 return deepMap(x, cube, true); + }, + + 'Unit': function(x) { + return x.pow(3); } }); diff --git a/lib/function/arithmetic/sign.js b/lib/function/arithmetic/sign.js index d1b2b7bfc..2fd7f700d 100644 --- a/lib/function/arithmetic/sign.js +++ b/lib/function/arithmetic/sign.js @@ -29,9 +29,9 @@ function factory (type, config, load, typed) { * * abs * - * @param {number | BigNumber | Fraction | Complex | Array | Matrix} x + * @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x * The number for which to determine the sign - * @return {number | BigNumber | Fraction | Complex | Array | Matrix}e + * @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit}e * The sign of `x` */ var sign = typed('sign', { @@ -53,6 +53,10 @@ function factory (type, config, load, typed) { 'Array | Matrix': function (x) { // deep map collection, skip zeros since sign(0) = 0 return deepMap(x, sign, true); + }, + + 'Unit': function(x) { + return number.sign(x.value); } }); diff --git a/lib/function/arithmetic/sqrt.js b/lib/function/arithmetic/sqrt.js index 5105701de..a86e5a0f1 100644 --- a/lib/function/arithmetic/sqrt.js +++ b/lib/function/arithmetic/sqrt.js @@ -22,9 +22,9 @@ function factory (type, config, load, typed) { * * square, multiply * - * @param {number | BigNumber | Complex | Array | Matrix} x + * @param {number | BigNumber | Complex | Array | Matrix | Unit} x * Value for which to calculate the square root. - * @return {number | BigNumber | Complex | Array | Matrix} + * @return {number | BigNumber | Complex | Array | Matrix | Unit} * Returns the square root of `x` */ var sqrt = typed('sqrt', { @@ -45,7 +45,13 @@ function factory (type, config, load, typed) { 'Array | Matrix': function (x) { // deep map collection, skip zeros since sqrt(0) = 0 return deepMap(x, sqrt, true); + }, + + 'Unit': function (x) { + // Someday will work for complex units when they are implemented + return x.pow(0.5); } + }); /** diff --git a/lib/function/arithmetic/square.js b/lib/function/arithmetic/square.js index c6b868df5..dafaf1a5f 100644 --- a/lib/function/arithmetic/square.js +++ b/lib/function/arithmetic/square.js @@ -24,9 +24,9 @@ function factory (type, config, load, typed) { * * multiply, cube, sqrt, pow * - * @param {number | BigNumber | Fraction | Complex | Array | Matrix} x + * @param {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} x * Number for which to calculate the square - * @return {number | BigNumber | Fraction | Complex | Array | Matrix} + * @return {number | BigNumber | Fraction | Complex | Array | Matrix | Unit} * Squared value */ var square = typed('square', { @@ -52,6 +52,10 @@ function factory (type, config, load, typed) { 'Array | Matrix': function (x) { // deep map collection, skip zeros since square(0) = 0 return deepMap(x, square, true); + }, + + 'Unit': function(x) { + return x.pow(2); } }); diff --git a/test/function/arithmetic/abs.test.js b/test/function/arithmetic/abs.test.js index 67c698013..d4bfb279f 100644 --- a/test/function/arithmetic/abs.test.js +++ b/test/function/arithmetic/abs.test.js @@ -69,10 +69,13 @@ describe('abs', function () { assert.deepEqual(a1.valueOf(), [2,1,0,1,2]) }); - it('should throw an error with a measurment unit', function () { - assert.throws(function () { - math.abs(math.unit(5, 'km')); - }); + it('should return the absolute value of a unit', function () { + var u = math.abs(math.unit('5 m')); + assert.equal(u.toString(), '5 m'); + u = math.abs(math.unit('-5 m')); + assert.equal(u.toString(), '5 m'); + u = math.abs(math.unit('-283.15 degC')); + assert.equal(u.toString(), '-263.15 degC'); }); it('should throw an error in case of invalid number of arguments', function() { @@ -86,8 +89,8 @@ describe('abs', function () { }); it('should LaTeX abs', function () { - var expression = math.parse('abs(-1)'); - assert.equal(expression.toTex(),'\\left|-1\\right|'); + var expression = math.parse('abs(-1)'); + assert.equal(expression.toTex(),'\\left|-1\\right|'); }); }); diff --git a/test/function/arithmetic/cube.test.js b/test/function/arithmetic/cube.test.js index fd520af56..f471e2ecc 100644 --- a/test/function/arithmetic/cube.test.js +++ b/test/function/arithmetic/cube.test.js @@ -43,12 +43,14 @@ describe('cube', function() { assert.deepEqual(cube(math.complex('2')), math.complex('8')); }); - it('should throw an error with strings', function() { - assert.throws(function () {cube('text')}); + it('should return the cube of a unit', function() { + assert.equal(cube(math.unit('4 cm')).toString(), '64 cm^3'); + assert.equal(cube(math.unit('-2 cm')).toString(), '-8 cm^3'); + assert.equal(cube(math.unit('0 cm')).toString(), '0 cm^3'); }); - it('should throw an error with units', function() { - assert.throws(function () {cube(unit('5cm'))}); + it('should throw an error with strings', function() { + assert.throws(function () {cube('text')}); }); it('should throw an error if there\'s wrong number of args', function() { diff --git a/test/function/arithmetic/sign.test.js b/test/function/arithmetic/sign.test.js index 61d4ceaa3..c244202d3 100644 --- a/test/function/arithmetic/sign.test.js +++ b/test/function/arithmetic/sign.test.js @@ -39,8 +39,13 @@ describe('sign', function() { approx.deepEqual(math.sign(math.complex(2,-3)), math.complex(0.554700196225229, -0.832050294337844)); }); - it('should throw an error when used with a unit', function() { - assert.throws(function () { math.sign(math.unit('5cm')); }); + it('should calculate the sign of a unit', function() { + assert.equal(math.sign(math.unit('5 cm')), 1); + assert.equal(math.sign(math.unit('-5 kg')), -1); + assert.equal(math.sign(math.unit('0 mol/s')), 0); + assert.equal(math.sign(math.unit('-283.15 degC')), -1); + assert.equal(math.sign(math.unit('-273.15 degC')), 0); + assert.equal(math.sign(math.unit('-263.15 degC')), 1); }); it('should throw an error when used with a string', function() { diff --git a/test/function/arithmetic/sqrt.test.js b/test/function/arithmetic/sqrt.test.js index 2bb69f55e..e37ebdae4 100644 --- a/test/function/arithmetic/sqrt.test.js +++ b/test/function/arithmetic/sqrt.test.js @@ -64,10 +64,14 @@ describe('sqrt', function() { assert.deepEqual(sqrt(math.complex(1e10, 1e-10)), math.complex(1e5, 5e-16)); }); - it('should throw an error when used with a unit', function() { - assert.throws(function () { - sqrt(math.unit(5, 'km')); - }); + it('should return the square root of a unit', function() { + assert.equal(sqrt(math.unit('25 m^2/s^2')).toString(), '5 m / s'); + assert.equal(sqrt(math.unit('4 kg')).toString(), '2 kg^0.5'); + }); + + it('should return NaN when computing the square root of a negative unit', function() { + // Update this when support for complex units is added + assert.equal(sqrt(math.unit('-25 m^2/s^2')).toString(), 'NaN m / s'); }); it('should throw an error when used with a string', function() { diff --git a/test/function/arithmetic/square.test.js b/test/function/arithmetic/square.test.js index ce77e62c6..52b2fe488 100644 --- a/test/function/arithmetic/square.test.js +++ b/test/function/arithmetic/square.test.js @@ -48,8 +48,10 @@ describe('square', function() { assert.deepEqual(square(math.complex('2')), math.complex('4')); }); - it('should throw an error when used with a unit', function() { - assert.throws(function () {square(unit('5cm'))}); + it('should return the square of a unit', function() { + assert.equal(square(math.unit('4 cm')).toString(), '16 cm^2'); + assert.equal(square(math.unit('-2 cm')).toString(), '4 cm^2'); + assert.equal(square(math.unit('0 cm')).toString(), '0 cm^2'); }); it('should throw an error when used with a string', function() {