Added Unit.createUnits and createUnits function, and tests.

This commit is contained in:
Eric 2016-07-13 00:29:14 -06:00
parent 1588b0ebe0
commit b7978dee23
5 changed files with 221 additions and 5 deletions

View File

@ -370,11 +370,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?
@ -538,6 +540,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)) {
@ -2863,6 +2876,78 @@ function factory (type, config, load, typed, math) {
}
}
/**
* Create a user-defined unit and register it with the Unit type.
* Example:
* createUnit('knot', '0.514444444 m/s')
* createUnit('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.createUnit = function(name, definition, options) {
if(typeof(name) !== 'string') {
throw new TypeError("createUnit 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.
var defUnit;
if(typeof(definition) === 'string') {
defUnit = Unit.parse(definition);
}
else if(definition && definition.type === 'Unit') {
defUnit = definition.clone();
}
else {
throw new TypeError("createUnit expects second parameter to be of type 'string' or 'Unit'");
}
var prefixes = PREFIXES.NONE;
var aliases = [];
var offset = 0;
if(options) {
if(options.prefixes) {
prefixes = PREFIXES[options.prefixes.toUpperCase()] || PREFIXES.NONE;
}
aliases = options.aliases || [];
offset = options.offset || 0;
}
var newUnit = {
name: name,
value: defUnit.value,
dimensions: JSON.parse(JSON.stringify(defUnit.dimensions)),
prefixes: prefixes,
offset: offset
}
Unit.UNITS[name] = newUnit;
for (var i=0; i<aliases.length; i++) {
var name = aliases[i];
var alias = Object.create(newUnit);
alias.name = name;
Unit.UNITS[name] = alias;
}
return new Unit(null, name);
};
Unit.PREFIXES = PREFIXES;
Unit.BASE_UNITS = BASE_UNITS;
Unit.UNITS = UNITS;

View File

@ -0,0 +1,39 @@
'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(string, unit : string, [object])
*
* Example:
*
* math.createUnit('knot', '0.514444444 m/s', {aliases: ['knots', 'kt', 'kts]})
*
* @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', {
'string, Unit | string': function (name, def) {
return type.Unit.createUnit(name, def);
},
'string, Unit | string, Object': function (name, def, options) {
return type.Unit.createUnit(name, def, options);
}
});
return createUnit;
}
exports.name = 'createUnit';
exports.factory = factory;

View File

@ -5,6 +5,9 @@ module.exports = [
// construction function
require('./function/unit'),
// create new units
require('./function/createUnit'),
// physical constants
require('./physicalConstants')
];

View File

@ -1040,4 +1040,60 @@ describe('Unit', function() {
assert.equal(new Unit(1, 'eV') .equals(new Unit(1.602176565e-19, 'J')), true);
});
});
describe('createUnit', function() {
it('should create a custom unit from a string definition', function() {
Unit.createUnit('widget', '5 kg bytes');
assert.equal(new Unit(1, 'widget').equals(new Unit(5, 'kg bytes')), true);
Unit.createUnit('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.createUnit('gadget', Unit1);
assert.equal(new Unit(1, 'gadget').equals(new Unit(5, 'N/woggle')), true);
});
it('should return the new (value-less) unit', function() {
var Unit2 = new Unit(1000, 'N h kg^-2 bytes^-2');
var newUnit = Unit.createUnit('whimsy', '8 gadget hours');
assert.equal(Unit2.to(newUnit).toString(), '2500 whimsy');
});
it('should not override an existing unit', function() {
assert.throws(function () { Unit.createUnit('m', '1 kg'); }, /Cannot create unit .*: a unit with that name already exists/);
assert.throws(function () { Unit.createUnit('gadget', '1 kg'); }, /Cannot create unit .*: a unit with that name already exists/);
});
it('should throw an error for invalid parameters', function() {
assert.throws(function() { Unit.createUnit(); }, /createUnit expects first parameter/);
assert.throws(function() { Unit.createUnit(42); }, /createUnit expects first parameter/);
assert.throws(function() { Unit.createUnit('42'); }, /createUnit expects second parameter/);
assert.throws(function() { Unit.createUnit('42', 3.14); }, /createUnit expects second parameter/);
});
it('should apply the correct prefixes', function() {
Unit.createUnit('millizilch', '1e-3 m', {prefixes: 'long'});
assert.equal(new Unit(1e-6, 'millizilch').toString(), '1 micromillizilch');
});
it('should override prefixed built-in units', function() {
Unit.createUnit('mm', '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.createUnit('knot', '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.createUnit('whatsit', '3.14 kN', {offset:2});
assert.equal(new Unit(1, 'whatsit').to('kN').toString(), '9.42 kN');
});
});
});

View File

@ -0,0 +1,33 @@
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 unit 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", 3.14 kN, {prefixes:"long"})');
assert.equal(math.eval('1e-9 whosit').toString(), '1 nanowhosit');
math.eval('createUnit("wheresit", 3.14 kN, {offset:2})');
assert.equal(math.eval('1 wheresit to kN').toString(), '9.42 kN');
});
});