Repository: josdejong/mathjs
Pull request #515 (2015-11-28 20:18:56)

Added support for complex units
Added support for complex-valued units.
Added unit tests.

Issues:
Docs not updated yet.
Unit constructor not able to parse a complex-valued unit, such as `"(2 + 3i) kg"`.
Units `VA` and `VAR` have not been added yet.



Patch:
diff --git a/lib/function/arithmetic/abs.js b/lib/function/arithmetic/abs.js
index f609dff79..9f191d4d8 100644
--- a/lib/function/arithmetic/abs.js
+++ b/lib/function/arithmetic/abs.js
@@ -63,11 +63,7 @@ function factory (type, config, load, typed) {
     },
 
     '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 = abs(ret.value);
-      return ret;
+      return x.abs();
     }
   });
 
diff --git a/lib/function/arithmetic/cbrt.js b/lib/function/arithmetic/cbrt.js
index 6fca77d9e..07cf61371 100644
--- a/lib/function/arithmetic/cbrt.js
+++ b/lib/function/arithmetic/cbrt.js
@@ -172,30 +172,39 @@ function factory (type, config, load, typed) {
    * @private
    */
   function _cbrtUnit(x) {
-    var negate = isNegative(x.value);
-    if (negate) {
-      x.value = unaryMinus(x.value);
-    }
-
-    // TODO: create a helper function for this
-    var third;
-    if (x.value && x.value.isBigNumber) {
-      third = new type.BigNumber(1).div(3);
-    }
-    else if (x.value && x.value.isFraction) {
-      third = new type.Fraction(1, 3);
+    if(x.value && x.value.isComplex) {
+      var result = x.clone();
+      result.value = 1.0;
+      result = result.pow(1.0/3);           // Compute the units
+      result.value = _cbrtComplex(x.value); // Compute the value
+      return result;
     }
     else {
-      third = 1/3;
+      var negate = isNegative(x.value);
+      if (negate) {
+        x.value = unaryMinus(x.value);
+      }
+
+      // TODO: create a helper function for this
+      var third;
+      if (x.value && x.value.isBigNumber) {
+        third = new type.BigNumber(1).div(3);
+      }
+      else if (x.value && x.value.isFraction) {
+        third = new type.Fraction(1, 3);
+      }
+      else {
+        third = 1/3;
+      }
+
+      var result = x.pow(third);
+
+      if (negate) {
+        result.value = unaryMinus(result.value);
+      }
+
+      return result;
     }
-
-    var result = x.pow(third);
-
-    if (negate) {
-      result.value = unaryMinus(result.value);
-    }
-
-    return result;
   }
 
   cbrt.toTex = '\\sqrt[3]{${args[0]}}';
diff --git a/lib/function/arithmetic/multiplyScalar.js b/lib/function/arithmetic/multiplyScalar.js
index 7351303c3..caacfa70e 100644
--- a/lib/function/arithmetic/multiplyScalar.js
+++ b/lib/function/arithmetic/multiplyScalar.js
@@ -36,13 +36,13 @@ function factory(type, config, load, typed) {
       return x.mul(y);
     },
 
-    'number | Fraction | BigNumber, Unit': function (x, y) {
+    'number | Fraction | BigNumber | Complex, Unit': function (x, y) {
       var res = y.clone();
       res.value = (res.value === null) ? res._normalize(x) : multiplyScalar(res.value, x);
       return res;
     },
 
-    'Unit, number | Fraction | BigNumber': function (x, y) {
+    'Unit, number | Fraction | BigNumber | Complex': function (x, y) {
       var res = x.clone();
       res.value = (res.value === null) ? res._normalize(y) : multiplyScalar(res.value, y);
       return res;
diff --git a/lib/type/complex/Complex.js b/lib/type/complex/Complex.js
index 6c0778224..3f8a7002d 100644
--- a/lib/type/complex/Complex.js
+++ b/lib/type/complex/Complex.js
@@ -5,7 +5,8 @@ var format = require('../../utils/number').format;
 
 function factory (type, config, load, typed) {
   // TODO: remove dependency on Unit, not good for modularization
-  var Unit = load(require('./../unit/Unit'));
+  // Update: Unit.hasBase now accepts a String, so no need to use Unit.BASE_UNITS
+  // var Unit = load(require('./../unit/Unit'));
 
   /**
    * @constructor Complex
@@ -317,7 +318,7 @@ function factory (type, config, load, typed) {
         var r = arguments[0],
             phi = arguments[1];
         if (isNumber(r)) {
-          if (phi && phi.isUnit && phi.hasBase(Unit.BASE_UNITS.ANGLE)) {
+          if (phi && phi.isUnit && phi.hasBase('ANGLE')) {
             // convert unit to a number in radians
             phi = phi.toNumber('rad');
           }
diff --git a/lib/type/unit/Unit.js b/lib/type/unit/Unit.js
index b0bd84fd6..d11f1922e 100644
--- a/lib/type/unit/Unit.js
+++ b/lib/type/unit/Unit.js
@@ -9,11 +9,13 @@ function factory (type, config, load, typed) {
   var multiply  = load(require('../../function/arithmetic/multiplyScalar'));
   var divide    = load(require('../../function/arithmetic/divideScalar'));
   var pow       = load(require('../../function/arithmetic/pow'));
+  var abs       = load(require('../../function/arithmetic/abs'));
   var equal     = load(require('../../function/relational/equal'));
   var isNumeric = load(require('../../function/utils/isNumeric'));
   var format    = load(require('../../function/utils/format'));
   var getTypeOf = load(require('../../function/utils/typeof'));
   var toNumber  = load(require('../../type/number'));
+  var Complex   = load(require('../../type/complex/Complex'));
 
   /**
    * @constructor Unit
@@ -29,7 +31,7 @@ function factory (type, config, load, typed) {
    *     var c = math.in(a, new Unit(null, 'm');  // 0.05 m
    *     var d = new Unit(9.81, "m/s^2");         // 9.81 m/s^2
    *
-   * @param {number | BigNumber | Fraction | boolean} [value]  A value like 5.2
+   * @param {number | BigNumber | Fraction | Complex | boolean} [value]  A value like 5.2
    * @param {string} [name]   A unit name like "cm" or "inch", or a derived unit of the form: "u1[^ex1] [u2[^ex2] ...] [/ u3[^ex3] [u4[^ex4]]]", such as "kg m^2/s^2", where each unit appearing after the forward slash is taken to be in the denominator. "kg m^2 s^-2" is a synonym and is also acceptable. Any of the units can include a prefix.
    */
   function Unit(value, name) {
@@ -37,8 +39,8 @@ function factory (type, config, load, typed) {
       throw new Error('Constructor must be called with the new operator');
     }
 
-    if (value != undefined && !isNumeric(value)) {
-      throw new TypeError('First parameter in Unit constructor must numeric');
+    if (!(value === undefined || isNumeric(value) || value.isComplex)) {
+      throw new TypeError('First parameter in Unit constructor must be number, BigNumber, Fraction, Complex, or undefined');
     }
     if (name != undefined && (typeof name !== 'string' || name == '')) {
       throw new TypeError('Second parameter in Unit constructor must be a string');
@@ -564,10 +566,18 @@ function factory (type, config, load, typed) {
   /**
    * check if this unit has given base unit
    * If this unit is a derived unit, this will ALWAYS return false, since by definition base units are not derived.
-   * @param {BASE_UNITS | undefined} base
+   * @param {BASE_UNITS | STRING | undefined} base
    */
   Unit.prototype.hasBase = function (base) {
 
+    if(typeof(base) === "string") {
+      base = BASE_UNITS[base];
+    }
+
+    if(!base)
+      return false;
+
+
     // All dimensions must be the same
     for(var i=0; i<BASE_DIMENSIONS.length; i++) {
       if (Math.abs(this.dimensions[i] - base.dimensions[i]) > 1e-12) {
@@ -692,9 +702,10 @@ function factory (type, config, load, typed) {
       res.value = pow(res.value, p);
 
       // only allow numeric output, we don't want to return a Complex number
-      if (!isNumeric(res.value)) {
-        res.value = NaN;
-      }
+      //if (!isNumeric(res.value)) {
+      //  res.value = NaN;
+      //}
+      // Update: Complex supported now
     }
     else {
       res.value = null;
@@ -705,6 +716,25 @@ function factory (type, config, load, typed) {
     return res;
   };
 
+  /**
+   * Calculate the absolute value of a unit
+   * @param {number | Fraction | BigNumber} x
+   * @returns {Unit}      The result: |x|, absolute value of x
+   */
+  Unit.prototype.abs = function () {
+    // This gives correct, but unexpected, results for units with an offset.
+    // For example, abs(-283.15 degC) = -263.15 degC !!!
+    var ret = this.clone();
+    ret.value = abs(ret.value);
+
+    for(var i in ret.units) {
+      if(ret.units[i].unit.name === 'VA' || ret.units[i].unit.name === 'VAR') {
+        ret.units[i].unit = UNITS["W"];
+      }
+    }
+
+    return ret;
+  };
 
   /**
    * Convert the unit to a specific unit name.
@@ -958,6 +988,27 @@ function factory (type, config, load, typed) {
     // Simplfy the unit list, if necessary
     this.simplifyUnitListLazy();
 
+    // Apply some custom logic for handling VA and VAR. The goal is to express the value of the unit as a real value, if possible. Otherwise, use a real-valued unit instead of a complex-valued one.
+    var isImaginary = false;
+    var isReal = true;
+    if(typeof(this.value) !== 'undefined' && this.value !== null && this.value.isComplex) {
+      // TODO: Make this better, for example, use relative magnitude of re and im rather than absolute
+      isImaginary = Math.abs(this.value.re) < 1e-14;
+      isReal = Math.abs(this.value.im) < 1e-14;
+    }
+    
+    for(var i in this.units) {
+      if(this.units[i].unit) {
+        if(this.units[i].unit.name === 'VA' && isImaginary) {
+          this.units[i].unit = UNITS["VAR"];
+        }
+        else if(this.units[i].unit.name === 'VAR' && !isImaginary) {
+          this.units[i].unit = UNITS["VA"];
+        }
+      }
+    }
+
+
     // Now apply the best prefix
     // Units must have only one unit and not have the fixPrefix flag set
     if (this.units.length === 1 && !this.fixPrefix) {
@@ -972,6 +1023,9 @@ function factory (type, config, load, typed) {
     var value = this._denormalize(this.value);
     var str = (this.value !== null) ? format(value, options || {}) : '';
     var unitStr = this.formatUnits();
+    if(this.value && this.value.isComplex) {
+      str = "(" + str + ")";    // Surround complex values with ( ) to enable better parsing 
+    }
     if(unitStr.length > 0 && str.length > 0) {
       str += " ";
     }
@@ -1000,14 +1054,16 @@ function factory (type, config, load, typed) {
 
     // Note: the units value can be any numeric type, but to find the best
     // prefix it's enough to work with limited precision of a regular number
-    var absValue = Math.abs(toNumber(this.value));
+    // Update: using mathjs abs since we also allow complex numbers
+    var absValue = abs(this.value);
+    var absUnitValue = abs(this.units[0].unit.value);
     var bestPrefix = this.units[0].prefix;
     if (absValue === 0) {
       return bestPrefix;
     }
     var power = this.units[0].power;
     var bestDiff = Math.abs(
-        Math.log(absValue / Math.pow(bestPrefix.value * this.units[0].unit.value, power)) / Math.LN10 - 1.2);
+        Math.log(absValue / Math.pow(bestPrefix.value * absUnitValue, power)) / Math.LN10 - 1.2);
 
     var prefixes = this.units[0].unit.prefixes;
     for (var p in prefixes) {
@@ -1016,7 +1072,7 @@ function factory (type, config, load, typed) {
         if (prefix.scientific) {
 
           var diff = Math.abs(
-              Math.log(absValue / Math.pow(prefix.value * this.units[0].unit.value, power)) / Math.LN10 - 1.2);
+              Math.log(absValue / Math.pow(prefix.value * absUnitValue, power)) / Math.LN10 - 1.2);
 
           if (diff < bestDiff
               || (diff === bestDiff && prefix.name.length < bestPrefix.name.length)) {
@@ -2176,6 +2232,23 @@ function factory (type, config, load, typed) {
       offset: 0
     },
 
+    // Electrical power units
+    VAR: {
+      name: 'VAR',
+      base: BASE_UNITS.POWER,
+      prefixes: PREFIXES.SHORT,
+      value: new Complex(0,1),
+      offset: 0
+    },
+    
+    VA: {
+      name: 'VA',
+      base: BASE_UNITS.POWER,
+      prefixes: PREFIXES.SHORT,
+      value: 1,
+      offset: 0
+    },
+
     // Pressure
     Pa: {
       name: 'Pa',
@@ -2549,6 +2622,10 @@ function factory (type, config, load, typed) {
       return new type.Fraction(x);
     },
 
+    Complex: function (x) {
+      return x;
+    },
+
     number: function (x) {
       return x;
     }
diff --git a/lib/type/unit/function/unit.js b/lib/type/unit/function/unit.js
index 8d3a8493c..9eccc4b37 100644
--- a/lib/type/unit/function/unit.js
+++ b/lib/type/unit/function/unit.js
@@ -40,7 +40,7 @@ function factory (type, config, load, typed) {
       return type.Unit.parse(x); // a unit with value, like '5cm'
     },
 
-    'number | BigNumber | Fraction, string': function (value, unit) {
+    'number | BigNumber | Fraction | Complex, string': function (value, unit) {
       return new type.Unit(value, unit);
     },
 


Test patch:
diff --git a/test/function/arithmetic/abs.test.js b/test/function/arithmetic/abs.test.js
index b1658ef1b..d439e41f8 100644
--- a/test/function/arithmetic/abs.test.js
+++ b/test/function/arithmetic/abs.test.js
@@ -81,6 +81,9 @@ describe('abs', function () {
 
     u = math.abs(math.unit(math.fraction(2,3), 'm'));
     assert.equal(u.toString(), '2/3 m');
+
+    u = math.abs(math.unit(math.complex(-4, 3), 'in'));
+    assert.equal(u.toString(), '5 in');
   });
 
   it('should throw an error in case of invalid number of arguments', function() {
diff --git a/test/function/arithmetic/addScalar.test.js b/test/function/arithmetic/addScalar.test.js
index cb603d61a..91bbd4658 100644
--- a/test/function/arithmetic/addScalar.test.js
+++ b/test/function/arithmetic/addScalar.test.js
@@ -96,6 +96,8 @@ describe('add', function() {
     approx.deepEqual(add(math.unit(5, 'km'), math.unit(100, 'mile')), math.unit(165.93, 'km'));
 
     approx.deepEqual(add(math.unit(math.fraction(1,3), 'm'), math.unit(math.fraction(1,3), 'm')).toString(), '2/3 m');
+
+    approx.deepEqual(add(math.unit(math.complex(-3, 2), 'g'), math.unit(math.complex(5, -6), 'g')).toString(), '(2 - 4i) g');
   });
 
   it('should throw an error for two measures of different units', function() {
diff --git a/test/function/arithmetic/cbrt.test.js b/test/function/arithmetic/cbrt.test.js
index dda5d1d55..bf66287c9 100644
--- a/test/function/arithmetic/cbrt.test.js
+++ b/test/function/arithmetic/cbrt.test.js
@@ -106,6 +106,9 @@ describe('cbrt', function() {
     assert.deepEqual(cbrt(math.unit(math.bignumber(27), 'm^3')).value, math.bignumber(3));
     assert(cbrt(math.unit(math.bignumber(-27), 'm^3')).value.isBigNumber);
     assert.deepEqual(cbrt(math.unit(math.bignumber(-27), 'm^3')).value, math.bignumber(-3));
+
+    assert(cbrt(math.unit(math.complex(-46, 9), 's^3')).value.isComplex);
+    approx.deepEqual(cbrt(math.unit(math.complex(-46, 9), 's^3')).value, math.complex(2, 3));
   });
 
   it('should throw an error when used with a string', function() {
diff --git a/test/function/arithmetic/divide.test.js b/test/function/arithmetic/divide.test.js
index 023ef159c..123c641ab 100644
--- a/test/function/arithmetic/divide.test.js
+++ b/test/function/arithmetic/divide.test.js
@@ -130,6 +130,8 @@ describe('divide', function() {
     assert.equal(divide(10, math.unit('4 mg/s')).toString(), '2.5 s / mg');
 
     assert.equal(divide(10, math.unit(math.fraction(4), 'mg/s')).toString(), '5/2 s / mg');
+
+    approx.equal(math.format(divide(10, math.unit(math.complex(1,2), 'm/s')), 14), '(2 - 4i) s / m');
   });
 
   it('should divide two units', function() {
@@ -138,6 +140,10 @@ describe('divide', function() {
     var a = math.unit(math.fraction(75), 'mi/h');
     var b = math.unit(math.fraction(40), 'mi/gal');
     assert.equal(divide(a, b).to('gal/minute').toString(), '1/32 gal / minute');
+
+    var c = math.unit(math.complex(21, 1), 'kg');
+    var d = math.unit(math.complex(2, -3), 's');
+    assert.equal(divide(c, d).toString(), "(3 + 5i) kg / s");
   });
 
   it('should divide one valued unit by a valueless unit and vice-versa', function() {
diff --git a/test/function/arithmetic/multiply.test.js b/test/function/arithmetic/multiply.test.js
index 8bf04b846..4a05d9cd9 100644
--- a/test/function/arithmetic/multiply.test.js
+++ b/test/function/arithmetic/multiply.test.js
@@ -148,6 +148,9 @@ describe('multiply', function() {
       assert.equal(multiply(3, unit(math.fraction(1,4), 'm')).toString(), '3/4 m');
       assert.equal(multiply(math.fraction(1,4), unit(3, 'm')).toString(), '3/4 m');
       assert.equal(multiply(unit(3, 'm'), math.fraction(1,4)).toString(), '3/4 m');
+
+      assert.equal(multiply(unit(math.complex(9, 8), 'm'), 2).toString(), '(18 + 16i) m');
+      assert.equal(math.format(multiply(unit(math.complex(2, 3), 'g'), math.complex(4, 5)), 14), '(-7 + 22i) g');
     });
 
     it('should multiply a number and a unit without value correctly', function() {
@@ -165,6 +168,7 @@ describe('multiply', function() {
       assert.equal(multiply(unit('65 mi/h'), unit('2 h')).to('mi').toString(), '130 mi');
       assert.equal(multiply(unit('2 L'), unit('1 s^-1')).toString(), '2 L / s');
       assert.equal(multiply(unit('2 m/s'), unit('0.5 s/m')).toString(), '1');
+      assert.equal(multiply(unit(math.complex(3,-4), 'N'), unit(math.complex(7,-2), 'm')).toString(), '(13 - 34i) J');
     });
 
     it('should multiply valueless units correctly', function() {
@@ -193,12 +197,12 @@ describe('multiply', function() {
       assert.equal(multiply(unit('inch'), bignumber(2)).toString(), '2 inch');
     });
 
-
     it('should throw an error in case of unit non-numeric argument', function() {
-      // Multiplying two units is supported now --ericman314
+      // Multiplying two units is supported now
       //assert.throws(function () {multiply(math.unit('5cm'), math.unit('4cm'));}, /TypeError: Unexpected type/);
-      assert.throws(function () {multiply(math.unit('5cm'), math.complex('2+3i'));}, /TypeError: Unexpected type/);
-      assert.throws(function () {multiply(math.complex('2+3i'), math.unit('5cm'));}, /TypeError: Unexpected type/);
+      // Complex units are supported now
+      //assert.throws(function () {multiply(math.unit('5cm'), math.complex('2+3i'));}, /TypeError: Unexpected type/);
+      //assert.throws(function () {multiply(math.complex('2+3i'), math.unit('5cm'));}, /TypeError: Unexpected type/);
     });
 
 
diff --git a/test/function/arithmetic/sign.test.js b/test/function/arithmetic/sign.test.js
index 3dd05f271..3ade5dc20 100644
--- a/test/function/arithmetic/sign.test.js
+++ b/test/function/arithmetic/sign.test.js
@@ -4,6 +4,7 @@ var approx = require('../../../tools/approx');
 var math = require('../../../index');
 var bignumber = math.bignumber;
 var fraction = math.fraction;
+var complex = math.complex;
 
 describe('sign', function() {
   it('should calculate the sign of a boolean', function () {
@@ -51,6 +52,8 @@ describe('sign', function() {
     assert.deepEqual(math.sign(math.unit(bignumber(-5), 'cm')), bignumber(-1));
     assert.deepEqual(math.sign(math.unit(fraction(5), 'cm')), fraction(1));
     assert.deepEqual(math.sign(math.unit(fraction(-5), 'cm')), fraction(-1));
+
+    assert.deepEqual(math.sign(math.unit(complex(3,4), 'mi')), complex(0.6,0.8));
   });
 
   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 e37ebdae4..1fa1b4cc7 100644
--- a/test/function/arithmetic/sqrt.test.js
+++ b/test/function/arithmetic/sqrt.test.js
@@ -69,9 +69,10 @@ describe('sqrt', function() {
     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() {
+  it('should return a Unit with a Complex value 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');
+    //assert.equal(sqrt(math.unit('-25 m^2/s^2')).toString(), 'NaN m / s');
+    assert.equal(math.format(sqrt(math.unit('-25 m^2/s^2')), 14), '(5i) m / s');
   });
 
   it('should throw an error when used with a string', function() {
diff --git a/test/function/arithmetic/subtract.test.js b/test/function/arithmetic/subtract.test.js
index 965960b94..57284dcc5 100644
--- a/test/function/arithmetic/subtract.test.js
+++ b/test/function/arithmetic/subtract.test.js
@@ -94,6 +94,9 @@ describe('subtract', function() {
     approx.deepEqual(subtract(math.unit(5, 'km'), math.unit(100, 'mile')), math.unit(-155.93, 'km'));
 
     assert.deepEqual(subtract(math.unit(math.bignumber(5), 'km'), math.unit(math.bignumber(2), 'km')), math.unit(math.bignumber(3), 'km'));
+
+    assert.deepEqual(subtract(math.unit(math.complex(10,10), 'K'), math.unit(math.complex(3,4), 'K')), math.unit(math.complex(7,6), 'K'));
+    assert.deepEqual(subtract(math.unit(math.complex(10,10), 'K'), math.unit(3, 'K')), math.unit(math.complex(7,10), 'K'));
   });
 
   it('should throw an error if subtracting two quantities of different units', function() {
diff --git a/test/function/arithmetic/unaryMinus.test.js b/test/function/arithmetic/unaryMinus.test.js
index 812202698..75fbfc275 100644
--- a/test/function/arithmetic/unaryMinus.test.js
+++ b/test/function/arithmetic/unaryMinus.test.js
@@ -3,6 +3,7 @@ var assert = require('assert');
 var math = require('../../../index');
 var bignumber = math.bignumber;
 var fraction = math.fraction;
+var complex = math.complex;
 
 describe('unaryMinus', function() {
   it('should return unary minus of a boolean', function () {
@@ -52,6 +53,7 @@ describe('unaryMinus', function() {
   it('should perform unary minus of a unit', function() {
     assert.equal(math.unaryMinus(math.unit(5, 'km')).toString(), '-5 km');
     assert.equal(math.unaryMinus(math.unit(fraction(2/3), 'km')).toString(), '-2/3 km');
+    assert.equal(math.unaryMinus(math.unit(complex(2,-4), 'gal')).toString(), '(-2 + 4i) gal');
   });
 
   it('should perform element-wise unary minus on a matrix', function() {
diff --git a/test/function/trigonometry/cos.test.js b/test/function/trigonometry/cos.test.js
index 714e64285..939e01f96 100644
--- a/test/function/trigonometry/cos.test.js
+++ b/test/function/trigonometry/cos.test.js
@@ -99,6 +99,8 @@ describe('cos', function() {
 
     assert(cos(unit(math.bignumber(45), 'deg')).isBigNumber);
     approx.equal(cos(unit(math.bignumber(45), 'deg')).toNumber(), 0.707106781186548);
+
+    approx.deepEqual(cos(unit(complex(1,1), 'rad')), complex(0.833730025131149, -0.988897705762865));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/cosh.test.js b/test/function/trigonometry/cosh.test.js
index f38b87961..adccea206 100644
--- a/test/function/trigonometry/cosh.test.js
+++ b/test/function/trigonometry/cosh.test.js
@@ -64,6 +64,8 @@ describe('cosh', function() {
 
     assert(cosh(unit(math.bignumber(90), 'deg')).isBigNumber);
     approx.equal(cosh(unit(math.bignumber(90), 'deg')).toNumber(), 2.5091784786581);
+
+    approx.deepEqual(cosh(math.unit(complex('2 + i'), 'rad')), complex(2.0327230070197, 3.0518977991518));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/cot.test.js b/test/function/trigonometry/cot.test.js
index 6ae661bf6..d14516cf0 100644
--- a/test/function/trigonometry/cot.test.js
+++ b/test/function/trigonometry/cot.test.js
@@ -82,6 +82,8 @@ describe('cot', function() {
 
     assert(cot(unit(math.bignumber(45), 'deg')).isBigNumber);
     approx.equal(cot(unit(math.bignumber(45), 'deg')).toNumber(), 1);
+
+    approx.deepEqual(cot(math.unit(complex('1+i'), 'rad')), complex(0.217621561854403, -0.868014142895925));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/coth.test.js b/test/function/trigonometry/coth.test.js
index 90b782819..9ae94b408 100644
--- a/test/function/trigonometry/coth.test.js
+++ b/test/function/trigonometry/coth.test.js
@@ -52,6 +52,8 @@ describe('coth', function() {
 
     assert(coth(unit(math.bignumber(90), 'deg')).isBigNumber);
     approx.equal(coth(unit(math.bignumber(90), 'deg')).toNumber(), 1.0903314107274);
+
+    approx.deepEqual(coth(math.unit(complex('2 + i'), 'rad')), complex(0.98432922645819, -0.032797755533753));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/csc.test.js b/test/function/trigonometry/csc.test.js
index f3698a3ee..5516c32ed 100644
--- a/test/function/trigonometry/csc.test.js
+++ b/test/function/trigonometry/csc.test.js
@@ -73,6 +73,8 @@ describe('csc', function() {
 
     assert(csc(unit(math.bignumber(45), 'deg')).isBigNumber);
     approx.equal(csc(unit(math.bignumber(45), 'deg')).toNumber(), 1.41421356237310);
+
+    approx.deepEqual(csc(unit(complex('1+i'), 'rad')), complex(0.621518017170428, -0.303931001628426));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/csch.test.js b/test/function/trigonometry/csch.test.js
index 352de58c6..6915d4206 100644
--- a/test/function/trigonometry/csch.test.js
+++ b/test/function/trigonometry/csch.test.js
@@ -55,6 +55,8 @@ describe('csch', function() {
 
     assert(csch(unit(math.bignumber(90), 'deg')).isBigNumber);
     approx.equal(csch(unit(math.bignumber(90), 'deg')).toNumber(), 0.4345372080947);
+
+    approx.deepEqual(csch(unit(complex('2 + i'), 'rad')), complex(0.14136302161241, -0.22837506559969));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/sec.test.js b/test/function/trigonometry/sec.test.js
index 5258a8b7d..33c417a45 100644
--- a/test/function/trigonometry/sec.test.js
+++ b/test/function/trigonometry/sec.test.js
@@ -82,6 +82,8 @@ describe('sec', function() {
 
     assert(sec(unit(math.bignumber(45), 'deg')).isBigNumber);
     approx.equal(sec(unit(math.bignumber(45), 'deg')).toNumber(), 1.41421356237310);
+
+    approx.deepEqual(sec(unit(complex('1+i'), 'rad')), complex(0.498337030555187, 0.591083841721045));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/sech.test.js b/test/function/trigonometry/sech.test.js
index 7df3afc81..75c6a3ef2 100644
--- a/test/function/trigonometry/sech.test.js
+++ b/test/function/trigonometry/sech.test.js
@@ -53,6 +53,8 @@ describe('sech', function() {
 
     assert(sech(unit(math.bignumber(90), 'deg')).isBigNumber);
     approx.equal(sech(unit(math.bignumber(90), 'deg')).toNumber(), 0.39853681533839);
+
+    approx.deepEqual(sech(unit(complex('2 + i'), 'rad')), complex(0.15117629826558, -0.22697367539372));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/sin.test.js b/test/function/trigonometry/sin.test.js
index 4cc552c39..eccb91e00 100644
--- a/test/function/trigonometry/sin.test.js
+++ b/test/function/trigonometry/sin.test.js
@@ -91,6 +91,8 @@ describe('sin', function() {
 
     assert(sin(unit(math.bignumber(45), 'deg')).isBigNumber);
     approx.equal(sin(unit(math.bignumber(45), 'deg')).toNumber(), 0.707106781186548);
+
+    approx.deepEqual(sin(unit(complex('1+i'), 'rad')), complex(1.298457581415977, 0.634963914784736));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/sinh.test.js b/test/function/trigonometry/sinh.test.js
index c96066c88..34b2711af 100644
--- a/test/function/trigonometry/sinh.test.js
+++ b/test/function/trigonometry/sinh.test.js
@@ -67,6 +67,8 @@ describe('sinh', function() {
 
     assert(sinh(unit(math.bignumber(90), 'deg')).isBigNumber);
     approx.equal(sinh(unit(math.bignumber(90), 'deg')).toNumber(), 2.3012989023073);
+
+    approx.deepEqual(sinh(unit(complex('2 + i'), 'rad')), complex(1.9596010414216, 3.1657785132162));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/tan.test.js b/test/function/trigonometry/tan.test.js
index 8daa15b1e..37797ac27 100644
--- a/test/function/trigonometry/tan.test.js
+++ b/test/function/trigonometry/tan.test.js
@@ -76,6 +76,8 @@ describe('tan', function() {
 
     assert(tan(unit(math.bignumber(60), 'deg')).isBigNumber);
     approx.equal(tan(unit(math.bignumber(60), 'deg')).toNumber(), math.sqrt(3));
+
+    approx.deepEqual(tan(unit(complex('1+i'), 'rad')), complex(0.271752585319512, 1.083923327338695));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/function/trigonometry/tanh.test.js b/test/function/trigonometry/tanh.test.js
index 335a5029e..fbb34e6dd 100644
--- a/test/function/trigonometry/tanh.test.js
+++ b/test/function/trigonometry/tanh.test.js
@@ -63,6 +63,8 @@ describe('tanh', function() {
 
     assert(tanh(unit(math.bignumber(90), 'deg')).isBigNumber);
     approx.equal(tanh(unit(math.bignumber(90), 'deg')).toNumber(), 0.91715233566727);
+
+    approx.deepEqual(tanh(unit(complex('2 + i'), 'rad')), complex(1.0147936161466, 0.033812826079897));
   });
 
   it('should throw an error if called with an invalid unit', function() {
diff --git a/test/type/unit/Unit.test.js b/test/type/unit/Unit.test.js
index 50a5570bd..6c6b64152 100644
--- a/test/type/unit/Unit.test.js
+++ b/test/type/unit/Unit.test.js
@@ -39,6 +39,12 @@ describe('Unit', function() {
       assert.equal(unit1.units[0].unit.name, 'm');
     });
 
+    it('should create a unit with Complex value', function () {
+      var unit1 = new Unit(math.complex(500, 600), 'cm');
+      assert.deepEqual(unit1.value, math.complex(5, 6));
+      assert.equal(unit1.units[0].unit.name, 'm');
+    });
+
     it('should create square meter correctly', function() {
       var unit1 = new Unit(0.000001, 'km2');
       assert.equal(unit1.value, 1);
@@ -155,6 +161,16 @@ describe('Unit', function() {
       assert.equal(new Unit(100, 'cm').equals(new Unit(math.fraction(2), 'm')), false);
     });
 
+    it('should test whether two Complex units are equal', function() {
+      assert.equal(new Unit(math.complex(3, 4), 'km').equals(new Unit(math.complex(3000, 4000), 'm')), true);
+      assert.equal(new Unit(math.complex(3, 4), 'km').equals(new Unit(math.complex(3000, 10), 'm')), false);
+    });
+
+    it('should test whether a Complex unit and a unit with a number are equal', function() {
+      assert.equal(new Unit(math.complex(3, 0), 'km').equals(new Unit(3000, 'm')), true);
+      assert.equal(new Unit(math.complex(3, 4), 'km').equals(new Unit(3000, 'm')), false);
+    });
+
   });
 
   describe('clone', function() {
@@ -190,6 +206,14 @@ describe('Unit', function() {
       assert(u1.value !== u2.value); // should be cloned
     });
 
+    it('should clone a Complex unit', function() {
+      var u1 = new Unit(math.complex(1,3), 'cm');
+      var u2 = u1.clone();
+      assert(u1 !== u2);
+      assert.deepEqual(u1, u2);
+      assert(u1.value !== u2.value); // should be cloned
+    });
+
   });
 
   describe('toNumber', function() {
@@ -218,7 +242,7 @@ describe('Unit', function() {
     });
   });
 
-  describe('toNumberic', function() {
+  describe('toNumeric', function() {
     it ('should convert a unit to a numeric value', function () {
       var u = new Unit(math.fraction(1,3), 'cm');
       assert.deepEqual(u.toNumeric('mm'), math.fraction(10,3));
@@ -268,6 +292,13 @@ describe('Unit', function() {
       assert.equal(u2.fixPrefix, true);
     });
 
+    it ('should convert a Complex unit', function() {
+      var u1 = new Unit(math.complex(300,400), 'kPa');
+      var u2 = u1.to('lbf/in^2');
+      approx.deepEqual(u2.value, math.complex(300000, 400000));
+      assert.deepEqual(u2.toString(), "(43.511321319062766 + 58.01509509208369i) lbf / in^2");
+    });
+
     it ('should convert a unit to a fixed unit', function () {
       var u1 = new Unit(5000, 'cm');
       assert.equal(u1.value, 50);
@@ -447,6 +478,10 @@ describe('Unit', function() {
     it('should convert a unit with Fraction to string properly', function() {
       assert.equal(new Unit(math.fraction(9/10), 'mm').toString(), '9/10 mm');
     });
+
+    it('should convert a Complex unit to string properly', function() {
+      assert.equal(new Unit(math.complex(-1,-2), 'J / (mol K)').toString(), '(-1 - 2i) J / (mol K)');
+    });
   });
 
   describe('simplifyUnitListLazy', function() {
@@ -531,9 +566,19 @@ describe('Unit', function() {
             unit: 'cm',
             fixPrefix: false
           });
+      approx.deepEqual(new Unit(math.complex(2, 4), 'g').toJSON(),
+          {
+            mathjs: 'Unit',
+            value: math.complex(2, 4),
+            unit: 'g',
+            fixPrefix: false
+          });
 
       var str = JSON.stringify(new Unit(math.fraction(0.375), 'cm'));
       assert.deepEqual(str, '{"mathjs":"Unit","value":{"mathjs":"Fraction","n":3,"d":8},"unit":"cm","fixPrefix":false}');
+
+      var cmpx = JSON.stringify(new Unit(math.complex(2, 4), 'g'));
+      assert.equal(cmpx, '{"mathjs":"Unit","value":{"mathjs":"Complex","re":2,"im":4},"unit":"g","fixPrefix":false}');
     });
 
     it('fromJSON', function () {
@@ -559,6 +604,14 @@ describe('Unit', function() {
         fixPrefix: false
       });
       assert.deepEqual(u7, new Unit(math.fraction(0.375), 'cm'))
+
+      var u8 = Unit.fromJSON({
+        mathjs: 'Unit',
+        value: math.complex(2, 4),
+        unit: 'g',
+        fixPrefix: false
+      });
+      assert.deepEqual(u8, new Unit(math.complex(2,4), 'g'));
     });
 
     it('toJSON -> fromJSON should recover an "equal" unit', function() {
@@ -598,6 +651,23 @@ describe('Unit', function() {
       assert.equal(new Unit(math.fraction(4/5), 'm').format(), '4/5 m');
     });
 
+    it('should format a Complex unit', function() {
+      assert.equal(new Unit(math.complex(-2, 4.5), 'mm').format(14), '(-2 + 4.5i) mm');
+    });
+
+    it('should format units with VA and VAR correctly', function() {
+      assert.equal(math.eval('4000 VAR + 3000 VA').format(), "(3 + 4i) kVA");
+      assert.equal(math.eval('3000 VA + 4000 VAR').format(), "(3 + 4i) kVA");
+      assert.equal(math.eval('4000 VAR').format(), "(4) kVAR");
+      assert.equal(math.eval('4000i VA').format(), "(4) kVAR");
+      assert.equal(math.eval('4000i VAR').format(), "(-4) kVA");
+      assert.equal(math.eval('abs(4000 VAR + 3000 VA)').format(), "5 kW");
+      assert.equal(math.eval('abs(3000 VA + 4000 VAR)').format(), "5 kW");
+      assert.equal(math.eval('abs(4000 VAR)').format(), "4 kW");
+      assert.equal(math.eval('abs(4000i VA)').format(), "4 kW");
+      assert.equal(math.eval('abs(4000i VAR)').format(), "4 kW");
+    });
+
     it('should ignore properties in Object.prototype when finding the best prefix', function() {
       Object.prototype.foo = 'bar';