Repository: josdejong/mathjs
Pull request #683 (2016-07-14 02:08:41)

Create user-defined units
Create a user-defined unit. The user supplies the name of the new unit, the definition in terms of existing units, and options such as prefixes and aliases.

```js
math.createUnit('knot', '0.514444 m/s', {aliases: ['knots', 'kts', 'kt']})  // knot
math.eval('4 week to createUnit("fortnight", 14 days)')  // 2 fortnight
```

TODO:

Units are installed directly in the global `math.type.Unit` scope, so you can't use different sets of user-defined units in multiple scopes at the same time like you can with user-defined variables. Supporting this might be time consuming and not very much worth it.

Currently the only validation done on names of new units is to check for collisions with other units. We might want to add checks for collisions with built-in functions, and to check for valid characters (like not allowing `%` as a unit name).

When we are satisfied with the API I will also update the docs.




Patch:
diff --git a/docs/datatypes/units.md b/docs/datatypes/units.md
index 416ce11a5..1e020efc1 100644
--- a/docs/datatypes/units.md
+++ b/docs/datatypes/units.md
@@ -112,6 +112,67 @@ 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).
 
+## User-Defined Units
+
+You can add your own units to Math.js using the `math.createUnit` function. The following example defines a new unit `furlong`, then uses the user-defined unit in a calculation:
+
+```js
+math.createUnit('furlong', '220 yards');
+math.eval('1 mile to furlong');            // 8 furlong
+```
+
+If you cannot express the new unit in terms of any existing unit, then the second argument can be omitted. In this case, a new base unit is created:
+
+```js
+// A 'foo' cannot be expressed in terms of any other unit.
+math.createUnit('foo');
+math.eval('8 foo * 4 feet');               // 32 foo feet
+```
+
+The second argument to `createUnit` can also be a configuration object consisting of the following properties:
+
+* **definition** A `string` or `Unit` which defines the user-defined unit in terms of existing built-in or user-defined units. If omitted, a new base unit is created.
+* **prefixes** A `string` indicating which prefixes math.js should use with the new unit. Possible values are `'none'`, `'short'`, `'long'`, `'binary_short'`, or `'binary_long'`. Default is `'none'`.
+* **offset** A value applied when converting to the unit. This is very helpful for temperature scales that do not share a zero with the absolute temperature scale. For example, if we were defining fahrenheit for the first time, we would use: `math.createUnit('fahrenheit', {definition: '0.555556 kelvin', offset: 459.67})`
+* **aliases** An array of strings to alias the new unit. Example: `math.createUnit('knot', {definition: '0.514444 m/s', aliases: ['knots', 'kt', 'kts']})`
+
+An optional `options` object can also be supplied as the last argument to `createUnits`. Currently only the `override` option is supported:
+
+```js
+// Redefine the mile (would not be the first time in history)
+math.createUnit('mile', '1609.347218694', {override: true}});
+```
+Base units created without specifying a definition cannot be overridden.
+
+Multiple units can defined using a single call to `createUnit` by passing an object map as the first argument, where each key in the object is the name of a new unit and the value is either a string defining the unit, or an object with the configuration properties listed above. If the value is an empty string or an object lacking a definition property, a new base unit is created.
+
+For example:
+
+```js
+math.createUnit( {
+  foo: {
+    prefixes: 'long'
+  },
+  bar: '40 foo',
+  baz: {
+    definition: '1 bar/hour',
+    prefixes: 'long'
+  }
+},
+{
+  override: true
+});
+math.eval('50000 kilofoo/s');   // 4.5 gigabaz
+```
+
+### Return Value
+`createUnit` returns the created unit, or, when multiple units are created, the last unit created. Since `createUnit` is also compatible with the expression parser, this allows you to do things like this:
+
+```js
+math.eval('45 mile/hour to createUnit("knot", "0.514444m/s")')
+// 39.103964668651976 knot
+```
+
 ## API
 A `Unit` object contains the following functions:
 
diff --git a/lib/type/unit/Unit.js b/lib/type/unit/Unit.js
index b6f4a41c1..981e5441d 100644
--- a/lib/type/unit/Unit.js
+++ b/lib/type/unit/Unit.js
@@ -60,7 +60,10 @@ function factory (type, config, load, typed, math) {
           power: 0
         }
       ];
-      this.dimensions = [0, 0, 0, 0, 0, 0, 0, 0, 0];
+      this.dimensions = []; 
+      for(var i=0; i<BASE_DIMENSIONS.length; i++) {
+        this.dimensions[i] = 0;
+      }
     }
 
     this.value = (value != undefined) ? this._normalize(value) : null;
@@ -229,7 +232,8 @@ function factory (type, config, load, typed, math) {
    * @param {string} str        A string like "5.2 inch", "4e2 cm/s^2"
    * @return {Unit} unit
    */
-  Unit.parse = function (str) {
+  Unit.parse = function (str, options) {
+    options = options || {};
     text = str;
     index = -1;
     c = '';
@@ -335,7 +339,7 @@ function factory (type, config, load, typed, math) {
         power: power
       });
       for(var i=0; i<BASE_DIMENSIONS.length; i++) {
-        unit.dimensions[i] += res.unit.dimensions[i] * power;
+        unit.dimensions[i] += (res.unit.dimensions[i] || 0) * power;
       }
 
       // Check for and consume closing parentheses, popping from the stack.
@@ -370,11 +374,13 @@ function factory (type, config, load, typed, math) {
       }
 
       // Replace the unit into the auto unit system
-      var baseDim = res.unit.base.key;
-      UNIT_SYSTEMS.auto[baseDim] = {
-        unit: res.unit,
-        prefix: res.prefix
-      };
+      if(res.unit.base) {
+        var baseDim = res.unit.base.key;
+        UNIT_SYSTEMS.auto[baseDim] = {
+          unit: res.unit,
+          prefix: res.prefix
+        };
+      }
     }
     
     // Has the string been entirely consumed?
@@ -394,7 +400,7 @@ function factory (type, config, load, typed, math) {
     }
 
     // Are there any units at all?
-    if(unit.units.length == 0) {
+    if(unit.units.length == 0 && !options.allowNoUnits) {
       throw new SyntaxError('"' + str + '" contains no units');
     }
 
@@ -538,6 +544,17 @@ function factory (type, config, load, typed, math) {
    * @private
    */
   function _findUnit(str) {
+  
+    // First, match units names exactly. For example, a user could define 'mm' as 10^-4 m, which is silly, but then we would want 'mm' to match the user-defined unit.
+    if(UNITS.hasOwnProperty(str)) {
+      var unit = UNITS[str];
+      var prefix = unit.prefixes[''];
+      return {
+        unit: unit,
+        prefix: prefix
+      }
+    }
+
     for (var name in UNITS) {
       if (UNITS.hasOwnProperty(name)) {
         if (endsWith(str, name)) {
@@ -589,7 +606,7 @@ function factory (type, config, load, typed, math) {
 
     // 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) {
+      if (Math.abs((this.dimensions[i] || 0) - (base.dimensions[i] || 0)) > 1e-12) {
         return false;
       }
     }
@@ -607,7 +624,7 @@ function factory (type, config, load, typed, math) {
   Unit.prototype.equalBase = function (other) {
     // All dimensions must be the same
     for(var i=0; i<BASE_DIMENSIONS.length; i++) {
-      if (Math.abs(this.dimensions[i] - other.dimensions[i]) > 1e-12) {
+      if (Math.abs((this.dimensions[i] || 0) - (other.dimensions[i] || 0)) > 1e-12) {
         return false;
       }
     }
@@ -634,12 +651,17 @@ function factory (type, config, load, typed, math) {
     var res = this.clone();
     
     for(var i = 0; i<BASE_DIMENSIONS.length; i++) {
-      res.dimensions[i] = this.dimensions[i] + other.dimensions[i];
+      // Dimensions arrays may be of different lengths. Default to 0.
+      res.dimensions[i] = (this.dimensions[i] || 0) + (other.dimensions[i] || 0);
     }
 
     // Append other's units list onto res (simplify later in Unit.prototype.format)
     for(var i=0; i<other.units.length; i++) {
-      var inverted = JSON.parse(JSON.stringify(other.units[i])); 
+      // Make a deep copy
+      var inverted = {};
+      for(var key in other.units[i]) {
+        inverted[key] = other.units[i][key];
+      }
       res.units.push(inverted);
     }
 
@@ -669,13 +691,17 @@ function factory (type, config, load, typed, math) {
     var res = this.clone();
     
     for(var i=0; i<BASE_DIMENSIONS.length; i++) {
-      res.dimensions[i] = this.dimensions[i] - other.dimensions[i];
+      // Dimensions arrays may be of different lengths. Default to 0.
+      res.dimensions[i] = (this.dimensions[i] || 0) - (other.dimensions[i] || 0);
     }
 
     // Invert and append other's units list onto res (simplify later in Unit.prototype.format)
     for(var i=0; i<other.units.length; i++) {
-      // Clone other's unit
-      var inverted = JSON.parse(JSON.stringify(other.units[i])); 
+      // Make a deep copy
+      var inverted = {};
+      for(var key in other.units[i]) {
+        inverted[key] = other.units[i][key];
+      }
       inverted.power = -inverted.power;
       res.units.push(inverted);
     }
@@ -706,7 +732,8 @@ function factory (type, config, load, typed, math) {
     var res = this.clone();
     
     for(var i=0; i<BASE_DIMENSIONS.length; i++) {
-      res.dimensions[i] = this.dimensions[i] * p;
+      // Dimensions arrays may be of different lengths. Default to 0.
+      res.dimensions[i] = (this.dimensions[i] || 0) * p;
     }
 
     // Adjust the power of each unit in the list
@@ -912,10 +939,9 @@ function factory (type, config, load, typed, math) {
       if(matchingBase) {
         // Does the unit system have a matching unit?
         if(currentUnitSystem.hasOwnProperty(matchingBase)) {
-          matchingUnit = currentUnitSystem[matchingBase]
+          matchingUnit = currentUnitSystem[matchingBase];
         }
       }
-
       var value;
       var str;
       if(matchingUnit) {
@@ -929,19 +955,26 @@ function factory (type, config, load, typed, math) {
         // Multiple units or units with powers are formatted like this:
         // 5 (kg m^2) / (s^3 mol)
         // Build an representation from the base units of the current unit system
+        var missingBaseDim = false;
         for(var i=0; i<BASE_DIMENSIONS.length; i++) {
           var baseDim = BASE_DIMENSIONS[i];
-          if(Math.abs(this.dimensions[i]) > 1e-12) {
-            proposedUnitList.push({
-              unit: currentUnitSystem[baseDim].unit,
-              prefix: currentUnitSystem[baseDim].prefix,
-              power: this.dimensions[i]
-            });
+          if(Math.abs(this.dimensions[i] || 0) > 1e-12) {
+            if(currentUnitSystem.hasOwnProperty(baseDim)) {
+              proposedUnitList.push({
+                unit: currentUnitSystem[baseDim].unit,
+                prefix: currentUnitSystem[baseDim].prefix,
+                power: this.dimensions[i] || 0
+              });
+            }
+            else {
+              missingBaseDim = true;
+            }
           }
         }
+        var util = require('util');
 
         // Is the proposed unit list "simpler" than the existing one?
-        if(proposedUnitList.length < this.units.length) {
+        if(proposedUnitList.length < this.units.length && !missingBaseDim) {
           // Replace this unit list with the proposed list
           this.units = proposedUnitList;
         }
@@ -1062,6 +1095,7 @@ function factory (type, config, load, typed, math) {
       }
     }
 
+
     var value = this._denormalize(this.value);
     var str = (this.value !== null) ? format(value, options || {}) : '';
     var unitStr = this.formatUnits();
@@ -2854,15 +2888,283 @@ function factory (type, config, load, typed, math) {
 
   // Create aliases
   for (var name in ALIASES) {
-    /* istanbul ignore next (we cannot really test next statement) */
-    if (ALIASES.hasOwnProperty(name)) {
+    if(ALIASES.hasOwnProperty(name)) {
       var unit = UNITS[ALIASES[name]];
-      var alias = Object.create(unit);
+      var alias = {};
+      for(var key in unit) {
+        if(unit.hasOwnProperty(key)) {
+          alias[key] = unit[key];
+        }
+      }
       alias.name = name;
       UNITS[name] = alias;
     }
   }
 
+  function assertUnitNameIsValid(name) {
+    for(var i=0; i<name.length; i++) {
+      var c = name.charAt(i);
+       
+      var isValidAlpha = function (p) {
+        return /^[a-zA-Z]$/.test(p);
+      };
+
+      var isDigit = function (c) {
+        return (c >= '0' && c <= '9');
+      }
+
+      if(i === 0 && !isValidAlpha(c))
+        throw new Error('Invalid unit name (must begin with alpha character): "' + name + '"');
+
+      if(i > 0 && !( isValidAlpha(c)
+                  || isDigit(c)))
+        throw new Error('Invalid unit name (only alphanumeric characters are allowed): "' + name + '"');
+
+    }
+  }
+
+  /**
+   * Wrapper around createUnitSingle.
+   * Example: 
+   *  createUnit({
+   *    foo: { },
+   *    bar: {
+   *      definition: 'kg/foo',
+   *      aliases: ['ba', 'barr', 'bars'],
+   *      offset: 200
+   *    },
+   *    baz: '4 bar'
+   *  }, 
+   *  {
+   *    override: true;
+   *  });
+   * @param {object} obj      Object map. Each key becomes a unit which is defined by its value.
+   * @param {object} options
+   */
+  Unit.createUnit = function(obj, options) {
+    
+    if(typeof(obj) !== 'object') {
+      throw new TypeError("createUnit expects first parameter to be of type 'Object'");
+    }
+
+    // Remove all units and aliases we are overriding
+    if(options && options.override) {
+      for(var key in obj) {
+        if(obj.hasOwnProperty(key)) {
+          Unit.deleteUnit(key);
+        }
+        if(obj[key].aliases) {
+          console.log(obj[key].aliases);
+          for(var i=0; i<obj[key].aliases.length; i++) {
+            Unit.deleteUnit(obj[key].aliases[i]);
+          }
+        }
+      }
+    }
+
+    // TODO: traverse multiple times until all units have been added
+    var lastUnit;
+    for(var key in obj) {
+      if(obj.hasOwnProperty(key)) {
+        lastUnit = Unit.createUnitSingle(key, obj[key]);
+      }
+    }
+    return lastUnit;
+  };
+
+  /**
+   * Create a user-defined unit and register it with the Unit type.
+   * Example: 
+   *  createUnitSingle('knot', '0.514444444 m/s')
+   *  createUnitSingle('acre', new Unit(43560, 'ft^2'))
+   *
+   * @param {string} name      The name of the new unit. Must be unique. Example: 'knot'
+   * @param {string, Unit} definition      Definition of the unit in terms of existing units. For example, '0.514444444 m / s'.
+   * @param {Object} options   (optional) An object containing any of the following properties:
+   *     prefixes {string} "none", "short", "long", "binary_short", or "binary_long". The default is "none".
+   *     aliases {Array} Array of strings. Example: ['knots', 'kt', 'kts']
+   *     offset {Numeric} An offset to apply when converting from the unit. For example, the offset for celsius is 273.15 and the offset for farhenheit is 459.67. Default is 0.
+   *
+   * @return {Unit} 
+   */
+  Unit.createUnitSingle = function(name, obj, options) {
+
+    if(typeof(obj) === 'undefined' || obj === null) {
+      obj = {};
+    }
+    
+    if(typeof(name) !== 'string') {
+      throw new TypeError("createUnitSingle expects first parameter to be of type 'string'");
+    }
+   
+    // Check collisions with existing units
+    if(UNITS.hasOwnProperty(name)) {
+      throw new Error('Cannot create unit "' + name + '": a unit with that name already exists');
+    }
+
+    // TODO: Validate name for collisions with other built-in functions (like abs or cos, for example), and for acceptable variable names. For example, '42' is probably not a valid unit. Nor is '%', since it is also an operator.
+
+    assertUnitNameIsValid(name);
+
+    var defUnit = null;   // The Unit from which the new unit will be created.
+    var aliases = [];
+    var offset = 0;
+    var definition;
+    var prefixes;
+    if(obj && obj.type === 'Unit') {
+      defUnit = obj.clone();
+    }
+    else if(typeof(obj) === 'string') {
+      if(obj !== '') {
+        definition = obj;
+      }
+    }
+    else if(typeof(obj) === 'object') {
+      definition = obj.definition;
+      prefixes = obj.prefixes; 
+      offset = obj.offset;
+      aliases = obj.aliases;
+    }
+    else {
+      throw new TypeError('Cannot create unit "' + name + '" from "' + obj.toString() + '": expecting "string" or "Unit" or "Object"');
+    }
+
+    if(aliases) {
+      for (var i=0; i<aliases.length; i++) {
+        if(UNITS.hasOwnProperty(aliases[i])) {
+          throw new Error('Cannot create alias "' + aliases[i] + '": a unit with that name already exists');
+        }
+      }
+    }
+
+    if(definition && typeof(definition) === 'string' && !defUnit) {
+      try {
+        defUnit = Unit.parse(definition, {allowNoUnits: true});
+      }
+      catch (ex) {
+        ex.message = 'Could not create unit "' + name + '" from "' + definition + '": ' + ex.message;
+        throw(ex);
+      }
+    }
+    else if(definition && definition.type === 'Unit') {
+      defUnit = definition.clone();
+    }
+
+    aliases = aliases || [];
+    offset = offset || 0;
+    if(prefixes && prefixes.toUpperCase) 
+      prefixes = PREFIXES[prefixes.toUpperCase()] || PREFIXES.NONE;
+    else
+      prefixes = PREFIXES.NONE;
+
+
+    // If defUnit is null, it is because the user did not
+    // specify a defintion. So create a new base dimension.
+    var newUnit = {};
+    if(!defUnit) {
+      // Add a new base dimension
+      var baseName = name + "_STUFF";   // foo --> foo_STUFF, or the essence of foo
+      if(BASE_DIMENSIONS.indexOf(baseName) >= 0) {
+        throw new Error('Cannot create new base unit "' + name + '": a base unit with that name already exists (and cannot be overridden)');
+      }
+      BASE_DIMENSIONS.push(baseName);
+
+      // Push 0 onto existing base units
+      for(var b in BASE_UNITS) {
+        if(BASE_UNITS.hasOwnProperty(b)) {
+          BASE_UNITS[b].dimensions[BASE_DIMENSIONS.length-1] = 0;
+        }
+      }
+
+      // Add the new base unit
+      var newBaseUnit = { dimensions: [] };
+      for(var i=0; i<BASE_DIMENSIONS.length; i++) {
+        newBaseUnit.dimensions[i] = 0;
+      }
+      newBaseUnit.dimensions[BASE_DIMENSIONS.length-1] = 1;
+      newBaseUnit.key = baseName;
+      BASE_UNITS[baseName] = newBaseUnit;
+       
+      newUnit = {
+        name: name,
+        value: 1,
+        dimensions: BASE_UNITS[baseName].dimensions.slice(0),
+        prefixes: prefixes,
+        offset: offset,
+        base: baseName
+      };
+
+      currentUnitSystem[baseName] = {
+        unit: newUnit,
+        prefix: PREFIXES.NONE['']
+      };
+
+    }
+    else {
+
+      newUnit = {
+        name: name,
+        value: defUnit.value,
+        dimensions: defUnit.dimensions.slice(0),
+        prefixes: prefixes,
+        offset: offset,
+      };
+      
+      // Create a new base if no matching base exists
+      var anyMatch = false;
+      for(var i in BASE_UNITS) {
+        if(BASE_UNITS.hasOwnProperty(i)) {
+          var match = true;
+          for(var j=0; j<BASE_DIMENSIONS.length; j++) {
+            if (Math.abs((newUnit.dimensions[j] || 0) - (BASE_UNITS[i].dimensions[j] || 0)) > 1e-12) {
+              match = false;
+              break;
+            }
+          }
+          if(match) {
+            anyMatch = true;
+            break;
+          }
+        }
+      }
+      if(!anyMatch) {
+        var baseName = name + "_STUFF";   // foo --> foo_STUFF, or the essence of foo
+        // Add the new base unit
+        var newBaseUnit = { dimensions: defUnit.dimensions.slice(0) };
+        newBaseUnit.key = baseName;
+        BASE_UNITS[baseName] = newBaseUnit;
+
+        currentUnitSystem[baseName] = {
+          unit: newUnit,
+          prefix: PREFIXES.NONE['']
+        };
+
+        newUnit.base = baseName;
+      }
+    }
+
+    Unit.UNITS[name] = newUnit;
+
+    for (var i=0; i<aliases.length; i++) {
+      var aliasName = aliases[i];
+      var alias = {};
+      for(var key in newUnit) {
+        if(newUnit.hasOwnProperty(key)) {
+          alias[key] = newUnit[key];
+        }
+      }
+      alias.name = aliasName;
+      Unit.UNITS[aliasName] = alias;
+    }
+
+    return new Unit(null, name);
+  };
+
+  Unit.deleteUnit = function(name) {
+    delete Unit.UNITS[name];
+  };
+
+
   Unit.PREFIXES = PREFIXES;
   Unit.BASE_UNITS = BASE_UNITS;
   Unit.UNITS = UNITS;
diff --git a/lib/type/unit/function/createUnit.js b/lib/type/unit/function/createUnit.js
new file mode 100644
index 000000000..1936d1a08
--- /dev/null
+++ b/lib/type/unit/function/createUnit.js
@@ -0,0 +1,81 @@
+'use strict';
+
+var deepMap = require('../../../utils/collection/deepMap');
+
+function factory (type, config, load, typed) {
+  /**
+   * Create a user-defined unit and register it with the Unit type.
+   *
+   * Syntax:
+   *
+   * math.createUnit({
+   *   baseUnit1: {
+   *     aliases: [string, ...]
+   *     prefixes: object
+   *   },
+   *   unit2: {
+   *     definition: string,
+   *     aliases: [string, ...]
+   *     prefixes: object,
+   *     offset: number
+   *   },
+   *   unit3: string    // Shortcut
+   * })
+   *
+   * Another shortcut:
+   * math.createUnit(string, unit : string, [object])
+   *
+   * Examples: 
+   *
+   *  math.createUnit('foo'); 
+   *  math.createUnit('knot', {definition: '0.514444444 m/s', aliases: ['knots', 'kt', 'kts]});
+   *  math.createUnit('mph', '1 mile/hour');
+   *
+   * @param {string} name      The name of the new unit. Must be unique. Example: 'knot'
+   * @param {string, Unit} definition      Definition of the unit in terms of existing units. For example, '0.514444444 m / s'.
+   * @param {Object} options   (optional) An object containing any of the following properties:
+   *     prefixes {string} "none", "short", "long", "binary_short", or "binary_long". The default is "none".
+   *     aliases {Array} Array of strings. Example: ['knots', 'kt', 'kts']
+   *     offset {Numeric} An offset to apply when converting from the unit. For example, the offset for celsius is 273.15. Default is 0.
+   *
+   * @return {Unit} The new unit
+   */
+  var createUnit = typed('createUnit', {
+
+    // General function signature. First parameter is an object where each property is the definition of a new unit. The object keys are the unit names and the values are the definitions. The values can be objects, strings, or Units. If a property is an empty object or an empty string, a new base unit is created. The second parameter is the options.
+    'Object, Object': function(obj, options) {
+      return type.Unit.createUnit(obj, options);
+    },
+
+    // Same as above but without the options.
+    'Object': function(obj) {
+      return type.Unit.createUnit(obj, {});
+    },
+
+    // Shortcut method for creating one unit.
+    'string, Unit | string | Object, Object': function (name, def, options) {
+      var obj = {};
+      obj[name] = def;
+      return type.Unit.createUnit(obj, options);
+    },
+
+    // Same as above but without the options.
+    'string, Unit | string | Object': function (name, def) {
+      var obj = {};
+      obj[name] = def;
+      return type.Unit.createUnit(obj, {});
+    },
+
+    // Without a definition, creates a base unit.
+    'string': function (name) {
+      var obj = {};
+      obj[name] = {};
+      return type.Unit.createUnit(obj, {});
+    },
+  });
+
+  return createUnit;
+}
+
+exports.name = 'createUnit';
+exports.factory = factory;
diff --git a/lib/type/unit/index.js b/lib/type/unit/index.js
index a567149bf..320847e61 100644
--- a/lib/type/unit/index.js
+++ b/lib/type/unit/index.js
@@ -5,6 +5,9 @@ module.exports = [
   // construction function
   require('./function/unit'),
 
+  // create new units
+  require('./function/createUnit'),
+
   // physical constants
   require('./physicalConstants')
 ];


Test patch:
diff --git a/test/type/unit/Unit.test.js b/test/type/unit/Unit.test.js
index 675c81ee3..9df28a957 100644
--- a/test/type/unit/Unit.test.js
+++ b/test/type/unit/Unit.test.js
@@ -1023,6 +1023,9 @@ describe('Unit', function() {
       assert.equal(unit2.value, 453.59237e-3);
       assert.equal(unit2.units[0].unit.name, 'lb');
       assert.equal(unit2.units[0].prefix.name, '');
+
+      assert.equal(math.eval('2 feet * 8 s').toString(), '16 feet s');
+      assert.equal(math.eval('2 s * 8 feet').toString(), '16 s feet');
     });
   });
 
@@ -1040,4 +1043,107 @@ describe('Unit', function() {
       assert.equal(new Unit(1, 'eV')    .equals(new Unit(1.602176565e-19, 'J')), true);
     });
   });
+
+  describe('createUnitSingle', function() {
+    it('should create a custom unit from a string definition', function() {
+      Unit.createUnitSingle('widget', '5 kg bytes');
+      assert.equal(new Unit(1, 'widget').equals(new Unit(5, 'kg bytes')), true);
+      Unit.createUnitSingle('woggle', '4 widget^2');
+      assert.equal(new Unit(1, 'woggle').equals(new Unit(4, 'widget^2')), true);
+      assert.equal(new Unit(2, 'woggle').equals(new Unit(200, 'kg^2 bytes^2')), true);
+    });
+
+    it('should create a custom unit from a Unit definition', function() {
+      var Unit1 = new Unit(5, 'N/woggle');
+      Unit.createUnitSingle('gadget', Unit1);
+      assert.equal(new Unit(1, 'gadget').equals(new Unit(5, 'N/woggle')), true);
+    });
+
+    it('should create a custom unit from a configuration object', function() {
+      Unit.createUnitSingle('wiggle', { definition: '4 rad^2/s', offset: 1, prefixes: 'long' });
+      assert.equal(math.eval('8000 rad^2/s').toString(), '1 kilowiggle');
+    });
+
+    it('should return the new (value-less) unit', function() {
+      var Unit2 = new Unit(1000, 'N h kg^-2 bytes^-2');
+      var newUnit = Unit.createUnitSingle('whimsy', '8 gadget hours');
+      assert.equal(Unit2.to(newUnit).toString(), '2500 whimsy');
+    });
+
+    it('should not override an existing unit', function() {
+      assert.throws(function () { Unit.createUnitSingle('m', '1 kg'); }, /Cannot create unit .*: a unit with that name already exists/);
+      assert.throws(function () { Unit.createUnitSingle('gadget', '1 kg'); }, /Cannot create unit .*: a unit with that name already exists/);
+      assert.throws(function () { Unit.createUnitSingle('morogrove', { aliases: 's' }); }, /Cannot create alias .*: a unit with that name already exists/);
+    });
+
+    it('should throw an error for invalid parameters', function() {
+      assert.throws(function() { Unit.createUnitSingle(); }, /createUnitSingle expects first parameter/);
+      assert.throws(function() { Unit.createUnitSingle(42); }, /createUnitSingle expects first parameter/);
+      assert.throws(function() { Unit.createUnitSingle('42'); }, /Error: Invalid unit name/);
+    });
+
+    it('should apply the correct prefixes', function() {
+      Unit.createUnitSingle('millizilch', {definition: '1e-3 m', prefixes: 'long'});
+      assert.equal(new Unit(1e-6, 'millizilch').toString(), '1 micromillizilch');
+    });
+
+    it('should override prefixed built-in units', function() {
+      Unit.createUnitSingle('mm', { definition: '1e-4 m', prefixes: 'short'});   // User is being silly
+      assert.equal(new Unit(1e-3, 'mm').toString(), '1 mmm'); // Use the user's new definition
+      assert.equal(new Unit(1e-3, 'mm').to('m').format(4), '1e-7 m'); // Use the user's new definition
+    });
+
+    it('should create aliases', function() {
+      Unit.createUnitSingle('knot', {definition: '0.51444444 m/s', aliases:['knots', 'kts', 'kt']});
+      assert.equal(new Unit(1, 'knot').equals(new Unit(1, 'kts')), true);
+      assert.equal(new Unit(1, 'kt').equals(new Unit(1, 'knots')), true);
+    });
+
+    it('should apply offset correctly', function() {
+      Unit.createUnitSingle('whatsit', {definition: '3.14 kN', offset:2});
+      assert.equal(new Unit(1, 'whatsit').to('kN').toString(), '9.42 kN');
+    });
+
+    it('should create new base units', function() {
+      var fooBase_unit = Unit.createUnitSingle('fooBase');
+      assert.equal(fooBase_unit.dimensions.toString(), Unit.BASE_UNITS['fooBase_STUFF'].dimensions.toString());
+      var testUnit = new Unit(5, 'fooBase');
+      assert.equal(testUnit.toString(), '5 fooBase');
+    });
+
+    it('should not override base units', function() {
+      assert.throws(function() { Unit.createUnitSingle('fooBase', '', {override: true}); }, /Cannot create/);
+    });
+
+    it('should create and use a new base if no matching base exists', function() {
+      Unit.createUnitSingle('jabberwocky', '1 mile^5/hour');
+      assert.equal('jabberwocky_STUFF' in Unit.BASE_UNITS, true);
+      assert.equal(math.eval('4 mile^5/minute').format(4), '240 jabberwocky');
+    });
+  });
+
+  describe('createUnit', function() {
+    it('should create multiple units', function() {
+      Unit.createUnit({
+        'foo1': '',
+        'foo2': '2 foo1',
+        'foo3': {
+          definition: '2 foo2',
+          prefixes: 'long'
+        }
+      });
+      assert.equal(math.eval('2 foo3 to foo1').toString(), '8 foo1');
+    });
+
+    it('should override units when requested and if able', function() {
+      assert.throws(function() {Unit.createUnit({foo1:''});}, /Cannot/);
+      assert.throws(function() {Unit.createUnit({foo1:'', override:true});}, /Cannot/);
+      Unit.createUnit({foo3:''}, {override: true});
+    });
+
+    it('should throw error when first parameter is not an object', function() {
+      assert.throws(function() {Unit.createUnit('not an object');}, /createUnit expects first/);
+    });
+  });
+
 });
diff --git a/test/type/unit/function/createUnit.test.js b/test/type/unit/function/createUnit.test.js
new file mode 100644
index 000000000..b25342549
--- /dev/null
+++ b/test/type/unit/function/createUnit.test.js
@@ -0,0 +1,48 @@
+var assert = require('assert');
+var math = require('../../../../index');
+var createUnit = math.createUnit;
+var Unit = math.type.Unit;
+
+describe('createUnit', function() {
+
+  it('should create a unit', function () {
+    var u = createUnit('flibbity', '4 hogshead');
+    assert.equal(math.eval('2 flibbity to hogshead').toString(), '8 hogshead');
+  });
+
+  it('should accept a unit as second parameter', function () {
+    assert.equal(math.eval('50 in^2 to createUnit("bingo", 25 in^2)').toString(), '2 bingo');
+  });
+
+  it('should accept a string as second parameter', function () {
+    assert.equal(math.eval('50 in^2 to createUnit("zingo", "25 in^2")').toString(), '2 zingo');
+  });
+
+  it('should return the created unit', function() {
+    assert.equal(math.eval('createUnit("giblet", "6 flibbity")').toString(), 'giblet');
+    assert.equal(math.eval('120 hogshead to createUnit("fliblet", "0.25 giblet")').format(4), '20 fliblet');
+  });
+
+  it('should accept options', function() {
+    math.eval('createUnit("whosit", { definition: 3.14 kN, prefixes:"long"})');
+    assert.equal(math.eval('1e-9 whosit').toString(), '1 nanowhosit');
+
+    math.eval('createUnit("wheresit", { definition: 3.14 kN, offset:2})');
+    assert.equal(math.eval('1 wheresit to kN').toString(), '9.42 kN');
+  });
+
+  it('should create multiple units', function() {
+    math.eval('createUnit({"xfoo":{}, "xbar":{}, "xfoobar":"1 xfoo xbar"})');
+    assert.equal(math.eval('5 xfoo').toString(), '5 xfoo');
+  });
+
+  it('should simplify created units', function() {
+    // TODO: New units do not have base units set, therefore simplifying is impossible. Figure out a way to create base units for created units.
+    assert.equal(math.eval('5 xfoo * 5 xbar').toString(), '25 xfoobar');
+  });
+
+  it('should override units', function() {
+    math.eval('createUnit({"bar": 1e12 Pa}, {"override":true})');
+    assert.equal(math.eval('1 bar to Pa').toString(), '1e+12 Pa');
+  });
+});