From 25c1f81f249408ca0856c8e58c648f6ab0389ad6 Mon Sep 17 00:00:00 2001 From: Julien Sanchez Date: Sun, 25 Mar 2018 23:00:26 +0200 Subject: [PATCH] Release v1.7.1 --- History.md | 5 + README.md | 2 +- RELEASE | 2 +- bower.json | 2 +- build/quantities.esm.js | 1995 ++++++++++++++++++++++ build/quantities.js | 3452 +++++++++++++++++++-------------------- package.json | 2 +- src/quantities.js | 2 +- 8 files changed, 3731 insertions(+), 1731 deletions(-) create mode 100644 build/quantities.esm.js diff --git a/History.md b/History.md index e15bf8c..6e010ce 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +1.7.1 / 2018-03-25 +------------------ + +* Add missing unit aliases + 1.7.0 / 2017-08-23 ------------------ diff --git a/README.md b/README.md index 6e97093..6a6bffe 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ and within browsers. ### Browser -Download [latest release v1.7.0](https://raw.github.com/gentooboontoo/js-quantities/v1.7.0/build/quantities.js) +Download [latest release v1.7.1](https://raw.github.com/gentooboontoo/js-quantities/v1.7.1/build/quantities.js) or install it with Bower: bower install js-quantities diff --git a/RELEASE b/RELEASE index bd8bf88..943f9cb 100644 --- a/RELEASE +++ b/RELEASE @@ -1 +1 @@ -1.7.0 +1.7.1 diff --git a/bower.json b/bower.json index e7992bc..1997a0e 100644 --- a/bower.json +++ b/bower.json @@ -1,6 +1,6 @@ { "name": "js-quantities", - "version": "1.7.0", + "version": "1.7.1", "description": "JavaScript library for quantity calculation and unit conversion", "main": "./build/quantities.js", "keywords": [ diff --git a/build/quantities.esm.js b/build/quantities.esm.js new file mode 100644 index 0000000..95d8168 --- /dev/null +++ b/build/quantities.esm.js @@ -0,0 +1,1995 @@ +/* +The MIT License (MIT) +Copyright © 2006-2007 Kevin C. Olbrich +Copyright © 2010-2016 LIM SAS (http://lim.eu) - Julien Sanchez + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +/** + * Tests if a value is a string + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a string, false otherwise + */ +function isString(value) { + return typeof value === "string" || value instanceof String; +} + +/* + * Prefer stricter Number.isFinite if currently supported. + * To be dropped when ES6 is finalized. Obsolete browsers will + * have to use ES6 polyfills. + */ +var isFiniteImpl = Number.isFinite || window.isFinite; +/** + * Tests if a value is a number + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a number, false otherwise + */ +function isNumber(value) { + // Number.isFinite allows not to consider NaN or '1' as numbers + return isFiniteImpl(value); +} + +/* + * Identity function + */ +function identity(value) { + return value; +} + +/** + * Returns unique strings from list + * + * @param {string[]} strings - array of strings + * + * + * @returns {string[]} a new array of strings without duplicates + */ +function uniq(strings) { + var seen = {}; + return strings.filter(function(item) { + return seen.hasOwnProperty(item) ? false : (seen[item] = true); + }); +} + +function compareArray(array1, array2) { + if (array2.length !== array1.length) { + return false; + } + for (var i = 0; i < array1.length; i++) { + if (array2[i].compareArray) { + if (!array2[i].compareArray(array1[i])) { + return false; + } + } + if (array2[i] !== array1[i]) { + return false; + } + } + return true; +} + +function assign(target, properties) { + Object.keys(properties).forEach(function(key) { + target[key] = properties[key]; + }); +} + +/** + * Safely multiplies numbers while avoiding floating errors + * like 0.1 * 0.1 => 0.010000000000000002 + * + * @returns {number} result + * @param {...number} number + */ +function mulSafe() { + var result = 1, decimals = 0; + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + decimals = decimals + getFractional(arg); + result *= arg; + } + + return decimals !== 0 ? round(result, decimals) : result; +} + +/** + * Safely divides two numbers while avoiding floating errors + * like 0.3 / 0.05 => 5.999999999999999 + * + * @returns {number} result + * @param {number} num Numerator + * @param {number} den Denominator + */ +function divSafe(num, den) { + if (den === 0) { + throw new Error("Divide by zero"); + } + + var factor = Math.pow(10, getFractional(den)); + var invDen = factor / (factor * den); + + return mulSafe(num, invDen); +} + +/** + * Rounds value at the specified number of decimals + * + * @param {number} val - value to round + * @param {number} decimals - number of decimals + * + * @returns {number} rounded number + */ +function round(val, decimals) { + return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals); +} + +function getFractional(num) { + // Check for NaNs or Infinities + if (!isFinite(num)) { + return 0; + } + + // Faster than parsing strings + // http://jsperf.com/count-decimals/2 + var count = 0; + while (num % 1 !== 0) { + num *= 10; + count++; + } + return count; +} + +/** + * Custom error type definition + * @constructor + */ +function QtyError() { + var err; + if (!this) { // Allows to instantiate QtyError without new() + err = Object.create(QtyError.prototype); + QtyError.apply(err, arguments); + return err; + } + err = Error.apply(this, arguments); + this.name = "QtyError"; + this.message = err.message; + this.stack = err.stack; +} +QtyError.prototype = Object.create(Error.prototype, {constructor: { value: QtyError }}); + +/* + * Throws incompatible units error + * @param {string} left - units + * @param {string} right - units incompatible with first argument + * @throws "Incompatible units" error + */ +function throwIncompatibleUnits(left, right) { + throw new QtyError("Incompatible units: " + left + " and " + right); +} + +var UNITS = { + /* prefixes */ + "" : [["googol"], 1e100, "prefix"], + "" : [["Ki","Kibi","kibi"], Math.pow(2,10), "prefix"], + "" : [["Mi","Mebi","mebi"], Math.pow(2,20), "prefix"], + "" : [["Gi","Gibi","gibi"], Math.pow(2,30), "prefix"], + "" : [["Ti","Tebi","tebi"], Math.pow(2,40), "prefix"], + "" : [["Pi","Pebi","pebi"], Math.pow(2,50), "prefix"], + "" : [["Ei","Exi","exi"], Math.pow(2,60), "prefix"], + "" : [["Zi","Zebi","zebi"], Math.pow(2,70), "prefix"], + "" : [["Yi","Yebi","yebi"], Math.pow(2,80), "prefix"], + "" : [["Y","Yotta","yotta"], 1e24, "prefix"], + "" : [["Z","Zetta","zetta"], 1e21, "prefix"], + "" : [["E","Exa","exa"], 1e18, "prefix"], + "" : [["P","Peta","peta"], 1e15, "prefix"], + "" : [["T","Tera","tera"], 1e12, "prefix"], + "" : [["G","Giga","giga"], 1e9, "prefix"], + "" : [["M","Mega","mega"], 1e6, "prefix"], + "" : [["k","kilo"], 1e3, "prefix"], + "" : [["h","Hecto","hecto"], 1e2, "prefix"], + "" : [["da","Deca","deca","deka"], 1e1, "prefix"], + "" : [["d","Deci","deci"], 1e-1, "prefix"], + "" : [["c","Centi","centi"], 1e-2, "prefix"], + "" : [["m","Milli","milli"], 1e-3, "prefix"], + "" : [ + ["u","\u03BC"/*µ as greek letter*/,"\u00B5"/*µ as micro sign*/,"Micro","mc","micro"], + 1e-6, + "prefix" + ], + "" : [["n","Nano","nano"], 1e-9, "prefix"], + "" : [["p","Pico","pico"], 1e-12, "prefix"], + "" : [["f","Femto","femto"], 1e-15, "prefix"], + "" : [["a","Atto","atto"], 1e-18, "prefix"], + "" : [["z","Zepto","zepto"], 1e-21, "prefix"], + "" : [["y","Yocto","yocto"], 1e-24, "prefix"], + + "<1>" : [["1", "<1>"], 1, ""], + /* length units */ + "" : [["m","meter","meters","metre","metres"], 1.0, "length", [""] ], + "" : [["in","inch","inches","\""], 0.0254, "length", [""]], + "" : [["ft","foot","feet","'"], 0.3048, "length", [""]], + "" : [["yd","yard","yards"], 0.9144, "length", [""]], + "" : [["mi","mile","miles"], 1609.344, "length", [""]], + "" : [["nmi","naut-mile"], 1852, "length", [""]], + "": [["league","leagues"], 4828, "length", [""]], + "": [["furlong","furlongs"], 201.2, "length", [""]], + "" : [["rd","rod","rods"], 5.029, "length", [""]], + "" : [["mil","mils"], 0.0000254, "length", [""]], + "" :[["ang","angstrom","angstroms"], 1e-10, "length", [""]], + "" : [["fathom","fathoms"], 1.829, "length", [""]], + "" : [["pica","picas"], 0.00423333333, "length", [""]], + "" : [["pt","point","points"], 0.000352777778, "length", [""]], + "" : [["z","red-shift", "redshift"], 1.302773e26, "length", [""]], + "" : [["AU","astronomical-unit"], 149597900000, "length", [""]], + "":[["ls","light-second"], 299792500, "length", [""]], + "":[["lmin","light-minute"], 17987550000, "length", [""]], + "" : [["ly","light-year"], 9460528000000000, "length", [""]], + "" : [["pc","parsec","parsecs"], 30856780000000000, "length", [""]], + "" : [["DM","datamile"], 1828.8, "length", [""]], + + /* mass */ + "" : [["kg","kilogram","kilograms"], 1.0, "mass", [""]], + "" : [["u","AMU","amu"], 1.660538921e-27, "mass", [""]], + "" : [["Da","Dalton","Daltons","dalton","daltons"], 1.660538921e-27, "mass", [""]], + "" : [["slug","slugs"], 14.5939029, "mass", [""]], + "" : [["tn","ton","short-ton"], 907.18474, "mass", [""]], + "":[["tonne","metric-ton"], 1000, "mass", [""]], + "" : [["ct","carat","carats"], 0.0002, "mass", [""]], + "" : [["lbs","lb","pound","pounds","#"], 0.45359237, "mass", [""]], + "" : [["oz","ounce","ounces"], 0.0283495231, "mass", [""]], + "" : [["g","gram","grams","gramme","grammes"], 1e-3, "mass", [""]], + "" : [["grain","grains","gr"], 6.479891e-5, "mass", [""]], + "" : [["dram","drams","dr"], 0.0017718452, "mass",[""]], + "" : [["stone","stones","st"],6.35029318, "mass",[""]], + + /* area */ + "":[["hectare"], 10000, "area", ["",""]], + "":[["acre","acres"], 4046.85642, "area", ["",""]], + "":[["sqft"], 1, "area", ["",""]], + + /* volume */ + "" : [["l","L","liter","liters","litre","litres"], 0.001, "volume", ["","",""]], + "": [["gal","gallon","gallons"], 0.0037854118, "volume", ["","",""]], + "": [["qt","quart","quarts"], 0.00094635295, "volume", ["","",""]], + "": [["pt","pint","pints"], 0.000473176475, "volume", ["","",""]], + "": [["cu","cup","cups"], 0.000236588238, "volume", ["","",""]], + "": [["floz","fluid-ounce","fluid-ounces"], 2.95735297e-5, "volume", ["","",""]], + "": [["tb","tbsp","tbs","tablespoon","tablespoons"], 1.47867648e-5, "volume", ["","",""]], + "": [["tsp","teaspoon","teaspoons"], 4.92892161e-6, "volume", ["","",""]], + "": [["bu","bsh","bushel","bushels"], 0.035239072, "volume", ["","",""]], + + /* speed */ + "" : [["kph"], 0.277777778, "speed", [""], [""]], + "" : [["mph"], 0.44704, "speed", [""], [""]], + "" : [["kt","kn","kts","knot","knots"], 0.514444444, "speed", [""], [""]], + "" : [["fps"], 0.3048, "speed", [""], [""]], + + /* acceleration */ + "" : [["gee"], 9.80665, "acceleration", [""], ["",""]], + + /* temperature_difference */ + "" : [["degK","kelvin"], 1.0, "temperature", [""]], + "" : [["degC","celsius","celsius","centigrade"], 1.0, "temperature", [""]], + "" : [["degF","fahrenheit"], 5 / 9, "temperature", [""]], + "" : [["degR","rankine"], 5 / 9, "temperature", [""]], + "" : [["tempK","temp-K"], 1.0, "temperature", [""]], + "" : [["tempC","temp-C"], 1.0, "temperature", [""]], + "" : [["tempF","temp-F"], 5 / 9, "temperature", [""]], + "" : [["tempR","temp-R"], 5 / 9, "temperature", [""]], + + /* time */ + "": [["s","sec","secs","second","seconds"], 1.0, "time", [""]], + "": [["min","mins","minute","minutes"], 60.0, "time", [""]], + "": [["h","hr","hrs","hour","hours"], 3600.0, "time", [""]], + "": [["d","day","days"], 3600 * 24, "time", [""]], + "": [["wk","week","weeks"], 7 * 3600 * 24, "time", [""]], + "": [["fortnight","fortnights"], 1209600, "time", [""]], + "": [["y","yr","year","years","annum"], 31556926, "time", [""]], + "":[["decade","decades"], 315569260, "time", [""]], + "":[["century","centuries"], 3155692600, "time", [""]], + + /* pressure */ + "" : [["Pa","pascal","Pascal"], 1.0, "pressure", [""],["","",""]], + "" : [["bar","bars"], 100000, "pressure", [""],["","",""]], + "" : [["mmHg"], 133.322368, "pressure", [""],["","",""]], + "" : [["inHg"], 3386.3881472, "pressure", [""],["","",""]], + "" : [["torr"], 133.322368, "pressure", [""],["","",""]], + "" : [["atm","ATM","atmosphere","atmospheres"], 101325, "pressure", [""],["","",""]], + "" : [["psi"], 6894.76, "pressure", [""],["","",""]], + "" : [["cmH2O","cmh2o"], 98.0638, "pressure", [""],["","",""]], + "" : [["inH2O","inh2o"], 249.082052, "pressure", [""],["","",""]], + + /* viscosity */ + "" : [["P","poise"], 0.1, "viscosity", [""],["",""] ], + "" : [["St","stokes"], 1e-4, "viscosity", ["",""], [""]], + + /* substance */ + "" : [["mol","mole"], 1.0, "substance", [""]], + + /* concentration */ + "" : [["M","molar"], 1000, "concentration", [""], ["","",""]], + "" : [["wt%","wtpercent"], 10, "concentration", [""], ["","",""]], + + /* activity */ + "" : [["kat","katal","Katal"], 1.0, "activity", [""], [""]], + "" : [["U","enzUnit","unit"], 16.667e-16, "activity", [""], [""]], + + /* capacitance */ + "" : [["F","farad","Farad"], 1.0, "capacitance", ["","","","","",""], ["", "", ""]], + + /* charge */ + "" : [["C","coulomb","Coulomb"], 1.0, "charge", ["",""]], + "" : [["Ah"], 3600, "charge", ["",""]], + + /* current */ + "" : [["A","Ampere","ampere","amp","amps"], 1.0, "current", [""]], + + /* conductance */ + "" : [["S","Siemens","siemens"], 1.0, "conductance", ["","","","",""], ["","",""]], + + /* inductance */ + "" : [["H","Henry","henry"], 1.0, "inductance", ["","",""], ["","","",""]], + + /* potential */ + "" : [["V","Volt","volt","volts"], 1.0, "potential", ["","",""], ["","","",""]], + + /* resistance */ + "" : [ + ["Ohm","ohm","\u03A9"/*Ω as greek letter*/,"\u2126"/*Ω as ohm sign*/], + 1.0, + "resistance", + ["","",""],["","","","",""] + ], + /* magnetism */ + "" : [["Wb","weber","webers"], 1.0, "magnetism", ["","",""], ["","",""]], + "" : [["T","tesla","teslas"], 1.0, "magnetism", [""], ["","",""]], + "" : [["G","gauss"], 1e-4, "magnetism", [""], ["","",""]], + "" : [["Mx","maxwell","maxwells"], 1e-8, "magnetism", ["","",""], ["","",""]], + "" : [["Oe","oersted","oersteds"], 250.0 / Math.PI, "magnetism", [""], [""]], + + /* energy */ + "" : [["J","joule","Joule","joules"], 1.0, "energy", ["","",""], ["",""]], + "" : [["erg","ergs"], 1e-7, "energy", ["","",""], ["",""]], + "" : [["BTU","btu","BTUs"], 1055.056, "energy", ["","",""], ["",""]], + "" : [["cal","calorie","calories"], 4.18400, "energy",["","",""], ["",""]], + "" : [["Cal","Calorie","Calories"], 4184.00, "energy",["","",""], ["",""]], + "" : [["th","therm","therms","Therm","therm-US"], 105480400, "energy",["","",""], ["",""]], + "" : [["Wh"], 3600, "energy",["","",""], ["",""]], + + /* force */ + "" : [["N","Newton","newton"], 1.0, "force", ["",""], ["",""]], + "" : [["dyn","dyne"], 1e-5, "force", ["",""], ["",""]], + "" : [["lbf","pound-force"], 4.448222, "force", ["",""], ["",""]], + + /* frequency */ + "" : [["Hz","hertz","Hertz"], 1.0, "frequency", ["<1>"], [""]], + + /* angle */ + "" :[["rad","radian","radians"], 1.0, "angle", [""]], + "" :[["deg","degree","degrees"], Math.PI / 180.0, "angle", [""]], + "" :[["gon","grad","gradian","grads"], Math.PI / 200.0, "angle", [""]], + "" : [["sr","steradian","steradians"], 1.0, "solid_angle", [""]], + + /* rotation */ + "" : [["rotation"], 2.0 * Math.PI, "angle", [""]], + "" :[["rpm"], 2.0 * Math.PI / 60.0, "angular_velocity", [""], [""]], + + /* information */ + "" :[["B","byte","bytes"], 1.0, "information", [""]], + "" :[["b","bit","bits"], 0.125, "information", [""]], + + /* information rate */ + "" : [["Bps"], 1.0, "information_rate", [""], [""]], + "" : [["bps"], 0.125, "information_rate", [""], [""]], + + /* currency */ + "":[["USD","dollar"], 1.0, "currency", [""]], + "" :[["cents"], 0.01, "currency", [""]], + + /* luminosity */ + "" : [["cd","candela"], 1.0, "luminosity", [""]], + "" : [["lm","lumen"], 1.0, "luminous_power", ["",""]], + "" :[["lux"], 1.0, "illuminance", ["",""], ["",""]], + + /* power */ + "" : [["W","watt","watts"], 1.0, "power", ["","",""], ["","",""]], + "" : [["VA","volt-ampere"], 1.0, "power", ["","",""], ["","",""]], + "" : [["var","Var","VAr","VAR","volt-ampere-reactive"], 1.0, "power", ["","",""], ["","",""]], + "" : [["hp","horsepower"], 745.699872, "power", ["","",""], ["","",""]], + + /* radiation */ + "" : [["Gy","gray","grays"], 1.0, "radiation", ["",""], ["",""]], + "" : [["R","roentgen"], 0.009330, "radiation", ["",""], ["",""]], + "" : [["Sv","sievert","sieverts"], 1.0, "radiation", ["",""], ["",""]], + "" : [["Bq","becquerel","becquerels"], 1.0, "radiation", ["<1>"],[""]], + "" : [["Ci","curie","curies"], 3.7e10, "radiation", ["<1>"],[""]], + + /* rate */ + "" : [["cpm"], 1.0 / 60.0, "rate", [""],[""]], + "" : [["dpm"], 1.0 / 60.0, "rate", [""],[""]], + "" : [["bpm"], 1.0 / 60.0, "rate", [""],[""]], + + /* resolution / typography */ + "" : [["dot","dots"], 1, "resolution", [""]], + "" : [["pixel","px"], 1, "resolution", [""]], + "" : [["ppi"], 1, "resolution", [""], [""]], + "" : [["dpi"], 1, "typography", [""], [""]], + + /* other */ + "" : [["cells","cell"], 1, "counting", [""]], + "" : [["each"], 1.0, "counting", [""]], + "" : [["count"], 1.0, "counting", [""]], + "" : [["bp","base-pair"], 1.0, "counting", [""]], + "" : [["nt","nucleotide"], 1.0, "counting", [""]], + "" : [["molecule","molecules"], 1.0, "counting", ["<1>"]], + "" : [["doz","dz","dozen"],12.0,"prefix_only", [""]], + "": [["%","percent"], 0.01, "prefix_only", ["<1>"]], + "" : [["ppm"],1e-6, "prefix_only", ["<1>"]], + "" : [["ppt"],1e-9, "prefix_only", ["<1>"]], + "" : [["gr","gross"],144.0, "prefix_only", ["",""]], + "" : [["dB","decibel","decibels"], 1.0, "logarithmic", [""]] +}; + +var BASE_UNITS = ["","","","", "","","","","","","","","",""]; + +var UNITY = "<1>"; +var UNITY_ARRAY = [UNITY]; + +// Setup + +/** + * Asserts unit definition is valid + * + * @param {string} unitDef - Name of unit to test + * @param {Object} definition - Definition of unit to test + * + * @throws {QtyError} if unit definition is not valid + */ +function validateUnitDefinition(unitDef, definition) { + var scalar = definition[1]; + var numerator = definition[3] || []; + var denominator = definition[4] || []; + if (!isNumber(scalar)) { + throw new QtyError(unitDef + ": Invalid unit definition. " + + "'scalar' must be a number"); + } + + numerator.forEach(function(unit) { + if (UNITS[unit] === undefined) { + throw new QtyError(unitDef + ": Invalid unit definition. " + + "Unit " + unit + " in 'numerator' is not recognized"); + } + }); + + denominator.forEach(function(unit) { + if (UNITS[unit] === undefined) { + throw new QtyError(unitDef + ": Invalid unit definition. " + + "Unit " + unit + " in 'denominator' is not recognized"); + } + }); +} + +var PREFIX_VALUES = {}; +var PREFIX_MAP = {}; +var UNIT_VALUES = {}; +var UNIT_MAP = {}; +var OUTPUT_MAP = {}; +for (var unitDef in UNITS) { + if (UNITS.hasOwnProperty(unitDef)) { + var definition = UNITS[unitDef]; + if (definition[2] === "prefix") { + PREFIX_VALUES[unitDef] = definition[1]; + for (var i = 0; i < definition[0].length; i++) { + PREFIX_MAP[definition[0][i]] = unitDef; + } + } + else { + validateUnitDefinition(unitDef, definition); + UNIT_VALUES[unitDef] = { + scalar: definition[1], + numerator: definition[3], + denominator: definition[4] + }; + for (var j = 0; j < definition[0].length; j++) { + UNIT_MAP[definition[0][j]] = unitDef; + } + } + OUTPUT_MAP[unitDef] = definition[0][0]; + } +} + +/** + * Returns a list of available units of kind + * + * @param {string} [kind] + * @returns {array} names of units + * @throws {QtyError} if kind is unknown + */ +function getUnits (kind) { + var i; + var units = []; + var unitKeys = Object.keys(UNITS); + if (typeof kind === "undefined") { + for (i = 0; i < unitKeys.length; i++) { + if (["", "prefix"].indexOf(UNITS[unitKeys[i]][2]) === -1) { + units.push(unitKeys[i].substr(1, unitKeys[i].length - 2)); + } + } + } + else if (this.getKinds().indexOf(kind) === -1) { + throw new QtyError("Kind not recognized"); + } + else { + for (i = 0; i < unitKeys.length; i++) { + if (UNITS[unitKeys[i]][2] === kind) { + units.push(unitKeys[i].substr(1, unitKeys[i].length - 2)); + } + } + } + + return units.sort(function(a, b) { + if (a.toLowerCase() < b.toLowerCase()) { + return -1; + } + if (a.toLowerCase() > b.toLowerCase()) { + return 1; + } + return 0; + }); +} + +/** + * Returns a list of alternative names for a unit + * + * @param {string} unitName + * @returns {string[]} aliases for unit + * @throws {QtyError} if unit is unknown + */ +function getAliases(unitName) { + if (!UNIT_MAP[unitName]) { + throw new QtyError("Unit not recognized"); + } + return UNITS[UNIT_MAP[unitName]][0]; +} + +var SIGNATURE_VECTOR = ["length", "time", "temperature", "mass", "current", "substance", "luminosity", "currency", "information", "angle"]; + +/* +calculates the unit signature id for use in comparing compatible units and simplification +the signature is based on a simple classification of units and is based on the following publication + +Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering, +21(8), Aug 1995, pp.651-661 +doi://10.1109/32.403789 +http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr. +*/ +function unitSignature() { + if (this.signature) { + return this.signature; + } + var vector = unitSignatureVector.call(this); + for (var i = 0; i < vector.length; i++) { + vector[i] *= Math.pow(20, i); + } + + return vector.reduce( + function(previous, current) { + return previous + current; + }, + 0 + ); +} + +// calculates the unit signature vector used by unit_signature +function unitSignatureVector() { + if (!this.isBase()) { + return unitSignatureVector.call(this.toBase()); + } + + var vector = new Array(SIGNATURE_VECTOR.length); + for (var i = 0; i < vector.length; i++) { + vector[i] = 0; + } + var r, n; + for (var j = 0; j < this.numerator.length; j++) { + if ((r = UNITS[this.numerator[j]])) { + n = SIGNATURE_VECTOR.indexOf(r[2]); + if (n >= 0) { + vector[n] = vector[n] + 1; + } + } + } + + for (var k = 0; k < this.denominator.length; k++) { + if ((r = UNITS[this.denominator[k]])) { + n = SIGNATURE_VECTOR.indexOf(r[2]); + if (n >= 0) { + vector[n] = vector[n] - 1; + } + } + } + return vector; +} + +var SIGN = "[+-]"; +var INTEGER = "\\d+"; +var SIGNED_INTEGER = SIGN + "?" + INTEGER; +var FRACTION = "\\." + INTEGER; +var FLOAT = "(?:" + INTEGER + "(?:" + FRACTION + ")?" + ")" + + "|" + + "(?:" + FRACTION + ")"; +var EXPONENT = "[Ee]" + SIGNED_INTEGER; +var SCI_NUMBER = "(?:" + FLOAT + ")(?:" + EXPONENT + ")?"; +var SIGNED_NUMBER = SIGN + "?\\s*" + SCI_NUMBER; +var QTY_STRING = "(" + SIGNED_NUMBER + ")?" + "\\s*([^/]*)(?:\/(.+))?"; +var QTY_STRING_REGEX = new RegExp("^" + QTY_STRING + "$"); + +var POWER_OP = "\\^|\\*{2}"; +// Allow unit powers representing scalar, length, area, volume; 4 is for some +// special case representations in SI base units. +var SAFE_POWER = "[01234]"; +var TOP_REGEX = new RegExp ("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(-?" + SAFE_POWER + "(?![a-zA-Z]))"); +var BOTTOM_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(" + SAFE_POWER + "(?![a-zA-Z]))"); + +/* parse a string into a unit object. + * Typical formats like : + * "5.6 kg*m/s^2" + * "5.6 kg*m*s^-2" + * "5.6 kilogram*meter*second^-2" + * "2.2 kPa" + * "37 degC" + * "1" -- creates a unitless constant with value 1 + * "GPa" -- creates a unit with scalar 1 with units 'GPa' + * 6'4" -- recognized as 6 feet + 4 inches + * 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces + */ +function parse(val) { + if (!isString(val)) { + val = val.toString(); + } + val = val.trim(); + + var result = QTY_STRING_REGEX.exec(val); + if (!result) { + throw new QtyError(val + ": Quantity not recognized"); + } + + var scalarMatch = result[1]; + if (scalarMatch) { + // Allow whitespaces between sign and scalar for loose parsing + scalarMatch = scalarMatch.replace(/\s/g, ""); + this.scalar = parseFloat(scalarMatch); + } + else { + this.scalar = 1; + } + var top = result[2]; + var bottom = result[3]; + + var n, x, nx; + // TODO DRY me + while ((result = TOP_REGEX.exec(top))) { + n = parseFloat(result[2]); + if (isNaN(n)) { + // Prevents infinite loops + throw new QtyError("Unit exponent is not a number"); + } + // Disallow unrecognized unit even if exponent is 0 + if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) { + throw new QtyError("Unit not recognized"); + } + x = result[1] + " "; + nx = ""; + for (var i = 0; i < Math.abs(n) ; i++) { + nx += x; + } + if (n >= 0) { + top = top.replace(result[0], nx); + } + else { + bottom = bottom ? bottom + nx : nx; + top = top.replace(result[0], ""); + } + } + + while ((result = BOTTOM_REGEX.exec(bottom))) { + n = parseFloat(result[2]); + if (isNaN(n)) { + // Prevents infinite loops + throw new QtyError("Unit exponent is not a number"); + } + // Disallow unrecognized unit even if exponent is 0 + if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) { + throw new QtyError("Unit not recognized"); + } + x = result[1] + " "; + nx = ""; + for (var j = 0; j < n ; j++) { + nx += x; + } + + bottom = bottom.replace(result[0], nx); + } + + if (top) { + this.numerator = parseUnits(top.trim()); + } + if (bottom) { + this.denominator = parseUnits(bottom.trim()); + } +} + +var PREFIX_REGEX = Object.keys(PREFIX_MAP).sort(function(a, b) { + return b.length - a.length; +}).join("|"); +var UNIT_REGEX = Object.keys(UNIT_MAP).sort(function(a, b) { + return b.length - a.length; +}).join("|"); +/* + * Minimal boundary regex to support units with Unicode characters + * \b only works for ASCII + */ +var BOUNDARY_REGEX = "\\b|$"; +var UNIT_MATCH = "(" + PREFIX_REGEX + ")??(" + + UNIT_REGEX + + ")(?:" + BOUNDARY_REGEX + ")"; +var UNIT_TEST_REGEX = new RegExp("^\\s*(" + UNIT_MATCH + "[\\s\\*]*)+$"); +var UNIT_MATCH_REGEX = new RegExp(UNIT_MATCH, "g"); // g flag for multiple occurences +var parsedUnitsCache = {}; +/** + * Parses and converts units string to normalized unit array. + * Result is cached to speed up next calls. + * + * @param {string} units Units string + * @returns {string[]} Array of normalized units + * + * @example + * // Returns ["", "", ""] + * parseUnits("s m s"); + * + */ +function parseUnits(units) { + var cached = parsedUnitsCache[units]; + if (cached) { + return cached; + } + + var unitMatch, normalizedUnits = []; + + // Scan + if (!UNIT_TEST_REGEX.test(units)) { + throw new QtyError("Unit not recognized"); + } + + while ((unitMatch = UNIT_MATCH_REGEX.exec(units))) { + normalizedUnits.push(unitMatch.slice(1)); + } + + normalizedUnits = normalizedUnits.map(function(item) { + return PREFIX_MAP[item[0]] ? [PREFIX_MAP[item[0]], UNIT_MAP[item[1]]] : [UNIT_MAP[item[1]]]; + }); + + // Flatten and remove null elements + normalizedUnits = normalizedUnits.reduce(function(a,b) { + return a.concat(b); + }, []); + normalizedUnits = normalizedUnits.filter(function(item) { + return item; + }); + + parsedUnitsCache[units] = normalizedUnits; + + return normalizedUnits; +} + +/** + * Parses a string as a quantity + * @param {string} value - quantity as text + * @throws if value is not a string + * @returns {Qty|null} Parsed quantity or null if unrecognized + */ +function globalParse(value) { + if (!isString(value)) { + throw new QtyError("Argument should be a string"); + } + + try { + return this(value); + } + catch (e) { + return null; + } +} + +/** + * Tests if a value is a Qty instance + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a Qty instance, false otherwise + */ +function isQty(value) { + return value instanceof Qty; +} + +function Qty(initValue, initUnits) { + assertValidConstructorArgs.apply(null, arguments); + + if (!(isQty(this))) { + return new Qty(initValue, initUnits); + } + + this.scalar = null; + this.baseScalar = null; + this.signature = null; + this._conversionCache = {}; + this.numerator = UNITY_ARRAY; + this.denominator = UNITY_ARRAY; + + if (isDefinitionObject(initValue)) { + this.scalar = initValue.scalar; + this.numerator = (initValue.numerator && initValue.numerator.length !== 0) ? initValue.numerator : UNITY_ARRAY; + this.denominator = (initValue.denominator && initValue.denominator.length !== 0) ? initValue.denominator : UNITY_ARRAY; + } + else if (initUnits) { + parse.call(this, initUnits); + this.scalar = initValue; + } + else { + parse.call(this, initValue); + } + + // math with temperatures is very limited + if (this.denominator.join("*").indexOf("temp") >= 0) { + throw new QtyError("Cannot divide with temperatures"); + } + if (this.numerator.join("*").indexOf("temp") >= 0) { + if (this.numerator.length > 1) { + throw new QtyError("Cannot multiply by temperatures"); + } + if (!compareArray(this.denominator, UNITY_ARRAY)) { + throw new QtyError("Cannot divide with temperatures"); + } + } + + this.initValue = initValue; + updateBaseScalar.call(this); + + if (this.isTemperature() && this.baseScalar < 0) { + throw new QtyError("Temperatures must not be less than absolute zero"); + } +} + +Qty.prototype = { + // Properly set up constructor + constructor: Qty, +}; + +/** + * Asserts constructor arguments are valid + * + * @param {*} value - Value to test + * @param {string} [units] - Optional units when value is passed as a number + * + * @throws {QtyError} if constructor arguments are invalid + */ +function assertValidConstructorArgs(value, units) { + if (units) { + if (!(isNumber(value) && isString(units))) { + throw new QtyError("Only number accepted as initialization value " + + "when units are explicitly provided"); + } + } + else { + if (!(isString(value) || + isNumber(value) || + isQty(value) || + isDefinitionObject(value))) { + throw new QtyError("Only string, number or quantity accepted as " + + "single initialization value"); + } + } +} + +/** + * Tests if a value is a Qty definition object + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a definition object, false otherwise + */ +function isDefinitionObject(value) { + return value && typeof value === "object" && value.hasOwnProperty("scalar"); +} + +function updateBaseScalar() { + if (this.baseScalar) { + return this.baseScalar; + } + if (this.isBase()) { + this.baseScalar = this.scalar; + this.signature = unitSignature.call(this); + } + else { + var base = this.toBase(); + this.baseScalar = base.scalar; + this.signature = base.signature; + } +} + +var KINDS = { + "-312078": "elastance", + "-312058": "resistance", + "-312038": "inductance", + "-152058": "potential", + "-152040": "magnetism", + "-152038": "magnetism", + "-7997": "specific_volume", + "-79": "snap", + "-59": "jolt", + "-39": "acceleration", + "-38": "radiation", + "-20": "frequency", + "-19": "speed", + "-18": "viscosity", + "-17": "volumetric_flow", + "-1": "wavenumber", + "0": "unitless", + "1": "length", + "2": "area", + "3": "volume", + "20": "time", + "400": "temperature", + "7941": "yank", + "7942": "power", + "7959": "pressure", + "7961": "force", + "7962": "energy", + "7979": "viscosity", + "7981": "momentum", + "7982": "angular_momentum", + "7997": "density", + "7998": "area_density", + "8000": "mass", + "152020": "radiation_exposure", + "159999": "magnetism", + "160000": "current", + "160020": "charge", + "312058": "conductance", + "312078": "capacitance", + "3199980": "activity", + "3199997": "molar_concentration", + "3200000": "substance", + "63999998": "illuminance", + "64000000": "luminous_power", + "1280000000": "currency", + "25599999980": "information_rate", + "25600000000": "information", + "511999999980": "angular_velocity", + "512000000000": "angle" +}; + +/** + * Returns the list of available well-known kinds of units, e.g. + * "radiation" or "length". + * + * @returns {string[]} names of kinds of units + */ +function getKinds() { + return uniq(Object.keys(KINDS).map(function(knownSignature) { + return KINDS[knownSignature]; + })); +} + +Qty.prototype.kind = function() { + return KINDS[this.signature.toString()]; +}; + +assign(Qty.prototype, { + isDegrees: function() { + // signature may not have been calculated yet + return (this.signature === null || this.signature === 400) && + this.numerator.length === 1 && + compareArray(this.denominator, UNITY_ARRAY) && + (this.numerator[0].match(//) || this.numerator[0].match(/<(kelvin|celsius|rankine|fahrenheit)>/)); + }, + + isTemperature: function() { + return this.isDegrees() && this.numerator[0].match(//); + } +}); + +function subtractTemperatures(lhs,rhs) { + var lhsUnits = lhs.units(); + var rhsConverted = rhs.to(lhsUnits); + var dstDegrees = Qty(getDegreeUnits(lhsUnits)); + return Qty({"scalar": lhs.scalar - rhsConverted.scalar, "numerator": dstDegrees.numerator, "denominator": dstDegrees.denominator}); +} + +function subtractTempDegrees(temp,deg) { + var tempDegrees = deg.to(getDegreeUnits(temp.units())); + return Qty({"scalar": temp.scalar - tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator}); +} + +function addTempDegrees(temp,deg) { + var tempDegrees = deg.to(getDegreeUnits(temp.units())); + return Qty({"scalar": temp.scalar + tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator}); +} + +function getDegreeUnits(units) { + if (units === "tempK") { + return "degK"; + } + else if (units === "tempC") { + return "degC"; + } + else if (units === "tempF") { + return "degF"; + } + else if (units === "tempR") { + return "degR"; + } + else { + throw new QtyError("Unknown type for temp conversion from: " + units); + } +} + +function toDegrees(src,dst) { + var srcDegK = toDegK(src); + var dstUnits = dst.units(); + var dstScalar; + + if (dstUnits === "degK") { + dstScalar = srcDegK.scalar; + } + else if (dstUnits === "degC") { + dstScalar = srcDegK.scalar ; + } + else if (dstUnits === "degF") { + dstScalar = srcDegK.scalar * 9 / 5; + } + else if (dstUnits === "degR") { + dstScalar = srcDegK.scalar * 9 / 5; + } + else { + throw new QtyError("Unknown type for degree conversion to: " + dstUnits); + } + + return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator}); +} + +function toDegK(qty) { + var units = qty.units(); + var q; + if (units.match(/(deg)[CFRK]/)) { + q = qty.baseScalar; + } + else if (units === "tempK") { + q = qty.scalar; + } + else if (units === "tempC") { + q = qty.scalar; + } + else if (units === "tempF") { + q = qty.scalar * 5 / 9; + } + else if (units === "tempR") { + q = qty.scalar * 5 / 9; + } + else { + throw new QtyError("Unknown type for temp conversion from: " + units); + } + + return Qty({"scalar": q, "numerator": [""], "denominator": UNITY_ARRAY}); +} + +function toTemp(src,dst) { + var dstUnits = dst.units(); + var dstScalar; + + if (dstUnits === "tempK") { + dstScalar = src.baseScalar; + } + else if (dstUnits === "tempC") { + dstScalar = src.baseScalar - 273.15; + } + else if (dstUnits === "tempF") { + dstScalar = (src.baseScalar * 9 / 5) - 459.67; + } + else if (dstUnits === "tempR") { + dstScalar = src.baseScalar * 9 / 5; + } + else { + throw new QtyError("Unknown type for temp conversion to: " + dstUnits); + } + + return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator}); +} + +function toTempK(qty) { + var units = qty.units(); + var q; + if (units.match(/(deg)[CFRK]/)) { + q = qty.baseScalar; + } + else if (units === "tempK") { + q = qty.scalar; + } + else if (units === "tempC") { + q = qty.scalar + 273.15; + } + else if (units === "tempF") { + q = (qty.scalar + 459.67) * 5 / 9; + } + else if (units === "tempR") { + q = qty.scalar * 5 / 9; + } + else { + throw new QtyError("Unknown type for temp conversion from: " + units); + } + + return Qty({"scalar": q, "numerator": [""], "denominator": UNITY_ARRAY}); +} + +assign(Qty.prototype, { + /** + * Converts to other compatible units. + * Instance's converted quantities are cached for faster subsequent calls. + * + * @param {(string|Qty)} other - Target units as string or retrieved from + * other Qty instance (scalar is ignored) + * + * @returns {Qty} New converted Qty instance with target units + * + * @throws {QtyError} if target units are incompatible + * + * @example + * var weight = Qty("25 kg"); + * weight.to("lb"); // => Qty("55.11556554621939 lbs"); + * weight.to(Qty("3 g")); // => Qty("25000 g"); // scalar of passed Qty is ignored + */ + to: function(other) { + var cached, target; + + if (other === undefined || other === null) { + return this; + } + + if (!isString(other)) { + return this.to(other.units()); + } + + cached = this._conversionCache[other]; + if (cached) { + return cached; + } + + // Instantiating target to normalize units + target = Qty(other); + if (target.units() === this.units()) { + return this; + } + + if (!this.isCompatible(target)) { + if (this.isInverse(target)) { + target = this.inverse().to(other); + } + else { + throwIncompatibleUnits(this.units(), target.units()); + } + } + else { + if (target.isTemperature()) { + target = toTemp(this,target); + } + else if (target.isDegrees()) { + target = toDegrees(this,target); + } + else { + var q = divSafe(this.baseScalar, target.baseScalar); + target = Qty({"scalar": q, "numerator": target.numerator, "denominator": target.denominator}); + } + } + + this._conversionCache[other] = target; + return target; + }, + + // convert to base SI units + // results of the conversion are cached so subsequent calls to this will be fast + toBase: function() { + if (this.isBase()) { + return this; + } + + if (this.isTemperature()) { + return toTempK(this); + } + + var cached = baseUnitCache[this.units()]; + if (!cached) { + cached = toBaseUnits(this.numerator,this.denominator); + baseUnitCache[this.units()] = cached; + } + return cached.mul(this.scalar); + }, + + // Converts the unit back to a float if it is unitless. Otherwise raises an exception + toFloat: function() { + if (this.isUnitless()) { + return this.scalar; + } + throw new QtyError("Can't convert to Float unless unitless. Use Unit#scalar"); + }, + + /** + * Returns the nearest multiple of quantity passed as + * precision + * + * @param {(Qty|string|number)} precQuantity - Quantity, string formated + * quantity or number as expected precision + * + * @returns {Qty} Nearest multiple of precQuantity + * + * @example + * Qty('5.5 ft').toPrec('2 ft'); // returns 6 ft + * Qty('0.8 cu').toPrec('0.25 cu'); // returns 0.75 cu + * Qty('6.3782 m').toPrec('cm'); // returns 6.38 m + * Qty('1.146 MPa').toPrec('0.1 bar'); // returns 1.15 MPa + * + */ + toPrec: function(precQuantity) { + if (isString(precQuantity)) { + precQuantity = Qty(precQuantity); + } + if (isNumber(precQuantity)) { + precQuantity = Qty(precQuantity + " " + this.units()); + } + + if (!this.isUnitless()) { + precQuantity = precQuantity.to(this.units()); + } + else if (!precQuantity.isUnitless()) { + throwIncompatibleUnits(this.units(), precQuantity.units()); + } + + if (precQuantity.scalar === 0) { + throw new QtyError("Divide by zero"); + } + + var precRoundedResult = mulSafe(Math.round(this.scalar / precQuantity.scalar), + precQuantity.scalar); + + return Qty(precRoundedResult + this.units()); + } +}); + +/** + * Configures and returns a fast function to convert + * Number values from units to others. + * Useful to efficiently convert large array of values + * with same units into others with iterative methods. + * Does not take care of rounding issues. + * + * @param {string} srcUnits Units of values to convert + * @param {string} dstUnits Units to convert to + * + * @returns {Function} Converting function accepting Number value + * and returning converted value + * + * @throws "Incompatible units" if units are incompatible + * + * @example + * // Converting large array of numbers with the same units + * // into other units + * var converter = Qty.swiftConverter("m/h", "ft/s"); + * var convertedSerie = largeSerie.map(converter); + * + */ +function swiftConverter(srcUnits, dstUnits) { + var srcQty = Qty(srcUnits); + var dstQty = Qty(dstUnits); + + if (srcQty.eq(dstQty)) { + return identity; + } + + var convert; + if (!srcQty.isTemperature()) { + convert = function(value) { + return value * srcQty.baseScalar / dstQty.baseScalar; + }; + } + else { + convert = function(value) { + // TODO Not optimized + return srcQty.mul(value).to(dstQty).scalar; + }; + } + + return function converter(value) { + var i, + length, + result; + if (!Array.isArray(value)) { + return convert(value); + } + else { + length = value.length; + result = []; + for (i = 0; i < length; i++) { + result.push(convert(value[i])); + } + return result; + } + }; +} + +var baseUnitCache = {}; + +function toBaseUnits (numerator,denominator) { + var num = []; + var den = []; + var q = 1; + var unit; + for (var i = 0; i < numerator.length; i++) { + unit = numerator[i]; + if (PREFIX_VALUES[unit]) { + // workaround to fix + // 0.1 * 0.1 => 0.010000000000000002 + q = mulSafe(q, PREFIX_VALUES[unit]); + } + else { + if (UNIT_VALUES[unit]) { + q *= UNIT_VALUES[unit].scalar; + + if (UNIT_VALUES[unit].numerator) { + num.push(UNIT_VALUES[unit].numerator); + } + if (UNIT_VALUES[unit].denominator) { + den.push(UNIT_VALUES[unit].denominator); + } + } + } + } + for (var j = 0; j < denominator.length; j++) { + unit = denominator[j]; + if (PREFIX_VALUES[unit]) { + q /= PREFIX_VALUES[unit]; + } + else { + if (UNIT_VALUES[unit]) { + q /= UNIT_VALUES[unit].scalar; + + if (UNIT_VALUES[unit].numerator) { + den.push(UNIT_VALUES[unit].numerator); + } + if (UNIT_VALUES[unit].denominator) { + num.push(UNIT_VALUES[unit].denominator); + } + } + } + } + + // Flatten + num = num.reduce(function(a,b) { + return a.concat(b); + }, []); + den = den.reduce(function(a,b) { + return a.concat(b); + }, []); + + return Qty({"scalar": q, "numerator": num, "denominator": den}); +} + +Qty.parse = globalParse; + +Qty.getUnits = getUnits; +Qty.getAliases = getAliases; + +Qty.mulSafe = mulSafe; +Qty.divSafe = divSafe; + +Qty.getKinds = getKinds; + +Qty.swiftConverter = swiftConverter; + +Qty.Error = QtyError; + +assign(Qty.prototype, { + // Returns new instance with units of this + add: function(other) { + if (isString(other)) { + other = Qty(other); + } + + if (!this.isCompatible(other)) { + throwIncompatibleUnits(this.units(), other.units()); + } + + if (this.isTemperature() && other.isTemperature()) { + throw new QtyError("Cannot add two temperatures"); + } + else if (this.isTemperature()) { + return addTempDegrees(this, other); + } + else if (other.isTemperature()) { + return addTempDegrees(other, this); + } + + return Qty({"scalar": this.scalar + other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator}); + }, + + sub: function(other) { + if (isString(other)) { + other = Qty(other); + } + + if (!this.isCompatible(other)) { + throwIncompatibleUnits(this.units(), other.units()); + } + + if (this.isTemperature() && other.isTemperature()) { + return subtractTemperatures(this,other); + } + else if (this.isTemperature()) { + return subtractTempDegrees(this,other); + } + else if (other.isTemperature()) { + throw new QtyError("Cannot subtract a temperature from a differential degree unit"); + } + + return Qty({"scalar": this.scalar - other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator}); + }, + + mul: function(other) { + if (isNumber(other)) { + return Qty({"scalar": mulSafe(this.scalar, other), "numerator": this.numerator, "denominator": this.denominator}); + } + else if (isString(other)) { + other = Qty(other); + } + + if ((this.isTemperature()||other.isTemperature()) && !(this.isUnitless()||other.isUnitless())) { + throw new QtyError("Cannot multiply by temperatures"); + } + + // Quantities should be multiplied with same units if compatible, with base units else + var op1 = this; + var op2 = other; + + // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den + // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC" + if (op1.isCompatible(op2) && op1.signature !== 400) { + op2 = op2.to(op1); + } + var numden = cleanTerms(op1.numerator.concat(op2.numerator), op1.denominator.concat(op2.denominator)); + + return Qty({"scalar": mulSafe(op1.scalar, op2.scalar), "numerator": numden[0], "denominator": numden[1]}); + }, + + div: function(other) { + if (isNumber(other)) { + if (other === 0) { + throw new QtyError("Divide by zero"); + } + return Qty({"scalar": this.scalar / other, "numerator": this.numerator, "denominator": this.denominator}); + } + else if (isString(other)) { + other = Qty(other); + } + + if (other.scalar === 0) { + throw new QtyError("Divide by zero"); + } + + if (other.isTemperature()) { + throw new QtyError("Cannot divide with temperatures"); + } + else if (this.isTemperature() && !other.isUnitless()) { + throw new QtyError("Cannot divide with temperatures"); + } + + // Quantities should be multiplied with same units if compatible, with base units else + var op1 = this; + var op2 = other; + + // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den + // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC" + if (op1.isCompatible(op2) && op1.signature !== 400) { + op2 = op2.to(op1); + } + var numden = cleanTerms(op1.numerator.concat(op2.denominator), op1.denominator.concat(op2.numerator)); + + return Qty({"scalar": op1.scalar / op2.scalar, "numerator": numden[0], "denominator": numden[1]}); + }, + + // Returns a Qty that is the inverse of this Qty, + inverse: function() { + if (this.isTemperature()) { + throw new QtyError("Cannot divide with temperatures"); + } + if (this.scalar === 0) { + throw new QtyError("Divide by zero"); + } + return Qty({"scalar": 1 / this.scalar, "numerator": this.denominator, "denominator": this.numerator}); + } +}); + +function cleanTerms(num, den) { + num = num.filter(function(val) { + return val !== UNITY; + }); + den = den.filter(function(val) { + return val !== UNITY; + }); + + var combined = {}; + + var k; + for (var i = 0; i < num.length; i++) { + if (PREFIX_VALUES[num[i]]) { + k = [num[i], num[i + 1]]; + i++; + } + else { + k = num[i]; + } + if (k && k !== UNITY) { + if (combined[k]) { + combined[k][0]++; + } + else { + combined[k] = [1, k]; + } + } + } + + for (var j = 0; j < den.length; j++) { + if (PREFIX_VALUES[den[j]]) { + k = [den[j], den[j + 1]]; + j++; + } + else { + k = den[j]; + } + if (k && k !== UNITY) { + if (combined[k]) { + combined[k][0]--; + } + else { + combined[k] = [-1, k]; + } + } + } + + num = []; + den = []; + + for (var prop in combined) { + if (combined.hasOwnProperty(prop)) { + var item = combined[prop]; + var n; + if (item[0] > 0) { + for (n = 0; n < item[0]; n++) { + num.push(item[1]); + } + } + else if (item[0] < 0) { + for (n = 0; n < -item[0]; n++) { + den.push(item[1]); + } + } + } + } + + if (num.length === 0) { + num = UNITY_ARRAY; + } + if (den.length === 0) { + den = UNITY_ARRAY; + } + + // Flatten + num = num.reduce(function(a,b) { + return a.concat(b); + }, []); + den = den.reduce(function(a,b) { + return a.concat(b); + }, []); + + return [num, den]; +} + +assign(Qty.prototype, { + eq: function(other) { + return this.compareTo(other) === 0; + }, + + lt: function(other) { + return this.compareTo(other) === -1; + }, + + lte: function(other) { + return this.eq(other) || this.lt(other); + }, + + gt: function(other) { + return this.compareTo(other) === 1; + }, + + gte: function(other) { + return this.eq(other) || this.gt(other); + }, + + // Compare two Qty objects. Throws an exception if they are not of compatible types. + // Comparisons are done based on the value of the quantity in base SI units. + // + // NOTE: We cannot compare inverses as that breaks the general compareTo contract: + // if a.compareTo(b) < 0 then b.compareTo(a) > 0 + // if a.compareTo(b) == 0 then b.compareTo(a) == 0 + // + // Since "10S" == ".1ohm" (10 > .1) and "10ohm" == ".1S" (10 > .1) + // Qty("10S").inverse().compareTo("10ohm") == -1 + // Qty("10ohm").inverse().compareTo("10S") == -1 + // + // If including inverses in the sort is needed, I suggest writing: Qty.sort(qtyArray,units) + compareTo: function(other) { + if (isString(other)) { + return this.compareTo(Qty(other)); + } + if (!this.isCompatible(other)) { + throwIncompatibleUnits(this.units(), other.units()); + } + if (this.baseScalar < other.baseScalar) { + return -1; + } + else if (this.baseScalar === other.baseScalar) { + return 0; + } + else if (this.baseScalar > other.baseScalar) { + return 1; + } + }, + + // Return true if quantities and units match + // Unit("100 cm").same(Unit("100 cm")) # => true + // Unit("100 cm").same(Unit("1 m")) # => false + same: function(other) { + return (this.scalar === other.scalar) && (this.units() === other.units()); + } +}); + +assign(Qty.prototype, { + // returns true if no associated units + // false, even if the units are "unitless" like 'radians, each, etc' + isUnitless: function() { + return [this.numerator, this.denominator].every(function(item) { + return compareArray(item, UNITY_ARRAY); + }); + }, + + /* + check to see if units are compatible, but not the scalar part + this check is done by comparing signatures for performance reasons + if passed a string, it will create a unit object with the string and then do the comparison + this permits a syntax like: + unit =~ "mm" + if you want to do a regexp on the unit string do this ... + unit.units =~ /regexp/ + */ + isCompatible: function(other) { + if (isString(other)) { + return this.isCompatible(Qty(other)); + } + + if (!(isQty(other))) { + return false; + } + + if (other.signature !== undefined) { + return this.signature === other.signature; + } + else { + return false; + } + }, + + /* + check to see if units are inverse of each other, but not the scalar part + this check is done by comparing signatures for performance reasons + if passed a string, it will create a unit object with the string and then do the comparison + this permits a syntax like: + unit =~ "mm" + if you want to do a regexp on the unit string do this ... + unit.units =~ /regexp/ + */ + isInverse: function(other) { + return this.inverse().isCompatible(other); + }, + + // Returns 'true' if the Unit is represented in base units + isBase: function() { + if (this._isBase !== undefined) { + return this._isBase; + } + if (this.isDegrees() && this.numerator[0].match(/<(kelvin|temp-K)>/)) { + this._isBase = true; + return this._isBase; + } + + this.numerator.concat(this.denominator).forEach(function(item) { + if (item !== UNITY && BASE_UNITS.indexOf(item) === -1 ) { + this._isBase = false; + } + }, this); + if (this._isBase === false) { + return this._isBase; + } + this._isBase = true; + return this._isBase; + } +}); + +function NestedMap() {} + +NestedMap.prototype.get = function(keys) { + + // Allows to pass key1, key2, ... instead of [key1, key2, ...] + if (arguments.length > 1) { + // Slower with Firefox but faster with Chrome than + // Array.prototype.slice.call(arguments) + // See http://jsperf.com/array-apply-versus-array-prototype-slice-call + keys = Array.apply(null, arguments); + } + + return keys.reduce(function(map, key, index) { + if (map) { + + var childMap = map[key]; + + if (index === keys.length - 1) { + return childMap ? childMap.data : undefined; + } + else { + return childMap; + } + } + }, + this); +}; + +NestedMap.prototype.set = function(keys, value) { + + if (arguments.length > 2) { + keys = Array.prototype.slice.call(arguments, 0, -1); + value = arguments[arguments.length - 1]; + } + + return keys.reduce(function(map, key, index) { + + var childMap = map[key]; + if (childMap === undefined) { + childMap = map[key] = {}; + } + + if (index === keys.length - 1) { + childMap.data = value; + return value; + } + else { + return childMap; + } + }, this); +}; + +/** + * Default formatter + * + * @param {number} scalar + * @param {string} units + * + * @returns {string} formatted result + */ +function defaultFormatter(scalar, units) { + return (scalar + " " + units).trim(); +} + +/** + * + * Configurable Qty default formatter + * + * @type {function} + * + * @param {number} scalar + * @param {string} units + * + * @returns {string} formatted result + */ +Qty.formatter = defaultFormatter; + +assign(Qty.prototype, { + + // returns the 'unit' part of the Unit object without the scalar + units: function() { + if (this._units !== undefined) { + return this._units; + } + + var numIsUnity = compareArray(this.numerator, UNITY_ARRAY), + denIsUnity = compareArray(this.denominator, UNITY_ARRAY); + if (numIsUnity && denIsUnity) { + this._units = ""; + return this._units; + } + + var numUnits = stringifyUnits(this.numerator), + denUnits = stringifyUnits(this.denominator); + this._units = numUnits + (denIsUnity ? "" : ("/" + denUnits)); + return this._units; + }, + + /** + * Stringifies the quantity + * Deprecation notice: only units parameter is supported. + * + * @param {(number|string|Qty)} targetUnitsOrMaxDecimalsOrPrec - + * target units if string, + * max number of decimals if number, + * passed to #toPrec before converting if Qty + * + * @param {number=} maxDecimals - Maximum number of decimals of + * formatted output + * + * @returns {string} reparseable quantity as string + */ + toString: function(targetUnitsOrMaxDecimalsOrPrec, maxDecimals) { + var targetUnits; + if (isNumber(targetUnitsOrMaxDecimalsOrPrec)) { + targetUnits = this.units(); + maxDecimals = targetUnitsOrMaxDecimalsOrPrec; + } + else if (isString(targetUnitsOrMaxDecimalsOrPrec)) { + targetUnits = targetUnitsOrMaxDecimalsOrPrec; + } + else if (isQty(targetUnitsOrMaxDecimalsOrPrec)) { + return this.toPrec(targetUnitsOrMaxDecimalsOrPrec).toString(maxDecimals); + } + + var out = this.to(targetUnits); + + var outScalar = maxDecimals !== undefined ? round(out.scalar, maxDecimals) : out.scalar; + out = (outScalar + " " + out.units()).trim(); + return out; + }, + + /** + * Format the quantity according to optional passed target units + * and formatter + * + * @param {string} [targetUnits=current units] - + * optional units to convert to before formatting + * + * @param {function} [formatter=Qty.formatter] - + * delegates formatting to formatter callback. + * formatter is called back with two parameters (scalar, units) + * and should return formatted result. + * If unspecified, formatting is delegated to default formatter + * set to Qty.formatter + * + * @example + * var roundingAndLocalizingFormatter = function(scalar, units) { + * // localize or limit scalar to n max decimals for instance + * // return formatted result + * }; + * var qty = Qty('1.1234 m'); + * qty.format(); // same units, default formatter => "1.234 m" + * qty.format("cm"); // converted to "cm", default formatter => "123.45 cm" + * qty.format(roundingAndLocalizingFormatter); // same units, custom formatter => "1,2 m" + * qty.format("cm", roundingAndLocalizingFormatter); // convert to "cm", custom formatter => "123,4 cm" + * + * @returns {string} quantity as string + */ + format: function(targetUnits, formatter) { + if (arguments.length === 1) { + if (typeof targetUnits === "function") { + formatter = targetUnits; + targetUnits = undefined; + } + } + + formatter = formatter || Qty.formatter; + var targetQty = this.to(targetUnits); + return formatter.call(this, targetQty.scalar, targetQty.units()); + } +}); + +var stringifiedUnitsCache = new NestedMap(); +/** + * Returns a string representing a normalized unit array + * + * @param {string[]} units Normalized unit array + * @returns {string} String representing passed normalized unit array and + * suitable for output + * + */ +function stringifyUnits(units) { + + var stringified = stringifiedUnitsCache.get(units); + if (stringified) { + return stringified; + } + + var isUnity = compareArray(units, UNITY_ARRAY); + if (isUnity) { + stringified = "1"; + } + else { + stringified = simplify(getOutputNames(units)).join("*"); + } + + // Cache result + stringifiedUnitsCache.set(units, stringified); + + return stringified; +} + +function getOutputNames(units) { + var unitNames = [], token, tokenNext; + for (var i = 0; i < units.length; i++) { + token = units[i]; + tokenNext = units[i + 1]; + if (PREFIX_VALUES[token]) { + unitNames.push(OUTPUT_MAP[token] + OUTPUT_MAP[tokenNext]); + i++; + } + else { + unitNames.push(OUTPUT_MAP[token]); + } + } + return unitNames; +} + +function simplify (units) { + // this turns ['s','m','s'] into ['s2','m'] + + var unitCounts = units.reduce(function(acc, unit) { + var unitCounter = acc[unit]; + if (!unitCounter) { + acc.push(unitCounter = acc[unit] = [unit, 0]); + } + + unitCounter[1]++; + + return acc; + }, []); + + return unitCounts.map(function(unitCount) { + return unitCount[0] + (unitCount[1] > 1 ? unitCount[1] : ""); + }); +} + +Qty.version = "1.7.1"; + +export default Qty; diff --git a/build/quantities.js b/build/quantities.js index b796c1d..ffd3d0c 100644 --- a/build/quantities.js +++ b/build/quantities.js @@ -22,1982 +22,1982 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ (function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.Qty = factory()); + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.Qty = factory()); }(this, (function () { 'use strict'; -/** - * Tests if a value is a Qty instance - * - * @param {*} value - Value to test - * - * @returns {boolean} true if value is a Qty instance, false otherwise - */ -function isQty(value) { - return value instanceof Qty$1; -} - -/** - * Tests if a value is a string - * - * @param {*} value - Value to test - * - * @returns {boolean} true if value is a string, false otherwise - */ -function isString(value) { - return typeof value === "string" || value instanceof String; -} + /** + * Tests if a value is a string + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a string, false otherwise + */ + function isString(value) { + return typeof value === "string" || value instanceof String; + } -/* - * Prefer stricter Number.isFinite if currently supported. - * To be dropped when ES6 is finalized. Obsolete browsers will - * have to use ES6 polyfills. - */ -var isFiniteImpl = Number.isFinite || window.isFinite; -/** - * Tests if a value is a number - * - * @param {*} value - Value to test - * - * @returns {boolean} true if value is a number, false otherwise - */ -function isNumber(value) { - // Number.isFinite allows not to consider NaN or '1' as numbers - return isFiniteImpl(value); -} + /* + * Prefer stricter Number.isFinite if currently supported. + * To be dropped when ES6 is finalized. Obsolete browsers will + * have to use ES6 polyfills. + */ + var isFiniteImpl = Number.isFinite || window.isFinite; + /** + * Tests if a value is a number + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a number, false otherwise + */ + function isNumber(value) { + // Number.isFinite allows not to consider NaN or '1' as numbers + return isFiniteImpl(value); + } -/* - * Identity function - */ -function identity(value) { - return value; -} - -/** - * Returns unique strings from list - * - * @param {string[]} strings - array of strings - * - * - * @returns {string[]} a new array of strings without duplicates - */ -function uniq(strings) { - var seen = {}; - return strings.filter(function(item) { - return seen.hasOwnProperty(item) ? false : (seen[item] = true); - }); -} + /* + * Identity function + */ + function identity(value) { + return value; + } -function compareArray(array1, array2) { - if (array2.length !== array1.length) { - return false; + /** + * Returns unique strings from list + * + * @param {string[]} strings - array of strings + * + * + * @returns {string[]} a new array of strings without duplicates + */ + function uniq(strings) { + var seen = {}; + return strings.filter(function(item) { + return seen.hasOwnProperty(item) ? false : (seen[item] = true); + }); } - for (var i = 0; i < array1.length; i++) { - if (array2[i].compareArray) { - if (!array2[i].compareArray(array1[i])) { + + function compareArray(array1, array2) { + if (array2.length !== array1.length) { + return false; + } + for (var i = 0; i < array1.length; i++) { + if (array2[i].compareArray) { + if (!array2[i].compareArray(array1[i])) { + return false; + } + } + if (array2[i] !== array1[i]) { return false; } } - if (array2[i] !== array1[i]) { - return false; - } + return true; } - return true; -} -function assign(target, properties) { - Object.keys(properties).forEach(function(key) { - target[key] = properties[key]; - }); -} - -/** - * Safely multiplies numbers while avoiding floating errors - * like 0.1 * 0.1 => 0.010000000000000002 - * - * @returns {number} result - * @param {...number} number - */ -function mulSafe() { - var result = 1, decimals = 0; - for (var i = 0; i < arguments.length; i++) { - var arg = arguments[i]; - decimals = decimals + getFractional(arg); - result *= arg; + function assign(target, properties) { + Object.keys(properties).forEach(function(key) { + target[key] = properties[key]; + }); } - return decimals !== 0 ? round(result, decimals) : result; -} - -/** - * Safely divides two numbers while avoiding floating errors - * like 0.3 / 0.05 => 5.999999999999999 - * - * @returns {number} result - * @param {number} num Numerator - * @param {number} den Denominator - */ -function divSafe(num, den) { - if (den === 0) { - throw new Error("Divide by zero"); - } + /** + * Safely multiplies numbers while avoiding floating errors + * like 0.1 * 0.1 => 0.010000000000000002 + * + * @returns {number} result + * @param {...number} number + */ + function mulSafe() { + var result = 1, decimals = 0; + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + decimals = decimals + getFractional(arg); + result *= arg; + } - var factor = Math.pow(10, getFractional(den)); - var invDen = factor / (factor * den); - - return mulSafe(num, invDen); -} - -/** - * Rounds value at the specified number of decimals - * - * @param {number} val - value to round - * @param {number} decimals - number of decimals - * - * @returns {number} rounded number - */ -function round(val, decimals) { - return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals); -} - -function getFractional(num) { - // Check for NaNs or Infinities - if (!isFinite(num)) { - return 0; + return decimals !== 0 ? round(result, decimals) : result; } - // Faster than parsing strings - // http://jsperf.com/count-decimals/2 - var count = 0; - while (num % 1 !== 0) { - num *= 10; - count++; + /** + * Safely divides two numbers while avoiding floating errors + * like 0.3 / 0.05 => 5.999999999999999 + * + * @returns {number} result + * @param {number} num Numerator + * @param {number} den Denominator + */ + function divSafe(num, den) { + if (den === 0) { + throw new Error("Divide by zero"); + } + + var factor = Math.pow(10, getFractional(den)); + var invDen = factor / (factor * den); + + return mulSafe(num, invDen); } - return count; -} - -/** - * Custom error type definition - * @constructor - */ -function QtyError() { - var err; - if (!this) { // Allows to instantiate QtyError without new() - err = Object.create(QtyError.prototype); - QtyError.apply(err, arguments); - return err; + + /** + * Rounds value at the specified number of decimals + * + * @param {number} val - value to round + * @param {number} decimals - number of decimals + * + * @returns {number} rounded number + */ + function round(val, decimals) { + return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals); } - err = Error.apply(this, arguments); - this.name = "QtyError"; - this.message = err.message; - this.stack = err.stack; -} -QtyError.prototype = Object.create(Error.prototype, {constructor: { value: QtyError }}); -/* - * Throws incompatible units error - * @param {string} left - units - * @param {string} right - units incompatible with first argument - * @throws "Incompatible units" error - */ -function throwIncompatibleUnits(left, right) { - throw new QtyError("Incompatible units: " + left + " and " + right); -} - -var UNITS = { - /* prefixes */ - "" : [["googol"], 1e100, "prefix"], - "" : [["Ki","Kibi","kibi"], Math.pow(2,10), "prefix"], - "" : [["Mi","Mebi","mebi"], Math.pow(2,20), "prefix"], - "" : [["Gi","Gibi","gibi"], Math.pow(2,30), "prefix"], - "" : [["Ti","Tebi","tebi"], Math.pow(2,40), "prefix"], - "" : [["Pi","Pebi","pebi"], Math.pow(2,50), "prefix"], - "" : [["Ei","Exi","exi"], Math.pow(2,60), "prefix"], - "" : [["Zi","Zebi","zebi"], Math.pow(2,70), "prefix"], - "" : [["Yi","Yebi","yebi"], Math.pow(2,80), "prefix"], - "" : [["Y","Yotta","yotta"], 1e24, "prefix"], - "" : [["Z","Zetta","zetta"], 1e21, "prefix"], - "" : [["E","Exa","exa"], 1e18, "prefix"], - "" : [["P","Peta","peta"], 1e15, "prefix"], - "" : [["T","Tera","tera"], 1e12, "prefix"], - "" : [["G","Giga","giga"], 1e9, "prefix"], - "" : [["M","Mega","mega"], 1e6, "prefix"], - "" : [["k","kilo"], 1e3, "prefix"], - "" : [["h","Hecto","hecto"], 1e2, "prefix"], - "" : [["da","Deca","deca","deka"], 1e1, "prefix"], - "" : [["d","Deci","deci"], 1e-1, "prefix"], - "" : [["c","Centi","centi"], 1e-2, "prefix"], - "" : [["m","Milli","milli"], 1e-3, "prefix"], - "" : [ - ["u","\u03BC"/*µ as greek letter*/,"\u00B5"/*µ as micro sign*/,"Micro","mc","micro"], - 1e-6, - "prefix" - ], - "" : [["n","Nano","nano"], 1e-9, "prefix"], - "" : [["p","Pico","pico"], 1e-12, "prefix"], - "" : [["f","Femto","femto"], 1e-15, "prefix"], - "" : [["a","Atto","atto"], 1e-18, "prefix"], - "" : [["z","Zepto","zepto"], 1e-21, "prefix"], - "" : [["y","Yocto","yocto"], 1e-24, "prefix"], - - "<1>" : [["1", "<1>"], 1, ""], - /* length units */ - "" : [["m","meter","meters","metre","metres"], 1.0, "length", [""] ], - "" : [["in","inch","inches","\""], 0.0254, "length", [""]], - "" : [["ft","foot","feet","'"], 0.3048, "length", [""]], - "" : [["yd","yard","yards"], 0.9144, "length", [""]], - "" : [["mi","mile","miles"], 1609.344, "length", [""]], - "" : [["nmi"], 1852, "length", [""]], - "": [["league","leagues"], 4828, "length", [""]], - "": [["furlong","furlongs"], 201.2, "length", [""]], - "" : [["rd","rod","rods"], 5.029, "length", [""]], - "" : [["mil","mils"], 0.0000254, "length", [""]], - "" :[["ang","angstrom","angstroms"], 1e-10, "length", [""]], - "" : [["fathom","fathoms"], 1.829, "length", [""]], - "" : [["pica","picas"], 0.00423333333, "length", [""]], - "" : [["pt","point","points"], 0.000352777778, "length", [""]], - "" : [["z","red-shift", "redshift"], 1.302773e26, "length", [""]], - "" : [["AU","astronomical-unit"], 149597900000, "length", [""]], - "":[["ls","light-second"], 299792500, "length", [""]], - "":[["lmin","light-minute"], 17987550000, "length", [""]], - "" : [["ly","light-year"], 9460528000000000, "length", [""]], - "" : [["pc","parsec","parsecs"], 30856780000000000, "length", [""]], - "" : [["DM"], 1828.8, "length", [""]], - - /* mass */ - "" : [["kg","kilogram","kilograms"], 1.0, "mass", [""]], - "" : [["u","AMU","amu"], 1.660538921e-27, "mass", [""]], - "" : [["Da","Dalton","Daltons","dalton","daltons"], 1.660538921e-27, "mass", [""]], - "" : [["slug","slugs"], 14.5939029, "mass", [""]], - "" : [["tn","ton"], 907.18474, "mass", [""]], - "":[["tonne"], 1000, "mass", [""]], - "" : [["ct","carat","carats"], 0.0002, "mass", [""]], - "" : [["lbs","lb","pound","pounds","#"], 0.45359237, "mass", [""]], - "" : [["oz","ounce","ounces"], 0.0283495231, "mass", [""]], - "" : [["g","gram","grams","gramme","grammes"], 1e-3, "mass", [""]], - "" : [["grain","grains","gr"], 6.479891e-5, "mass", [""]], - "" : [["dram","drams","dr"], 0.0017718452, "mass",[""]], - "" : [["stone","stones","st"],6.35029318, "mass",[""]], - - /* area */ - "":[["hectare"], 10000, "area", ["",""]], - "":[["acre","acres"], 4046.85642, "area", ["",""]], - "":[["sqft"], 1, "area", ["",""]], - - /* volume */ - "" : [["l","L","liter","liters","litre","litres"], 0.001, "volume", ["","",""]], - "": [["gal","gallon","gallons"], 0.0037854118, "volume", ["","",""]], - "": [["qt","quart","quarts"], 0.00094635295, "volume", ["","",""]], - "": [["pt","pint","pints"], 0.000473176475, "volume", ["","",""]], - "": [["cu","cup","cups"], 0.000236588238, "volume", ["","",""]], - "": [["floz","fluid-ounce","fluid-ounces"], 2.95735297e-5, "volume", ["","",""]], - "": [["tb","tbsp","tbs","tablespoon","tablespoons"], 1.47867648e-5, "volume", ["","",""]], - "": [["tsp","teaspoon","teaspoons"], 4.92892161e-6, "volume", ["","",""]], - "": [["bu","bsh","bushel","bushels"], 0.035239072, "volume", ["","",""]], - - /* speed */ - "" : [["kph"], 0.277777778, "speed", [""], [""]], - "" : [["mph"], 0.44704, "speed", [""], [""]], - "" : [["kt","kn","kts","knot","knots"], 0.514444444, "speed", [""], [""]], - "" : [["fps"], 0.3048, "speed", [""], [""]], - - /* acceleration */ - "" : [["gee"], 9.80665, "acceleration", [""], ["",""]], - - /* temperature_difference */ - "" : [["degK","kelvin"], 1.0, "temperature", [""]], - "" : [["degC","celsius","celsius","centigrade"], 1.0, "temperature", [""]], - "" : [["degF","fahrenheit"], 5 / 9, "temperature", [""]], - "" : [["degR","rankine"], 5 / 9, "temperature", [""]], - "" : [["tempK"], 1.0, "temperature", [""]], - "" : [["tempC"], 1.0, "temperature", [""]], - "" : [["tempF"], 5 / 9, "temperature", [""]], - "" : [["tempR"], 5 / 9, "temperature", [""]], - - /* time */ - "": [["s","sec","secs","second","seconds"], 1.0, "time", [""]], - "": [["min","mins","minute","minutes"], 60.0, "time", [""]], - "": [["h","hr","hrs","hour","hours"], 3600.0, "time", [""]], - "": [["d","day","days"], 3600 * 24, "time", [""]], - "": [["wk","week","weeks"], 7 * 3600 * 24, "time", [""]], - "": [["fortnight","fortnights"], 1209600, "time", [""]], - "": [["y","yr","year","years","annum"], 31556926, "time", [""]], - "":[["decade","decades"], 315569260, "time", [""]], - "":[["century","centuries"], 3155692600, "time", [""]], - - /* pressure */ - "" : [["Pa","pascal","Pascal"], 1.0, "pressure", [""],["","",""]], - "" : [["bar","bars"], 100000, "pressure", [""],["","",""]], - "" : [["mmHg"], 133.322368, "pressure", [""],["","",""]], - "" : [["inHg"], 3386.3881472, "pressure", [""],["","",""]], - "" : [["torr"], 133.322368, "pressure", [""],["","",""]], - "" : [["atm","ATM","atmosphere","atmospheres"], 101325, "pressure", [""],["","",""]], - "" : [["psi"], 6894.76, "pressure", [""],["","",""]], - "" : [["cmH2O"], 98.0638, "pressure", [""],["","",""]], - "" : [["inH2O"], 249.082052, "pressure", [""],["","",""]], - - /* viscosity */ - "" : [["P","poise"], 0.1, "viscosity", [""],["",""] ], - "" : [["St","stokes"], 1e-4, "viscosity", ["",""], [""]], - - /* substance */ - "" : [["mol","mole"], 1.0, "substance", [""]], - - /* concentration */ - "" : [["M","molar"], 1000, "concentration", [""], ["","",""]], - "" : [["wt%","wtpercent"], 10, "concentration", [""], ["","",""]], - - /* activity */ - "" : [["kat","katal","Katal"], 1.0, "activity", [""], [""]], - "" : [["U","enzUnit"], 16.667e-16, "activity", [""], [""]], - - /* capacitance */ - "" : [["F","farad","Farad"], 1.0, "capacitance", ["","","","","",""], ["", "", ""]], - - /* charge */ - "" : [["C","coulomb","Coulomb"], 1.0, "charge", ["",""]], - "" : [["Ah"], 3600, "charge", ["",""]], - - /* current */ - "" : [["A","Ampere","ampere","amp","amps"], 1.0, "current", [""]], - - /* conductance */ - "" : [["S","Siemens","siemens"], 1.0, "conductance", ["","","","",""], ["","",""]], - - /* inductance */ - "" : [["H","Henry","henry"], 1.0, "inductance", ["","",""], ["","","",""]], - - /* potential */ - "" : [["V","Volt","volt","volts"], 1.0, "potential", ["","",""], ["","","",""]], - - /* resistance */ - "" : [ - ["Ohm","ohm","\u03A9"/*Ω as greek letter*/,"\u2126"/*Ω as ohm sign*/], - 1.0, - "resistance", - ["","",""],["","","","",""] - ], - /* magnetism */ - "" : [["Wb","weber","webers"], 1.0, "magnetism", ["","",""], ["","",""]], - "" : [["T","tesla","teslas"], 1.0, "magnetism", [""], ["","",""]], - "" : [["G","gauss"], 1e-4, "magnetism", [""], ["","",""]], - "" : [["Mx","maxwell","maxwells"], 1e-8, "magnetism", ["","",""], ["","",""]], - "" : [["Oe","oersted","oersteds"], 250.0 / Math.PI, "magnetism", [""], [""]], - - /* energy */ - "" : [["J","joule","Joule","joules"], 1.0, "energy", ["","",""], ["",""]], - "" : [["erg","ergs"], 1e-7, "energy", ["","",""], ["",""]], - "" : [["BTU","btu","BTUs"], 1055.056, "energy", ["","",""], ["",""]], - "" : [["cal","calorie","calories"], 4.18400, "energy",["","",""], ["",""]], - "" : [["Cal","Calorie","Calories"], 4184.00, "energy",["","",""], ["",""]], - "" : [["th","therm","therms","Therm"], 105480400, "energy",["","",""], ["",""]], - "" : [["Wh"], 3600, "energy",["","",""], ["",""]], - - /* force */ - "" : [["N","Newton","newton"], 1.0, "force", ["",""], ["",""]], - "" : [["dyn","dyne"], 1e-5, "force", ["",""], ["",""]], - "" : [["lbf","pound-force"], 4.448222, "force", ["",""], ["",""]], - - /* frequency */ - "" : [["Hz","hertz","Hertz"], 1.0, "frequency", ["<1>"], [""]], - - /* angle */ - "" :[["rad","radian","radians"], 1.0, "angle", [""]], - "" :[["deg","degree","degrees"], Math.PI / 180.0, "angle", [""]], - "" :[["gon","grad","gradian","grads"], Math.PI / 200.0, "angle", [""]], - "" : [["sr","steradian","steradians"], 1.0, "solid_angle", [""]], - - /* rotation */ - "" : [["rotation"], 2.0 * Math.PI, "angle", [""]], - "" :[["rpm"], 2.0 * Math.PI / 60.0, "angular_velocity", [""], [""]], - - /* information */ - "" :[["B","byte","bytes"], 1.0, "information", [""]], - "" :[["b","bit","bits"], 0.125, "information", [""]], - - /* information rate */ - "" : [["Bps"], 1.0, "information_rate", [""], [""]], - "" : [["bps"], 0.125, "information_rate", [""], [""]], - - /* currency */ - "":[["USD","dollar"], 1.0, "currency", [""]], - "" :[["cents"], 0.01, "currency", [""]], - - /* luminosity */ - "" : [["cd","candela"], 1.0, "luminosity", [""]], - "" : [["lm","lumen"], 1.0, "luminous_power", ["",""]], - "" :[["lux"], 1.0, "illuminance", ["",""], ["",""]], - - /* power */ - "" : [["W","watt","watts"], 1.0, "power", ["","",""], ["","",""]], - "" : [["VA"], 1.0, "power", ["","",""], ["","",""]], - "" : [["var","Var","VAr","VAR"], 1.0, "power", ["","",""], ["","",""]], - "" : [["hp","horsepower"], 745.699872, "power", ["","",""], ["","",""]], - - /* radiation */ - "" : [["Gy","gray","grays"], 1.0, "radiation", ["",""], ["",""]], - "" : [["R","roentgen"], 0.009330, "radiation", ["",""], ["",""]], - "" : [["Sv","sievert","sieverts"], 1.0, "radiation", ["",""], ["",""]], - "" : [["Bq","bequerel","bequerels"], 1.0, "radiation", ["<1>"],[""]], - "" : [["Ci","curie","curies"], 3.7e10, "radiation", ["<1>"],[""]], - - /* rate */ - "" : [["cpm"], 1.0 / 60.0, "rate", [""],[""]], - "" : [["dpm"], 1.0 / 60.0, "rate", [""],[""]], - "" : [["bpm"], 1.0 / 60.0, "rate", [""],[""]], - - /* resolution / typography */ - "" : [["dot","dots"], 1, "resolution", [""]], - "" : [["pixel","px"], 1, "resolution", [""]], - "" : [["ppi"], 1, "resolution", [""], [""]], - "" : [["dpi"], 1, "typography", [""], [""]], - - /* other */ - "" : [["cells","cell"], 1, "counting", [""]], - "" : [["each"], 1.0, "counting", [""]], - "" : [["count"], 1.0, "counting", [""]], - "" : [["bp"], 1.0, "counting", [""]], - "" : [["nt"], 1.0, "counting", [""]], - "" : [["molecule","molecules"], 1.0, "counting", ["<1>"]], - "" : [["doz","dz","dozen"],12.0,"prefix_only", [""]], - "": [["%","percent"], 0.01, "prefix_only", ["<1>"]], - "" : [["ppm"],1e-6, "prefix_only", ["<1>"]], - "" : [["ppt"],1e-9, "prefix_only", ["<1>"]], - "" : [["gr","gross"],144.0, "prefix_only", ["",""]], - "" : [["dB","decibel","decibels"], 1.0, "logarithmic", [""]] -}; - -var BASE_UNITS = ["","","","", "","","","","","","","","",""]; - -var UNITY = "<1>"; -var UNITY_ARRAY = [UNITY]; - -// Setup - -/** - * Asserts unit definition is valid - * - * @param {string} unitDef - Name of unit to test - * @param {Object} definition - Definition of unit to test - * - * @throws {QtyError} if unit definition is not valid - */ -function validateUnitDefinition(unitDef, definition) { - var scalar = definition[1]; - var numerator = definition[3] || []; - var denominator = definition[4] || []; - if (!isNumber(scalar)) { - throw new QtyError(unitDef + ": Invalid unit definition. " + - "'scalar' must be a number"); + function getFractional(num) { + // Check for NaNs or Infinities + if (!isFinite(num)) { + return 0; + } + + // Faster than parsing strings + // http://jsperf.com/count-decimals/2 + var count = 0; + while (num % 1 !== 0) { + num *= 10; + count++; + } + return count; } - numerator.forEach(function(unit) { - if (UNITS[unit] === undefined) { - throw new QtyError(unitDef + ": Invalid unit definition. " + - "Unit " + unit + " in 'numerator' is not recognized"); + /** + * Custom error type definition + * @constructor + */ + function QtyError() { + var err; + if (!this) { // Allows to instantiate QtyError without new() + err = Object.create(QtyError.prototype); + QtyError.apply(err, arguments); + return err; } - }); + err = Error.apply(this, arguments); + this.name = "QtyError"; + this.message = err.message; + this.stack = err.stack; + } + QtyError.prototype = Object.create(Error.prototype, {constructor: { value: QtyError }}); + + /* + * Throws incompatible units error + * @param {string} left - units + * @param {string} right - units incompatible with first argument + * @throws "Incompatible units" error + */ + function throwIncompatibleUnits(left, right) { + throw new QtyError("Incompatible units: " + left + " and " + right); + } + + var UNITS = { + /* prefixes */ + "" : [["googol"], 1e100, "prefix"], + "" : [["Ki","Kibi","kibi"], Math.pow(2,10), "prefix"], + "" : [["Mi","Mebi","mebi"], Math.pow(2,20), "prefix"], + "" : [["Gi","Gibi","gibi"], Math.pow(2,30), "prefix"], + "" : [["Ti","Tebi","tebi"], Math.pow(2,40), "prefix"], + "" : [["Pi","Pebi","pebi"], Math.pow(2,50), "prefix"], + "" : [["Ei","Exi","exi"], Math.pow(2,60), "prefix"], + "" : [["Zi","Zebi","zebi"], Math.pow(2,70), "prefix"], + "" : [["Yi","Yebi","yebi"], Math.pow(2,80), "prefix"], + "" : [["Y","Yotta","yotta"], 1e24, "prefix"], + "" : [["Z","Zetta","zetta"], 1e21, "prefix"], + "" : [["E","Exa","exa"], 1e18, "prefix"], + "" : [["P","Peta","peta"], 1e15, "prefix"], + "" : [["T","Tera","tera"], 1e12, "prefix"], + "" : [["G","Giga","giga"], 1e9, "prefix"], + "" : [["M","Mega","mega"], 1e6, "prefix"], + "" : [["k","kilo"], 1e3, "prefix"], + "" : [["h","Hecto","hecto"], 1e2, "prefix"], + "" : [["da","Deca","deca","deka"], 1e1, "prefix"], + "" : [["d","Deci","deci"], 1e-1, "prefix"], + "" : [["c","Centi","centi"], 1e-2, "prefix"], + "" : [["m","Milli","milli"], 1e-3, "prefix"], + "" : [ + ["u","\u03BC"/*µ as greek letter*/,"\u00B5"/*µ as micro sign*/,"Micro","mc","micro"], + 1e-6, + "prefix" + ], + "" : [["n","Nano","nano"], 1e-9, "prefix"], + "" : [["p","Pico","pico"], 1e-12, "prefix"], + "" : [["f","Femto","femto"], 1e-15, "prefix"], + "" : [["a","Atto","atto"], 1e-18, "prefix"], + "" : [["z","Zepto","zepto"], 1e-21, "prefix"], + "" : [["y","Yocto","yocto"], 1e-24, "prefix"], + + "<1>" : [["1", "<1>"], 1, ""], + /* length units */ + "" : [["m","meter","meters","metre","metres"], 1.0, "length", [""] ], + "" : [["in","inch","inches","\""], 0.0254, "length", [""]], + "" : [["ft","foot","feet","'"], 0.3048, "length", [""]], + "" : [["yd","yard","yards"], 0.9144, "length", [""]], + "" : [["mi","mile","miles"], 1609.344, "length", [""]], + "" : [["nmi","naut-mile"], 1852, "length", [""]], + "": [["league","leagues"], 4828, "length", [""]], + "": [["furlong","furlongs"], 201.2, "length", [""]], + "" : [["rd","rod","rods"], 5.029, "length", [""]], + "" : [["mil","mils"], 0.0000254, "length", [""]], + "" :[["ang","angstrom","angstroms"], 1e-10, "length", [""]], + "" : [["fathom","fathoms"], 1.829, "length", [""]], + "" : [["pica","picas"], 0.00423333333, "length", [""]], + "" : [["pt","point","points"], 0.000352777778, "length", [""]], + "" : [["z","red-shift", "redshift"], 1.302773e26, "length", [""]], + "" : [["AU","astronomical-unit"], 149597900000, "length", [""]], + "":[["ls","light-second"], 299792500, "length", [""]], + "":[["lmin","light-minute"], 17987550000, "length", [""]], + "" : [["ly","light-year"], 9460528000000000, "length", [""]], + "" : [["pc","parsec","parsecs"], 30856780000000000, "length", [""]], + "" : [["DM","datamile"], 1828.8, "length", [""]], + + /* mass */ + "" : [["kg","kilogram","kilograms"], 1.0, "mass", [""]], + "" : [["u","AMU","amu"], 1.660538921e-27, "mass", [""]], + "" : [["Da","Dalton","Daltons","dalton","daltons"], 1.660538921e-27, "mass", [""]], + "" : [["slug","slugs"], 14.5939029, "mass", [""]], + "" : [["tn","ton","short-ton"], 907.18474, "mass", [""]], + "":[["tonne","metric-ton"], 1000, "mass", [""]], + "" : [["ct","carat","carats"], 0.0002, "mass", [""]], + "" : [["lbs","lb","pound","pounds","#"], 0.45359237, "mass", [""]], + "" : [["oz","ounce","ounces"], 0.0283495231, "mass", [""]], + "" : [["g","gram","grams","gramme","grammes"], 1e-3, "mass", [""]], + "" : [["grain","grains","gr"], 6.479891e-5, "mass", [""]], + "" : [["dram","drams","dr"], 0.0017718452, "mass",[""]], + "" : [["stone","stones","st"],6.35029318, "mass",[""]], + + /* area */ + "":[["hectare"], 10000, "area", ["",""]], + "":[["acre","acres"], 4046.85642, "area", ["",""]], + "":[["sqft"], 1, "area", ["",""]], + + /* volume */ + "" : [["l","L","liter","liters","litre","litres"], 0.001, "volume", ["","",""]], + "": [["gal","gallon","gallons"], 0.0037854118, "volume", ["","",""]], + "": [["qt","quart","quarts"], 0.00094635295, "volume", ["","",""]], + "": [["pt","pint","pints"], 0.000473176475, "volume", ["","",""]], + "": [["cu","cup","cups"], 0.000236588238, "volume", ["","",""]], + "": [["floz","fluid-ounce","fluid-ounces"], 2.95735297e-5, "volume", ["","",""]], + "": [["tb","tbsp","tbs","tablespoon","tablespoons"], 1.47867648e-5, "volume", ["","",""]], + "": [["tsp","teaspoon","teaspoons"], 4.92892161e-6, "volume", ["","",""]], + "": [["bu","bsh","bushel","bushels"], 0.035239072, "volume", ["","",""]], + + /* speed */ + "" : [["kph"], 0.277777778, "speed", [""], [""]], + "" : [["mph"], 0.44704, "speed", [""], [""]], + "" : [["kt","kn","kts","knot","knots"], 0.514444444, "speed", [""], [""]], + "" : [["fps"], 0.3048, "speed", [""], [""]], + + /* acceleration */ + "" : [["gee"], 9.80665, "acceleration", [""], ["",""]], + + /* temperature_difference */ + "" : [["degK","kelvin"], 1.0, "temperature", [""]], + "" : [["degC","celsius","celsius","centigrade"], 1.0, "temperature", [""]], + "" : [["degF","fahrenheit"], 5 / 9, "temperature", [""]], + "" : [["degR","rankine"], 5 / 9, "temperature", [""]], + "" : [["tempK","temp-K"], 1.0, "temperature", [""]], + "" : [["tempC","temp-C"], 1.0, "temperature", [""]], + "" : [["tempF","temp-F"], 5 / 9, "temperature", [""]], + "" : [["tempR","temp-R"], 5 / 9, "temperature", [""]], + + /* time */ + "": [["s","sec","secs","second","seconds"], 1.0, "time", [""]], + "": [["min","mins","minute","minutes"], 60.0, "time", [""]], + "": [["h","hr","hrs","hour","hours"], 3600.0, "time", [""]], + "": [["d","day","days"], 3600 * 24, "time", [""]], + "": [["wk","week","weeks"], 7 * 3600 * 24, "time", [""]], + "": [["fortnight","fortnights"], 1209600, "time", [""]], + "": [["y","yr","year","years","annum"], 31556926, "time", [""]], + "":[["decade","decades"], 315569260, "time", [""]], + "":[["century","centuries"], 3155692600, "time", [""]], + + /* pressure */ + "" : [["Pa","pascal","Pascal"], 1.0, "pressure", [""],["","",""]], + "" : [["bar","bars"], 100000, "pressure", [""],["","",""]], + "" : [["mmHg"], 133.322368, "pressure", [""],["","",""]], + "" : [["inHg"], 3386.3881472, "pressure", [""],["","",""]], + "" : [["torr"], 133.322368, "pressure", [""],["","",""]], + "" : [["atm","ATM","atmosphere","atmospheres"], 101325, "pressure", [""],["","",""]], + "" : [["psi"], 6894.76, "pressure", [""],["","",""]], + "" : [["cmH2O","cmh2o"], 98.0638, "pressure", [""],["","",""]], + "" : [["inH2O","inh2o"], 249.082052, "pressure", [""],["","",""]], + + /* viscosity */ + "" : [["P","poise"], 0.1, "viscosity", [""],["",""] ], + "" : [["St","stokes"], 1e-4, "viscosity", ["",""], [""]], + + /* substance */ + "" : [["mol","mole"], 1.0, "substance", [""]], + + /* concentration */ + "" : [["M","molar"], 1000, "concentration", [""], ["","",""]], + "" : [["wt%","wtpercent"], 10, "concentration", [""], ["","",""]], + + /* activity */ + "" : [["kat","katal","Katal"], 1.0, "activity", [""], [""]], + "" : [["U","enzUnit","unit"], 16.667e-16, "activity", [""], [""]], + + /* capacitance */ + "" : [["F","farad","Farad"], 1.0, "capacitance", ["","","","","",""], ["", "", ""]], + + /* charge */ + "" : [["C","coulomb","Coulomb"], 1.0, "charge", ["",""]], + "" : [["Ah"], 3600, "charge", ["",""]], + + /* current */ + "" : [["A","Ampere","ampere","amp","amps"], 1.0, "current", [""]], + + /* conductance */ + "" : [["S","Siemens","siemens"], 1.0, "conductance", ["","","","",""], ["","",""]], + + /* inductance */ + "" : [["H","Henry","henry"], 1.0, "inductance", ["","",""], ["","","",""]], + + /* potential */ + "" : [["V","Volt","volt","volts"], 1.0, "potential", ["","",""], ["","","",""]], + + /* resistance */ + "" : [ + ["Ohm","ohm","\u03A9"/*Ω as greek letter*/,"\u2126"/*Ω as ohm sign*/], + 1.0, + "resistance", + ["","",""],["","","","",""] + ], + /* magnetism */ + "" : [["Wb","weber","webers"], 1.0, "magnetism", ["","",""], ["","",""]], + "" : [["T","tesla","teslas"], 1.0, "magnetism", [""], ["","",""]], + "" : [["G","gauss"], 1e-4, "magnetism", [""], ["","",""]], + "" : [["Mx","maxwell","maxwells"], 1e-8, "magnetism", ["","",""], ["","",""]], + "" : [["Oe","oersted","oersteds"], 250.0 / Math.PI, "magnetism", [""], [""]], + + /* energy */ + "" : [["J","joule","Joule","joules"], 1.0, "energy", ["","",""], ["",""]], + "" : [["erg","ergs"], 1e-7, "energy", ["","",""], ["",""]], + "" : [["BTU","btu","BTUs"], 1055.056, "energy", ["","",""], ["",""]], + "" : [["cal","calorie","calories"], 4.18400, "energy",["","",""], ["",""]], + "" : [["Cal","Calorie","Calories"], 4184.00, "energy",["","",""], ["",""]], + "" : [["th","therm","therms","Therm","therm-US"], 105480400, "energy",["","",""], ["",""]], + "" : [["Wh"], 3600, "energy",["","",""], ["",""]], + + /* force */ + "" : [["N","Newton","newton"], 1.0, "force", ["",""], ["",""]], + "" : [["dyn","dyne"], 1e-5, "force", ["",""], ["",""]], + "" : [["lbf","pound-force"], 4.448222, "force", ["",""], ["",""]], + + /* frequency */ + "" : [["Hz","hertz","Hertz"], 1.0, "frequency", ["<1>"], [""]], + + /* angle */ + "" :[["rad","radian","radians"], 1.0, "angle", [""]], + "" :[["deg","degree","degrees"], Math.PI / 180.0, "angle", [""]], + "" :[["gon","grad","gradian","grads"], Math.PI / 200.0, "angle", [""]], + "" : [["sr","steradian","steradians"], 1.0, "solid_angle", [""]], + + /* rotation */ + "" : [["rotation"], 2.0 * Math.PI, "angle", [""]], + "" :[["rpm"], 2.0 * Math.PI / 60.0, "angular_velocity", [""], [""]], + + /* information */ + "" :[["B","byte","bytes"], 1.0, "information", [""]], + "" :[["b","bit","bits"], 0.125, "information", [""]], + + /* information rate */ + "" : [["Bps"], 1.0, "information_rate", [""], [""]], + "" : [["bps"], 0.125, "information_rate", [""], [""]], + + /* currency */ + "":[["USD","dollar"], 1.0, "currency", [""]], + "" :[["cents"], 0.01, "currency", [""]], + + /* luminosity */ + "" : [["cd","candela"], 1.0, "luminosity", [""]], + "" : [["lm","lumen"], 1.0, "luminous_power", ["",""]], + "" :[["lux"], 1.0, "illuminance", ["",""], ["",""]], + + /* power */ + "" : [["W","watt","watts"], 1.0, "power", ["","",""], ["","",""]], + "" : [["VA","volt-ampere"], 1.0, "power", ["","",""], ["","",""]], + "" : [["var","Var","VAr","VAR","volt-ampere-reactive"], 1.0, "power", ["","",""], ["","",""]], + "" : [["hp","horsepower"], 745.699872, "power", ["","",""], ["","",""]], + + /* radiation */ + "" : [["Gy","gray","grays"], 1.0, "radiation", ["",""], ["",""]], + "" : [["R","roentgen"], 0.009330, "radiation", ["",""], ["",""]], + "" : [["Sv","sievert","sieverts"], 1.0, "radiation", ["",""], ["",""]], + "" : [["Bq","becquerel","becquerels"], 1.0, "radiation", ["<1>"],[""]], + "" : [["Ci","curie","curies"], 3.7e10, "radiation", ["<1>"],[""]], + + /* rate */ + "" : [["cpm"], 1.0 / 60.0, "rate", [""],[""]], + "" : [["dpm"], 1.0 / 60.0, "rate", [""],[""]], + "" : [["bpm"], 1.0 / 60.0, "rate", [""],[""]], + + /* resolution / typography */ + "" : [["dot","dots"], 1, "resolution", [""]], + "" : [["pixel","px"], 1, "resolution", [""]], + "" : [["ppi"], 1, "resolution", [""], [""]], + "" : [["dpi"], 1, "typography", [""], [""]], + + /* other */ + "" : [["cells","cell"], 1, "counting", [""]], + "" : [["each"], 1.0, "counting", [""]], + "" : [["count"], 1.0, "counting", [""]], + "" : [["bp","base-pair"], 1.0, "counting", [""]], + "" : [["nt","nucleotide"], 1.0, "counting", [""]], + "" : [["molecule","molecules"], 1.0, "counting", ["<1>"]], + "" : [["doz","dz","dozen"],12.0,"prefix_only", [""]], + "": [["%","percent"], 0.01, "prefix_only", ["<1>"]], + "" : [["ppm"],1e-6, "prefix_only", ["<1>"]], + "" : [["ppt"],1e-9, "prefix_only", ["<1>"]], + "" : [["gr","gross"],144.0, "prefix_only", ["",""]], + "" : [["dB","decibel","decibels"], 1.0, "logarithmic", [""]] + }; + + var BASE_UNITS = ["","","","", "","","","","","","","","",""]; + + var UNITY = "<1>"; + var UNITY_ARRAY = [UNITY]; + + // Setup - denominator.forEach(function(unit) { - if (UNITS[unit] === undefined) { + /** + * Asserts unit definition is valid + * + * @param {string} unitDef - Name of unit to test + * @param {Object} definition - Definition of unit to test + * + * @throws {QtyError} if unit definition is not valid + */ + function validateUnitDefinition(unitDef, definition) { + var scalar = definition[1]; + var numerator = definition[3] || []; + var denominator = definition[4] || []; + if (!isNumber(scalar)) { throw new QtyError(unitDef + ": Invalid unit definition. " + - "Unit " + unit + " in 'denominator' is not recognized"); + "'scalar' must be a number"); } - }); -} - -var PREFIX_VALUES = {}; -var PREFIX_MAP = {}; -var UNIT_VALUES = {}; -var UNIT_MAP = {}; -var OUTPUT_MAP = {}; -for (var unitDef in UNITS) { - if (UNITS.hasOwnProperty(unitDef)) { - var definition = UNITS[unitDef]; - if (definition[2] === "prefix") { - PREFIX_VALUES[unitDef] = definition[1]; - for (var i = 0; i < definition[0].length; i++) { - PREFIX_MAP[definition[0][i]] = unitDef; + + numerator.forEach(function(unit) { + if (UNITS[unit] === undefined) { + throw new QtyError(unitDef + ": Invalid unit definition. " + + "Unit " + unit + " in 'numerator' is not recognized"); } - } - else { - validateUnitDefinition(unitDef, definition); - UNIT_VALUES[unitDef] = { - scalar: definition[1], - numerator: definition[3], - denominator: definition[4] - }; - for (var j = 0; j < definition[0].length; j++) { - UNIT_MAP[definition[0][j]] = unitDef; + }); + + denominator.forEach(function(unit) { + if (UNITS[unit] === undefined) { + throw new QtyError(unitDef + ": Invalid unit definition. " + + "Unit " + unit + " in 'denominator' is not recognized"); } - } - OUTPUT_MAP[unitDef] = definition[0][0]; + }); } -} - -/** - * Returns a list of available units of kind - * - * @param {string} [kind] - * @returns {array} names of units - * @throws {QtyError} if kind is unknown - */ -function getUnits (kind) { - var i; - var units = []; - var unitKeys = Object.keys(UNITS); - if (typeof kind === "undefined") { - for (i = 0; i < unitKeys.length; i++) { - if (["", "prefix"].indexOf(UNITS[unitKeys[i]][2]) === -1) { - units.push(unitKeys[i].substr(1, unitKeys[i].length - 2)); + + var PREFIX_VALUES = {}; + var PREFIX_MAP = {}; + var UNIT_VALUES = {}; + var UNIT_MAP = {}; + var OUTPUT_MAP = {}; + for (var unitDef in UNITS) { + if (UNITS.hasOwnProperty(unitDef)) { + var definition = UNITS[unitDef]; + if (definition[2] === "prefix") { + PREFIX_VALUES[unitDef] = definition[1]; + for (var i = 0; i < definition[0].length; i++) { + PREFIX_MAP[definition[0][i]] = unitDef; + } } + else { + validateUnitDefinition(unitDef, definition); + UNIT_VALUES[unitDef] = { + scalar: definition[1], + numerator: definition[3], + denominator: definition[4] + }; + for (var j = 0; j < definition[0].length; j++) { + UNIT_MAP[definition[0][j]] = unitDef; + } + } + OUTPUT_MAP[unitDef] = definition[0][0]; } } - else if (Qty$1.getKinds().indexOf(kind) === -1) { - throw new QtyError("Kind not recognized"); - } - else { - for (i = 0; i < unitKeys.length; i++) { - if (UNITS[unitKeys[i]][2] === kind) { - units.push(unitKeys[i].substr(1, unitKeys[i].length - 2)); + + /** + * Returns a list of available units of kind + * + * @param {string} [kind] + * @returns {array} names of units + * @throws {QtyError} if kind is unknown + */ + function getUnits (kind) { + var i; + var units = []; + var unitKeys = Object.keys(UNITS); + if (typeof kind === "undefined") { + for (i = 0; i < unitKeys.length; i++) { + if (["", "prefix"].indexOf(UNITS[unitKeys[i]][2]) === -1) { + units.push(unitKeys[i].substr(1, unitKeys[i].length - 2)); + } } } + else if (this.getKinds().indexOf(kind) === -1) { + throw new QtyError("Kind not recognized"); + } + else { + for (i = 0; i < unitKeys.length; i++) { + if (UNITS[unitKeys[i]][2] === kind) { + units.push(unitKeys[i].substr(1, unitKeys[i].length - 2)); + } + } + } + + return units.sort(function(a, b) { + if (a.toLowerCase() < b.toLowerCase()) { + return -1; + } + if (a.toLowerCase() > b.toLowerCase()) { + return 1; + } + return 0; + }); } - return units.sort(function(a, b) { - if (a.toLowerCase() < b.toLowerCase()) { - return -1; - } - if (a.toLowerCase() > b.toLowerCase()) { - return 1; + /** + * Returns a list of alternative names for a unit + * + * @param {string} unitName + * @returns {string[]} aliases for unit + * @throws {QtyError} if unit is unknown + */ + function getAliases(unitName) { + if (!UNIT_MAP[unitName]) { + throw new QtyError("Unit not recognized"); } - return 0; - }); -} - -/** - * Returns a list of alternative names for a unit - * - * @param {string} unitName - * @returns {string[]} aliases for unit - * @throws {QtyError} if unit is unknown - */ -function getAliases(unitName) { - if (!UNIT_MAP[unitName]) { - throw new QtyError("Unit not recognized"); + return UNITS[UNIT_MAP[unitName]][0]; } - return UNITS[UNIT_MAP[unitName]][0]; -} -var SIGNATURE_VECTOR = ["length", "time", "temperature", "mass", "current", "substance", "luminosity", "currency", "information", "angle"]; + var SIGNATURE_VECTOR = ["length", "time", "temperature", "mass", "current", "substance", "luminosity", "currency", "information", "angle"]; -/* -calculates the unit signature id for use in comparing compatible units and simplification -the signature is based on a simple classification of units and is based on the following publication + /* + calculates the unit signature id for use in comparing compatible units and simplification + the signature is based on a simple classification of units and is based on the following publication -Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering, -21(8), Aug 1995, pp.651-661 -doi://10.1109/32.403789 -http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr. -*/ -function unitSignature() { - if (this.signature) { - return this.signature; - } - var vector = unitSignatureVector.call(this); - for (var i = 0; i < vector.length; i++) { - vector[i] *= Math.pow(20, i); - } + Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering, + 21(8), Aug 1995, pp.651-661 + doi://10.1109/32.403789 + http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr. + */ + function unitSignature() { + if (this.signature) { + return this.signature; + } + var vector = unitSignatureVector.call(this); + for (var i = 0; i < vector.length; i++) { + vector[i] *= Math.pow(20, i); + } - return vector.reduce( - function(previous, current) { - return previous + current; - }, - 0 - ); -} - -// calculates the unit signature vector used by unit_signature -function unitSignatureVector() { - if (!this.isBase()) { - return unitSignatureVector.call(this.toBase()); + return vector.reduce( + function(previous, current) { + return previous + current; + }, + 0 + ); } - var vector = new Array(SIGNATURE_VECTOR.length); - for (var i = 0; i < vector.length; i++) { - vector[i] = 0; - } - var r, n; - for (var j = 0; j < this.numerator.length; j++) { - if ((r = UNITS[this.numerator[j]])) { - n = SIGNATURE_VECTOR.indexOf(r[2]); - if (n >= 0) { - vector[n] = vector[n] + 1; + // calculates the unit signature vector used by unit_signature + function unitSignatureVector() { + if (!this.isBase()) { + return unitSignatureVector.call(this.toBase()); + } + + var vector = new Array(SIGNATURE_VECTOR.length); + for (var i = 0; i < vector.length; i++) { + vector[i] = 0; + } + var r, n; + for (var j = 0; j < this.numerator.length; j++) { + if ((r = UNITS[this.numerator[j]])) { + n = SIGNATURE_VECTOR.indexOf(r[2]); + if (n >= 0) { + vector[n] = vector[n] + 1; + } } } - } - for (var k = 0; k < this.denominator.length; k++) { - if ((r = UNITS[this.denominator[k]])) { - n = SIGNATURE_VECTOR.indexOf(r[2]); - if (n >= 0) { - vector[n] = vector[n] - 1; + for (var k = 0; k < this.denominator.length; k++) { + if ((r = UNITS[this.denominator[k]])) { + n = SIGNATURE_VECTOR.indexOf(r[2]); + if (n >= 0) { + vector[n] = vector[n] - 1; + } } } - } - return vector; -} - -var SIGN = "[+-]"; -var INTEGER = "\\d+"; -var SIGNED_INTEGER = SIGN + "?" + INTEGER; -var FRACTION = "\\." + INTEGER; -var FLOAT = "(?:" + INTEGER + "(?:" + FRACTION + ")?" + ")" + - "|" + - "(?:" + FRACTION + ")"; -var EXPONENT = "[Ee]" + SIGNED_INTEGER; -var SCI_NUMBER = "(?:" + FLOAT + ")(?:" + EXPONENT + ")?"; -var SIGNED_NUMBER = SIGN + "?\\s*" + SCI_NUMBER; -var QTY_STRING = "(" + SIGNED_NUMBER + ")?" + "\\s*([^/]*)(?:\/(.+))?"; -var QTY_STRING_REGEX = new RegExp("^" + QTY_STRING + "$"); - -var POWER_OP = "\\^|\\*{2}"; -// Allow unit powers representing scalar, length, area, volume; 4 is for some -// special case representations in SI base units. -var SAFE_POWER = "[01234]"; -var TOP_REGEX = new RegExp ("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(-?" + SAFE_POWER + "(?![a-zA-Z]))"); -var BOTTOM_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(" + SAFE_POWER + "(?![a-zA-Z]))"); - -/* parse a string into a unit object. - * Typical formats like : - * "5.6 kg*m/s^2" - * "5.6 kg*m*s^-2" - * "5.6 kilogram*meter*second^-2" - * "2.2 kPa" - * "37 degC" - * "1" -- creates a unitless constant with value 1 - * "GPa" -- creates a unit with scalar 1 with units 'GPa' - * 6'4" -- recognized as 6 feet + 4 inches - * 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces - */ -function parse(val) { - if (!isString(val)) { - val = val.toString(); - } - val = val.trim(); + return vector; + } + + var SIGN = "[+-]"; + var INTEGER = "\\d+"; + var SIGNED_INTEGER = SIGN + "?" + INTEGER; + var FRACTION = "\\." + INTEGER; + var FLOAT = "(?:" + INTEGER + "(?:" + FRACTION + ")?" + ")" + + "|" + + "(?:" + FRACTION + ")"; + var EXPONENT = "[Ee]" + SIGNED_INTEGER; + var SCI_NUMBER = "(?:" + FLOAT + ")(?:" + EXPONENT + ")?"; + var SIGNED_NUMBER = SIGN + "?\\s*" + SCI_NUMBER; + var QTY_STRING = "(" + SIGNED_NUMBER + ")?" + "\\s*([^/]*)(?:\/(.+))?"; + var QTY_STRING_REGEX = new RegExp("^" + QTY_STRING + "$"); + + var POWER_OP = "\\^|\\*{2}"; + // Allow unit powers representing scalar, length, area, volume; 4 is for some + // special case representations in SI base units. + var SAFE_POWER = "[01234]"; + var TOP_REGEX = new RegExp ("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(-?" + SAFE_POWER + "(?![a-zA-Z]))"); + var BOTTOM_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(" + SAFE_POWER + "(?![a-zA-Z]))"); + + /* parse a string into a unit object. + * Typical formats like : + * "5.6 kg*m/s^2" + * "5.6 kg*m*s^-2" + * "5.6 kilogram*meter*second^-2" + * "2.2 kPa" + * "37 degC" + * "1" -- creates a unitless constant with value 1 + * "GPa" -- creates a unit with scalar 1 with units 'GPa' + * 6'4" -- recognized as 6 feet + 4 inches + * 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces + */ + function parse(val) { + if (!isString(val)) { + val = val.toString(); + } + val = val.trim(); - var result = QTY_STRING_REGEX.exec(val); - if (!result) { - throw new QtyError(val + ": Quantity not recognized"); - } + var result = QTY_STRING_REGEX.exec(val); + if (!result) { + throw new QtyError(val + ": Quantity not recognized"); + } - var scalarMatch = result[1]; - if (scalarMatch) { - // Allow whitespaces between sign and scalar for loose parsing - scalarMatch = scalarMatch.replace(/\s/g, ""); - this.scalar = parseFloat(scalarMatch); - } - else { - this.scalar = 1; - } - var top = result[2]; - var bottom = result[3]; - - var n, x, nx; - // TODO DRY me - while ((result = TOP_REGEX.exec(top))) { - n = parseFloat(result[2]); - if (isNaN(n)) { - // Prevents infinite loops - throw new QtyError("Unit exponent is not a number"); - } - // Disallow unrecognized unit even if exponent is 0 - if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) { - throw new QtyError("Unit not recognized"); + var scalarMatch = result[1]; + if (scalarMatch) { + // Allow whitespaces between sign and scalar for loose parsing + scalarMatch = scalarMatch.replace(/\s/g, ""); + this.scalar = parseFloat(scalarMatch); + } + else { + this.scalar = 1; + } + var top = result[2]; + var bottom = result[3]; + + var n, x, nx; + // TODO DRY me + while ((result = TOP_REGEX.exec(top))) { + n = parseFloat(result[2]); + if (isNaN(n)) { + // Prevents infinite loops + throw new QtyError("Unit exponent is not a number"); + } + // Disallow unrecognized unit even if exponent is 0 + if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) { + throw new QtyError("Unit not recognized"); + } + x = result[1] + " "; + nx = ""; + for (var i = 0; i < Math.abs(n) ; i++) { + nx += x; + } + if (n >= 0) { + top = top.replace(result[0], nx); + } + else { + bottom = bottom ? bottom + nx : nx; + top = top.replace(result[0], ""); + } } - x = result[1] + " "; - nx = ""; - for (var i = 0; i < Math.abs(n) ; i++) { - nx += x; + + while ((result = BOTTOM_REGEX.exec(bottom))) { + n = parseFloat(result[2]); + if (isNaN(n)) { + // Prevents infinite loops + throw new QtyError("Unit exponent is not a number"); + } + // Disallow unrecognized unit even if exponent is 0 + if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) { + throw new QtyError("Unit not recognized"); + } + x = result[1] + " "; + nx = ""; + for (var j = 0; j < n ; j++) { + nx += x; + } + + bottom = bottom.replace(result[0], nx); } - if (n >= 0) { - top = top.replace(result[0], nx); + + if (top) { + this.numerator = parseUnits(top.trim()); } - else { - bottom = bottom ? bottom + nx : nx; - top = top.replace(result[0], ""); + if (bottom) { + this.denominator = parseUnits(bottom.trim()); } } - while ((result = BOTTOM_REGEX.exec(bottom))) { - n = parseFloat(result[2]); - if (isNaN(n)) { - // Prevents infinite loops - throw new QtyError("Unit exponent is not a number"); + var PREFIX_REGEX = Object.keys(PREFIX_MAP).sort(function(a, b) { + return b.length - a.length; + }).join("|"); + var UNIT_REGEX = Object.keys(UNIT_MAP).sort(function(a, b) { + return b.length - a.length; + }).join("|"); + /* + * Minimal boundary regex to support units with Unicode characters + * \b only works for ASCII + */ + var BOUNDARY_REGEX = "\\b|$"; + var UNIT_MATCH = "(" + PREFIX_REGEX + ")??(" + + UNIT_REGEX + + ")(?:" + BOUNDARY_REGEX + ")"; + var UNIT_TEST_REGEX = new RegExp("^\\s*(" + UNIT_MATCH + "[\\s\\*]*)+$"); + var UNIT_MATCH_REGEX = new RegExp(UNIT_MATCH, "g"); // g flag for multiple occurences + var parsedUnitsCache = {}; + /** + * Parses and converts units string to normalized unit array. + * Result is cached to speed up next calls. + * + * @param {string} units Units string + * @returns {string[]} Array of normalized units + * + * @example + * // Returns ["", "", ""] + * parseUnits("s m s"); + * + */ + function parseUnits(units) { + var cached = parsedUnitsCache[units]; + if (cached) { + return cached; } - // Disallow unrecognized unit even if exponent is 0 - if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) { + + var unitMatch, normalizedUnits = []; + + // Scan + if (!UNIT_TEST_REGEX.test(units)) { throw new QtyError("Unit not recognized"); } - x = result[1] + " "; - nx = ""; - for (var j = 0; j < n ; j++) { - nx += x; + + while ((unitMatch = UNIT_MATCH_REGEX.exec(units))) { + normalizedUnits.push(unitMatch.slice(1)); } - bottom = bottom.replace(result[0], nx); - } + normalizedUnits = normalizedUnits.map(function(item) { + return PREFIX_MAP[item[0]] ? [PREFIX_MAP[item[0]], UNIT_MAP[item[1]]] : [UNIT_MAP[item[1]]]; + }); - if (top) { - this.numerator = parseUnits(top.trim()); - } - if (bottom) { - this.denominator = parseUnits(bottom.trim()); - } -} - -var PREFIX_REGEX = Object.keys(PREFIX_MAP).sort(function(a, b) { - return b.length - a.length; -}).join("|"); -var UNIT_REGEX = Object.keys(UNIT_MAP).sort(function(a, b) { - return b.length - a.length; -}).join("|"); -/* - * Minimal boundary regex to support units with Unicode characters - * \b only works for ASCII - */ -var BOUNDARY_REGEX = "\\b|$"; -var UNIT_MATCH = "(" + PREFIX_REGEX + ")??(" + - UNIT_REGEX + - ")(?:" + BOUNDARY_REGEX + ")"; -var UNIT_TEST_REGEX = new RegExp("^\\s*(" + UNIT_MATCH + "[\\s\\*]*)+$"); -var UNIT_MATCH_REGEX = new RegExp(UNIT_MATCH, "g"); // g flag for multiple occurences -var parsedUnitsCache = {}; -/** - * Parses and converts units string to normalized unit array. - * Result is cached to speed up next calls. - * - * @param {string} units Units string - * @returns {string[]} Array of normalized units - * - * @example - * // Returns ["", "", ""] - * parseUnits("s m s"); - * - */ -function parseUnits(units) { - var cached = parsedUnitsCache[units]; - if (cached) { - return cached; - } + // Flatten and remove null elements + normalizedUnits = normalizedUnits.reduce(function(a,b) { + return a.concat(b); + }, []); + normalizedUnits = normalizedUnits.filter(function(item) { + return item; + }); - var unitMatch, normalizedUnits = []; + parsedUnitsCache[units] = normalizedUnits; - // Scan - if (!UNIT_TEST_REGEX.test(units)) { - throw new QtyError("Unit not recognized"); + return normalizedUnits; } - while ((unitMatch = UNIT_MATCH_REGEX.exec(units))) { - normalizedUnits.push(unitMatch.slice(1)); + /** + * Parses a string as a quantity + * @param {string} value - quantity as text + * @throws if value is not a string + * @returns {Qty|null} Parsed quantity or null if unrecognized + */ + function globalParse(value) { + if (!isString(value)) { + throw new QtyError("Argument should be a string"); + } + + try { + return this(value); + } + catch (e) { + return null; + } } - normalizedUnits = normalizedUnits.map(function(item) { - return PREFIX_MAP[item[0]] ? [PREFIX_MAP[item[0]], UNIT_MAP[item[1]]] : [UNIT_MAP[item[1]]]; - }); + /** + * Tests if a value is a Qty instance + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a Qty instance, false otherwise + */ + function isQty(value) { + return value instanceof Qty; + } - // Flatten and remove null elements - normalizedUnits = normalizedUnits.reduce(function(a,b) { - return a.concat(b); - }, []); - normalizedUnits = normalizedUnits.filter(function(item) { - return item; - }); + function Qty(initValue, initUnits) { + assertValidConstructorArgs.apply(null, arguments); - parsedUnitsCache[units] = normalizedUnits; + if (!(isQty(this))) { + return new Qty(initValue, initUnits); + } - return normalizedUnits; -} + this.scalar = null; + this.baseScalar = null; + this.signature = null; + this._conversionCache = {}; + this.numerator = UNITY_ARRAY; + this.denominator = UNITY_ARRAY; -/** - * Parses a string as a quantity - * @param {string} value - quantity as text - * @throws if value is not a string - * @returns {Qty|null} Parsed quantity or null if unrecognized - */ -function globalParse(value) { - if (!isString(value)) { - throw new QtyError("Argument should be a string"); - } + if (isDefinitionObject(initValue)) { + this.scalar = initValue.scalar; + this.numerator = (initValue.numerator && initValue.numerator.length !== 0) ? initValue.numerator : UNITY_ARRAY; + this.denominator = (initValue.denominator && initValue.denominator.length !== 0) ? initValue.denominator : UNITY_ARRAY; + } + else if (initUnits) { + parse.call(this, initUnits); + this.scalar = initValue; + } + else { + parse.call(this, initValue); + } - try { - return Qty$1(value); - } - catch (e) { - return null; - } -} + // math with temperatures is very limited + if (this.denominator.join("*").indexOf("temp") >= 0) { + throw new QtyError("Cannot divide with temperatures"); + } + if (this.numerator.join("*").indexOf("temp") >= 0) { + if (this.numerator.length > 1) { + throw new QtyError("Cannot multiply by temperatures"); + } + if (!compareArray(this.denominator, UNITY_ARRAY)) { + throw new QtyError("Cannot divide with temperatures"); + } + } -function Qty$1(initValue, initUnits) { - assertValidConstructorArgs.apply(null, arguments); + this.initValue = initValue; + updateBaseScalar.call(this); - if (!(isQty(this))) { - return new Qty$1(initValue, initUnits); + if (this.isTemperature() && this.baseScalar < 0) { + throw new QtyError("Temperatures must not be less than absolute zero"); + } } - this.scalar = null; - this.baseScalar = null; - this.signature = null; - this._conversionCache = {}; - this.numerator = UNITY_ARRAY; - this.denominator = UNITY_ARRAY; - - if (isDefinitionObject(initValue)) { - this.scalar = initValue.scalar; - this.numerator = (initValue.numerator && initValue.numerator.length !== 0) ? initValue.numerator : UNITY_ARRAY; - this.denominator = (initValue.denominator && initValue.denominator.length !== 0) ? initValue.denominator : UNITY_ARRAY; - } - else if (initUnits) { - parse.call(this, initUnits); - this.scalar = initValue; - } - else { - parse.call(this, initValue); - } + Qty.prototype = { + // Properly set up constructor + constructor: Qty, + }; - // math with temperatures is very limited - if (this.denominator.join("*").indexOf("temp") >= 0) { - throw new QtyError("Cannot divide with temperatures"); - } - if (this.numerator.join("*").indexOf("temp") >= 0) { - if (this.numerator.length > 1) { - throw new QtyError("Cannot multiply by temperatures"); + /** + * Asserts constructor arguments are valid + * + * @param {*} value - Value to test + * @param {string} [units] - Optional units when value is passed as a number + * + * @throws {QtyError} if constructor arguments are invalid + */ + function assertValidConstructorArgs(value, units) { + if (units) { + if (!(isNumber(value) && isString(units))) { + throw new QtyError("Only number accepted as initialization value " + + "when units are explicitly provided"); + } } - if (!compareArray(this.denominator, UNITY_ARRAY)) { - throw new QtyError("Cannot divide with temperatures"); + else { + if (!(isString(value) || + isNumber(value) || + isQty(value) || + isDefinitionObject(value))) { + throw new QtyError("Only string, number or quantity accepted as " + + "single initialization value"); + } } } - this.initValue = initValue; - updateBaseScalar.call(this); - - if (this.isTemperature() && this.baseScalar < 0) { - throw new QtyError("Temperatures must not be less than absolute zero"); + /** + * Tests if a value is a Qty definition object + * + * @param {*} value - Value to test + * + * @returns {boolean} true if value is a definition object, false otherwise + */ + function isDefinitionObject(value) { + return value && typeof value === "object" && value.hasOwnProperty("scalar"); } -} - -Qty$1.prototype = { - // Properly set up constructor - constructor: Qty$1, -}; - -/** - * Asserts constructor arguments are valid - * - * @param {*} value - Value to test - * @param {string} [units] - Optional units when value is passed as a number - * - * @throws {QtyError} if constructor arguments are invalid - */ -function assertValidConstructorArgs(value, units) { - if (units) { - if (!(isNumber(value) && isString(units))) { - throw new QtyError("Only number accepted as initialization value " + - "when units are explicitly provided"); + + function updateBaseScalar() { + if (this.baseScalar) { + return this.baseScalar; } - } - else { - if (!(isString(value) || - isNumber(value) || - isQty(value) || - isDefinitionObject(value))) { - throw new QtyError("Only string, number or quantity accepted as " + - "single initialization value"); + if (this.isBase()) { + this.baseScalar = this.scalar; + this.signature = unitSignature.call(this); } + else { + var base = this.toBase(); + this.baseScalar = base.scalar; + this.signature = base.signature; + } + } + + var KINDS = { + "-312078": "elastance", + "-312058": "resistance", + "-312038": "inductance", + "-152058": "potential", + "-152040": "magnetism", + "-152038": "magnetism", + "-7997": "specific_volume", + "-79": "snap", + "-59": "jolt", + "-39": "acceleration", + "-38": "radiation", + "-20": "frequency", + "-19": "speed", + "-18": "viscosity", + "-17": "volumetric_flow", + "-1": "wavenumber", + "0": "unitless", + "1": "length", + "2": "area", + "3": "volume", + "20": "time", + "400": "temperature", + "7941": "yank", + "7942": "power", + "7959": "pressure", + "7961": "force", + "7962": "energy", + "7979": "viscosity", + "7981": "momentum", + "7982": "angular_momentum", + "7997": "density", + "7998": "area_density", + "8000": "mass", + "152020": "radiation_exposure", + "159999": "magnetism", + "160000": "current", + "160020": "charge", + "312058": "conductance", + "312078": "capacitance", + "3199980": "activity", + "3199997": "molar_concentration", + "3200000": "substance", + "63999998": "illuminance", + "64000000": "luminous_power", + "1280000000": "currency", + "25599999980": "information_rate", + "25600000000": "information", + "511999999980": "angular_velocity", + "512000000000": "angle" + }; + + /** + * Returns the list of available well-known kinds of units, e.g. + * "radiation" or "length". + * + * @returns {string[]} names of kinds of units + */ + function getKinds() { + return uniq(Object.keys(KINDS).map(function(knownSignature) { + return KINDS[knownSignature]; + })); } -} - -/** - * Tests if a value is a Qty definition object - * - * @param {*} value - Value to test - * - * @returns {boolean} true if value is a definition object, false otherwise - */ -function isDefinitionObject(value) { - return value && typeof value === "object" && value.hasOwnProperty("scalar"); -} - -function updateBaseScalar() { - if (this.baseScalar) { - return this.baseScalar; - } - if (this.isBase()) { - this.baseScalar = this.scalar; - this.signature = unitSignature.call(this); - } - else { - var base = this.toBase(); - this.baseScalar = base.scalar; - this.signature = base.signature; - } -} - -var KINDS = { - "-312078": "elastance", - "-312058": "resistance", - "-312038": "inductance", - "-152058": "potential", - "-152040": "magnetism", - "-152038": "magnetism", - "-7997": "specific_volume", - "-79": "snap", - "-59": "jolt", - "-39": "acceleration", - "-38": "radiation", - "-20": "frequency", - "-19": "speed", - "-18": "viscosity", - "-17": "volumetric_flow", - "-1": "wavenumber", - "0": "unitless", - "1": "length", - "2": "area", - "3": "volume", - "20": "time", - "400": "temperature", - "7941": "yank", - "7942": "power", - "7959": "pressure", - "7961": "force", - "7962": "energy", - "7979": "viscosity", - "7981": "momentum", - "7982": "angular_momentum", - "7997": "density", - "7998": "area_density", - "8000": "mass", - "152020": "radiation_exposure", - "159999": "magnetism", - "160000": "current", - "160020": "charge", - "312058": "conductance", - "312078": "capacitance", - "3199980": "activity", - "3199997": "molar_concentration", - "3200000": "substance", - "63999998": "illuminance", - "64000000": "luminous_power", - "1280000000": "currency", - "25599999980": "information_rate", - "25600000000": "information", - "511999999980": "angular_velocity", - "512000000000": "angle" -}; - -/** - * Returns the list of available well-known kinds of units, e.g. - * "radiation" or "length". - * - * @returns {string[]} names of kinds of units - */ -function getKinds() { - return uniq(Object.keys(KINDS).map(function(knownSignature) { - return KINDS[knownSignature]; - })); -} - -Qty$1.prototype.kind = function() { - return KINDS[this.signature.toString()]; -}; - -assign(Qty$1.prototype, { - isDegrees: function() { - // signature may not have been calculated yet - return (this.signature === null || this.signature === 400) && - this.numerator.length === 1 && - compareArray(this.denominator, UNITY_ARRAY) && - (this.numerator[0].match(//) || this.numerator[0].match(/<(kelvin|celsius|rankine|fahrenheit)>/)); - }, - - isTemperature: function() { - return this.isDegrees() && this.numerator[0].match(//); - } -}); - -function subtractTemperatures(lhs,rhs) { - var lhsUnits = lhs.units(); - var rhsConverted = rhs.to(lhsUnits); - var dstDegrees = Qty$1(getDegreeUnits(lhsUnits)); - return Qty$1({"scalar": lhs.scalar - rhsConverted.scalar, "numerator": dstDegrees.numerator, "denominator": dstDegrees.denominator}); -} - -function subtractTempDegrees(temp,deg) { - var tempDegrees = deg.to(getDegreeUnits(temp.units())); - return Qty$1({"scalar": temp.scalar - tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator}); -} - -function addTempDegrees(temp,deg) { - var tempDegrees = deg.to(getDegreeUnits(temp.units())); - return Qty$1({"scalar": temp.scalar + tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator}); -} - -function getDegreeUnits(units) { - if (units === "tempK") { - return "degK"; - } - else if (units === "tempC") { - return "degC"; - } - else if (units === "tempF") { - return "degF"; - } - else if (units === "tempR") { - return "degR"; - } - else { - throw new QtyError("Unknown type for temp conversion from: " + units); - } -} -function toDegrees(src,dst) { - var srcDegK = toDegK(src); - var dstUnits = dst.units(); - var dstScalar; + Qty.prototype.kind = function() { + return KINDS[this.signature.toString()]; + }; - if (dstUnits === "degK") { - dstScalar = srcDegK.scalar; - } - else if (dstUnits === "degC") { - dstScalar = srcDegK.scalar ; - } - else if (dstUnits === "degF") { - dstScalar = srcDegK.scalar * 9 / 5; - } - else if (dstUnits === "degR") { - dstScalar = srcDegK.scalar * 9 / 5; - } - else { - throw new QtyError("Unknown type for degree conversion to: " + dstUnits); - } + assign(Qty.prototype, { + isDegrees: function() { + // signature may not have been calculated yet + return (this.signature === null || this.signature === 400) && + this.numerator.length === 1 && + compareArray(this.denominator, UNITY_ARRAY) && + (this.numerator[0].match(//) || this.numerator[0].match(/<(kelvin|celsius|rankine|fahrenheit)>/)); + }, - return Qty$1({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator}); -} + isTemperature: function() { + return this.isDegrees() && this.numerator[0].match(//); + } + }); -function toDegK(qty) { - var units = qty.units(); - var q; - if (units.match(/(deg)[CFRK]/)) { - q = qty.baseScalar; - } - else if (units === "tempK") { - q = qty.scalar; - } - else if (units === "tempC") { - q = qty.scalar; + function subtractTemperatures(lhs,rhs) { + var lhsUnits = lhs.units(); + var rhsConverted = rhs.to(lhsUnits); + var dstDegrees = Qty(getDegreeUnits(lhsUnits)); + return Qty({"scalar": lhs.scalar - rhsConverted.scalar, "numerator": dstDegrees.numerator, "denominator": dstDegrees.denominator}); } - else if (units === "tempF") { - q = qty.scalar * 5 / 9; + + function subtractTempDegrees(temp,deg) { + var tempDegrees = deg.to(getDegreeUnits(temp.units())); + return Qty({"scalar": temp.scalar - tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator}); } - else if (units === "tempR") { - q = qty.scalar * 5 / 9; + + function addTempDegrees(temp,deg) { + var tempDegrees = deg.to(getDegreeUnits(temp.units())); + return Qty({"scalar": temp.scalar + tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator}); } - else { - throw new QtyError("Unknown type for temp conversion from: " + units); + + function getDegreeUnits(units) { + if (units === "tempK") { + return "degK"; + } + else if (units === "tempC") { + return "degC"; + } + else if (units === "tempF") { + return "degF"; + } + else if (units === "tempR") { + return "degR"; + } + else { + throw new QtyError("Unknown type for temp conversion from: " + units); + } } - return Qty$1({"scalar": q, "numerator": [""], "denominator": UNITY_ARRAY}); -} + function toDegrees(src,dst) { + var srcDegK = toDegK(src); + var dstUnits = dst.units(); + var dstScalar; -function toTemp(src,dst) { - var dstUnits = dst.units(); - var dstScalar; + if (dstUnits === "degK") { + dstScalar = srcDegK.scalar; + } + else if (dstUnits === "degC") { + dstScalar = srcDegK.scalar ; + } + else if (dstUnits === "degF") { + dstScalar = srcDegK.scalar * 9 / 5; + } + else if (dstUnits === "degR") { + dstScalar = srcDegK.scalar * 9 / 5; + } + else { + throw new QtyError("Unknown type for degree conversion to: " + dstUnits); + } - if (dstUnits === "tempK") { - dstScalar = src.baseScalar; - } - else if (dstUnits === "tempC") { - dstScalar = src.baseScalar - 273.15; - } - else if (dstUnits === "tempF") { - dstScalar = (src.baseScalar * 9 / 5) - 459.67; - } - else if (dstUnits === "tempR") { - dstScalar = src.baseScalar * 9 / 5; - } - else { - throw new QtyError("Unknown type for temp conversion to: " + dstUnits); + return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator}); } - return Qty$1({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator}); -} + function toDegK(qty) { + var units = qty.units(); + var q; + if (units.match(/(deg)[CFRK]/)) { + q = qty.baseScalar; + } + else if (units === "tempK") { + q = qty.scalar; + } + else if (units === "tempC") { + q = qty.scalar; + } + else if (units === "tempF") { + q = qty.scalar * 5 / 9; + } + else if (units === "tempR") { + q = qty.scalar * 5 / 9; + } + else { + throw new QtyError("Unknown type for temp conversion from: " + units); + } -function toTempK(qty) { - var units = qty.units(); - var q; - if (units.match(/(deg)[CFRK]/)) { - q = qty.baseScalar; - } - else if (units === "tempK") { - q = qty.scalar; - } - else if (units === "tempC") { - q = qty.scalar + 273.15; - } - else if (units === "tempF") { - q = (qty.scalar + 459.67) * 5 / 9; - } - else if (units === "tempR") { - q = qty.scalar * 5 / 9; + return Qty({"scalar": q, "numerator": [""], "denominator": UNITY_ARRAY}); } - else { - throw new QtyError("Unknown type for temp conversion from: " + units); - } - - return Qty$1({"scalar": q, "numerator": [""], "denominator": UNITY_ARRAY}); -} -assign(Qty$1.prototype, { - /** - * Converts to other compatible units. - * Instance's converted quantities are cached for faster subsequent calls. - * - * @param {(string|Qty)} other - Target units as string or retrieved from - * other Qty instance (scalar is ignored) - * - * @returns {Qty} New converted Qty instance with target units - * - * @throws {QtyError} if target units are incompatible - * - * @example - * var weight = Qty("25 kg"); - * weight.to("lb"); // => Qty("55.11556554621939 lbs"); - * weight.to(Qty("3 g")); // => Qty("25000 g"); // scalar of passed Qty is ignored - */ - to: function(other) { - var cached, target; + function toTemp(src,dst) { + var dstUnits = dst.units(); + var dstScalar; - if (other === undefined || other === null) { - return this; + if (dstUnits === "tempK") { + dstScalar = src.baseScalar; } - - if (!isString(other)) { - return this.to(other.units()); + else if (dstUnits === "tempC") { + dstScalar = src.baseScalar - 273.15; } - - cached = this._conversionCache[other]; - if (cached) { - return cached; + else if (dstUnits === "tempF") { + dstScalar = (src.baseScalar * 9 / 5) - 459.67; + } + else if (dstUnits === "tempR") { + dstScalar = src.baseScalar * 9 / 5; + } + else { + throw new QtyError("Unknown type for temp conversion to: " + dstUnits); } - // Instantiating target to normalize units - target = Qty$1(other); - if (target.units() === this.units()) { - return this; + return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator}); + } + + function toTempK(qty) { + var units = qty.units(); + var q; + if (units.match(/(deg)[CFRK]/)) { + q = qty.baseScalar; + } + else if (units === "tempK") { + q = qty.scalar; + } + else if (units === "tempC") { + q = qty.scalar + 273.15; } + else if (units === "tempF") { + q = (qty.scalar + 459.67) * 5 / 9; + } + else if (units === "tempR") { + q = qty.scalar * 5 / 9; + } + else { + throw new QtyError("Unknown type for temp conversion from: " + units); + } + + return Qty({"scalar": q, "numerator": [""], "denominator": UNITY_ARRAY}); + } + + assign(Qty.prototype, { + /** + * Converts to other compatible units. + * Instance's converted quantities are cached for faster subsequent calls. + * + * @param {(string|Qty)} other - Target units as string or retrieved from + * other Qty instance (scalar is ignored) + * + * @returns {Qty} New converted Qty instance with target units + * + * @throws {QtyError} if target units are incompatible + * + * @example + * var weight = Qty("25 kg"); + * weight.to("lb"); // => Qty("55.11556554621939 lbs"); + * weight.to(Qty("3 g")); // => Qty("25000 g"); // scalar of passed Qty is ignored + */ + to: function(other) { + var cached, target; + + if (other === undefined || other === null) { + return this; + } - if (!this.isCompatible(target)) { - if (this.isInverse(target)) { - target = this.inverse().to(other); + if (!isString(other)) { + return this.to(other.units()); } - else { - throwIncompatibleUnits(this.units(), target.units()); + + cached = this._conversionCache[other]; + if (cached) { + return cached; } - } - else { - if (target.isTemperature()) { - target = toTemp(this,target); + + // Instantiating target to normalize units + target = Qty(other); + if (target.units() === this.units()) { + return this; } - else if (target.isDegrees()) { - target = toDegrees(this,target); + + if (!this.isCompatible(target)) { + if (this.isInverse(target)) { + target = this.inverse().to(other); + } + else { + throwIncompatibleUnits(this.units(), target.units()); + } } else { - var q = divSafe(this.baseScalar, target.baseScalar); - target = Qty$1({"scalar": q, "numerator": target.numerator, "denominator": target.denominator}); + if (target.isTemperature()) { + target = toTemp(this,target); + } + else if (target.isDegrees()) { + target = toDegrees(this,target); + } + else { + var q = divSafe(this.baseScalar, target.baseScalar); + target = Qty({"scalar": q, "numerator": target.numerator, "denominator": target.denominator}); + } + } + + this._conversionCache[other] = target; + return target; + }, + + // convert to base SI units + // results of the conversion are cached so subsequent calls to this will be fast + toBase: function() { + if (this.isBase()) { + return this; } - } - this._conversionCache[other] = target; - return target; - }, + if (this.isTemperature()) { + return toTempK(this); + } + + var cached = baseUnitCache[this.units()]; + if (!cached) { + cached = toBaseUnits(this.numerator,this.denominator); + baseUnitCache[this.units()] = cached; + } + return cached.mul(this.scalar); + }, + + // Converts the unit back to a float if it is unitless. Otherwise raises an exception + toFloat: function() { + if (this.isUnitless()) { + return this.scalar; + } + throw new QtyError("Can't convert to Float unless unitless. Use Unit#scalar"); + }, + + /** + * Returns the nearest multiple of quantity passed as + * precision + * + * @param {(Qty|string|number)} precQuantity - Quantity, string formated + * quantity or number as expected precision + * + * @returns {Qty} Nearest multiple of precQuantity + * + * @example + * Qty('5.5 ft').toPrec('2 ft'); // returns 6 ft + * Qty('0.8 cu').toPrec('0.25 cu'); // returns 0.75 cu + * Qty('6.3782 m').toPrec('cm'); // returns 6.38 m + * Qty('1.146 MPa').toPrec('0.1 bar'); // returns 1.15 MPa + * + */ + toPrec: function(precQuantity) { + if (isString(precQuantity)) { + precQuantity = Qty(precQuantity); + } + if (isNumber(precQuantity)) { + precQuantity = Qty(precQuantity + " " + this.units()); + } - // convert to base SI units - // results of the conversion are cached so subsequent calls to this will be fast - toBase: function() { - if (this.isBase()) { - return this; - } + if (!this.isUnitless()) { + precQuantity = precQuantity.to(this.units()); + } + else if (!precQuantity.isUnitless()) { + throwIncompatibleUnits(this.units(), precQuantity.units()); + } - if (this.isTemperature()) { - return toTempK(this); - } + if (precQuantity.scalar === 0) { + throw new QtyError("Divide by zero"); + } - var cached = baseUnitCache[this.units()]; - if (!cached) { - cached = toBaseUnits(this.numerator,this.denominator); - baseUnitCache[this.units()] = cached; - } - return cached.mul(this.scalar); - }, + var precRoundedResult = mulSafe(Math.round(this.scalar / precQuantity.scalar), + precQuantity.scalar); - // Converts the unit back to a float if it is unitless. Otherwise raises an exception - toFloat: function() { - if (this.isUnitless()) { - return this.scalar; + return Qty(precRoundedResult + this.units()); } - throw new QtyError("Can't convert to Float unless unitless. Use Unit#scalar"); - }, + }); /** - * Returns the nearest multiple of quantity passed as - * precision + * Configures and returns a fast function to convert + * Number values from units to others. + * Useful to efficiently convert large array of values + * with same units into others with iterative methods. + * Does not take care of rounding issues. + * + * @param {string} srcUnits Units of values to convert + * @param {string} dstUnits Units to convert to * - * @param {(Qty|string|number)} precQuantity - Quantity, string formated - * quantity or number as expected precision + * @returns {Function} Converting function accepting Number value + * and returning converted value * - * @returns {Qty} Nearest multiple of precQuantity + * @throws "Incompatible units" if units are incompatible * * @example - * Qty('5.5 ft').toPrec('2 ft'); // returns 6 ft - * Qty('0.8 cu').toPrec('0.25 cu'); // returns 0.75 cu - * Qty('6.3782 m').toPrec('cm'); // returns 6.38 m - * Qty('1.146 MPa').toPrec('0.1 bar'); // returns 1.15 MPa + * // Converting large array of numbers with the same units + * // into other units + * var converter = Qty.swiftConverter("m/h", "ft/s"); + * var convertedSerie = largeSerie.map(converter); * */ - toPrec: function(precQuantity) { - if (isString(precQuantity)) { - precQuantity = Qty$1(precQuantity); - } - if (isNumber(precQuantity)) { - precQuantity = Qty$1(precQuantity + " " + this.units()); - } + function swiftConverter(srcUnits, dstUnits) { + var srcQty = Qty(srcUnits); + var dstQty = Qty(dstUnits); - if (!this.isUnitless()) { - precQuantity = precQuantity.to(this.units()); + if (srcQty.eq(dstQty)) { + return identity; } - else if (!precQuantity.isUnitless()) { - throwIncompatibleUnits(this.units(), precQuantity.units()); - } - - if (precQuantity.scalar === 0) { - throw new QtyError("Divide by zero"); - } - - var precRoundedResult = mulSafe(Math.round(this.scalar / precQuantity.scalar), - precQuantity.scalar); - return Qty$1(precRoundedResult + this.units()); - } -}); - -/** - * Configures and returns a fast function to convert - * Number values from units to others. - * Useful to efficiently convert large array of values - * with same units into others with iterative methods. - * Does not take care of rounding issues. - * - * @param {string} srcUnits Units of values to convert - * @param {string} dstUnits Units to convert to - * - * @returns {Function} Converting function accepting Number value - * and returning converted value - * - * @throws "Incompatible units" if units are incompatible - * - * @example - * // Converting large array of numbers with the same units - * // into other units - * var converter = Qty.swiftConverter("m/h", "ft/s"); - * var convertedSerie = largeSerie.map(converter); - * - */ -function swiftConverter(srcUnits, dstUnits) { - var srcQty = Qty$1(srcUnits); - var dstQty = Qty$1(dstUnits); - - if (srcQty.eq(dstQty)) { - return identity; - } - - var convert; - if (!srcQty.isTemperature()) { - convert = function(value) { - return value * srcQty.baseScalar / dstQty.baseScalar; - }; - } - else { - convert = function(value) { - // TODO Not optimized - return srcQty.mul(value).to(dstQty).scalar; - }; - } - - return function converter(value) { - var i, - length, - result; - if (!Array.isArray(value)) { - return convert(value); + var convert; + if (!srcQty.isTemperature()) { + convert = function(value) { + return value * srcQty.baseScalar / dstQty.baseScalar; + }; } else { - length = value.length; - result = []; - for (i = 0; i < length; i++) { - result.push(convert(value[i])); - } - return result; - } - }; -} - -var baseUnitCache = {}; - -function toBaseUnits (numerator,denominator) { - var num = []; - var den = []; - var q = 1; - var unit; - for (var i = 0; i < numerator.length; i++) { - unit = numerator[i]; - if (PREFIX_VALUES[unit]) { - // workaround to fix - // 0.1 * 0.1 => 0.010000000000000002 - q = mulSafe(q, PREFIX_VALUES[unit]); + convert = function(value) { + // TODO Not optimized + return srcQty.mul(value).to(dstQty).scalar; + }; } - else { - if (UNIT_VALUES[unit]) { - q *= UNIT_VALUES[unit].scalar; - if (UNIT_VALUES[unit].numerator) { - num.push(UNIT_VALUES[unit].numerator); - } - if (UNIT_VALUES[unit].denominator) { - den.push(UNIT_VALUES[unit].denominator); + return function converter(value) { + var i, + length, + result; + if (!Array.isArray(value)) { + return convert(value); + } + else { + length = value.length; + result = []; + for (i = 0; i < length; i++) { + result.push(convert(value[i])); } + return result; } - } + }; } - for (var j = 0; j < denominator.length; j++) { - unit = denominator[j]; - if (PREFIX_VALUES[unit]) { - q /= PREFIX_VALUES[unit]; - } - else { - if (UNIT_VALUES[unit]) { - q /= UNIT_VALUES[unit].scalar; - if (UNIT_VALUES[unit].numerator) { - den.push(UNIT_VALUES[unit].numerator); + var baseUnitCache = {}; + + function toBaseUnits (numerator,denominator) { + var num = []; + var den = []; + var q = 1; + var unit; + for (var i = 0; i < numerator.length; i++) { + unit = numerator[i]; + if (PREFIX_VALUES[unit]) { + // workaround to fix + // 0.1 * 0.1 => 0.010000000000000002 + q = mulSafe(q, PREFIX_VALUES[unit]); + } + else { + if (UNIT_VALUES[unit]) { + q *= UNIT_VALUES[unit].scalar; + + if (UNIT_VALUES[unit].numerator) { + num.push(UNIT_VALUES[unit].numerator); + } + if (UNIT_VALUES[unit].denominator) { + den.push(UNIT_VALUES[unit].denominator); + } } - if (UNIT_VALUES[unit].denominator) { - num.push(UNIT_VALUES[unit].denominator); + } + } + for (var j = 0; j < denominator.length; j++) { + unit = denominator[j]; + if (PREFIX_VALUES[unit]) { + q /= PREFIX_VALUES[unit]; + } + else { + if (UNIT_VALUES[unit]) { + q /= UNIT_VALUES[unit].scalar; + + if (UNIT_VALUES[unit].numerator) { + den.push(UNIT_VALUES[unit].numerator); + } + if (UNIT_VALUES[unit].denominator) { + num.push(UNIT_VALUES[unit].denominator); + } } } } - } - // Flatten - num = num.reduce(function(a,b) { - return a.concat(b); - }, []); - den = den.reduce(function(a,b) { - return a.concat(b); - }, []); + // Flatten + num = num.reduce(function(a,b) { + return a.concat(b); + }, []); + den = den.reduce(function(a,b) { + return a.concat(b); + }, []); - return Qty$1({"scalar": q, "numerator": num, "denominator": den}); -} + return Qty({"scalar": q, "numerator": num, "denominator": den}); + } -Qty$1.parse = globalParse; + Qty.parse = globalParse; -Qty$1.getUnits = getUnits; -Qty$1.getAliases = getAliases; + Qty.getUnits = getUnits; + Qty.getAliases = getAliases; -Qty$1.mulSafe = mulSafe; -Qty$1.divSafe = divSafe; + Qty.mulSafe = mulSafe; + Qty.divSafe = divSafe; -Qty$1.getKinds = getKinds; + Qty.getKinds = getKinds; -Qty$1.swiftConverter = swiftConverter; + Qty.swiftConverter = swiftConverter; -Qty$1.Error = QtyError; + Qty.Error = QtyError; -assign(Qty$1.prototype, { - // Returns new instance with units of this - add: function(other) { - if (isString(other)) { - other = Qty$1(other); - } + assign(Qty.prototype, { + // Returns new instance with units of this + add: function(other) { + if (isString(other)) { + other = Qty(other); + } - if (!this.isCompatible(other)) { - throwIncompatibleUnits(this.units(), other.units()); - } + if (!this.isCompatible(other)) { + throwIncompatibleUnits(this.units(), other.units()); + } - if (this.isTemperature() && other.isTemperature()) { - throw new QtyError("Cannot add two temperatures"); - } - else if (this.isTemperature()) { - return addTempDegrees(this, other); - } - else if (other.isTemperature()) { - return addTempDegrees(other, this); - } + if (this.isTemperature() && other.isTemperature()) { + throw new QtyError("Cannot add two temperatures"); + } + else if (this.isTemperature()) { + return addTempDegrees(this, other); + } + else if (other.isTemperature()) { + return addTempDegrees(other, this); + } - return Qty$1({"scalar": this.scalar + other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator}); - }, + return Qty({"scalar": this.scalar + other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator}); + }, - sub: function(other) { - if (isString(other)) { - other = Qty$1(other); - } + sub: function(other) { + if (isString(other)) { + other = Qty(other); + } - if (!this.isCompatible(other)) { - throwIncompatibleUnits(this.units(), other.units()); - } + if (!this.isCompatible(other)) { + throwIncompatibleUnits(this.units(), other.units()); + } - if (this.isTemperature() && other.isTemperature()) { - return subtractTemperatures(this,other); - } - else if (this.isTemperature()) { - return subtractTempDegrees(this,other); - } - else if (other.isTemperature()) { - throw new QtyError("Cannot subtract a temperature from a differential degree unit"); - } + if (this.isTemperature() && other.isTemperature()) { + return subtractTemperatures(this,other); + } + else if (this.isTemperature()) { + return subtractTempDegrees(this,other); + } + else if (other.isTemperature()) { + throw new QtyError("Cannot subtract a temperature from a differential degree unit"); + } - return Qty$1({"scalar": this.scalar - other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator}); - }, + return Qty({"scalar": this.scalar - other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator}); + }, - mul: function(other) { - if (isNumber(other)) { - return Qty$1({"scalar": mulSafe(this.scalar, other), "numerator": this.numerator, "denominator": this.denominator}); - } - else if (isString(other)) { - other = Qty$1(other); - } + mul: function(other) { + if (isNumber(other)) { + return Qty({"scalar": mulSafe(this.scalar, other), "numerator": this.numerator, "denominator": this.denominator}); + } + else if (isString(other)) { + other = Qty(other); + } - if ((this.isTemperature()||other.isTemperature()) && !(this.isUnitless()||other.isUnitless())) { - throw new QtyError("Cannot multiply by temperatures"); - } + if ((this.isTemperature()||other.isTemperature()) && !(this.isUnitless()||other.isUnitless())) { + throw new QtyError("Cannot multiply by temperatures"); + } - // Quantities should be multiplied with same units if compatible, with base units else - var op1 = this; - var op2 = other; + // Quantities should be multiplied with same units if compatible, with base units else + var op1 = this; + var op2 = other; - // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den - // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC" - if (op1.isCompatible(op2) && op1.signature !== 400) { - op2 = op2.to(op1); - } - var numden = cleanTerms(op1.numerator.concat(op2.numerator), op1.denominator.concat(op2.denominator)); + // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den + // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC" + if (op1.isCompatible(op2) && op1.signature !== 400) { + op2 = op2.to(op1); + } + var numden = cleanTerms(op1.numerator.concat(op2.numerator), op1.denominator.concat(op2.denominator)); - return Qty$1({"scalar": mulSafe(op1.scalar, op2.scalar), "numerator": numden[0], "denominator": numden[1]}); - }, + return Qty({"scalar": mulSafe(op1.scalar, op2.scalar), "numerator": numden[0], "denominator": numden[1]}); + }, - div: function(other) { - if (isNumber(other)) { - if (other === 0) { - throw new QtyError("Divide by zero"); + div: function(other) { + if (isNumber(other)) { + if (other === 0) { + throw new QtyError("Divide by zero"); + } + return Qty({"scalar": this.scalar / other, "numerator": this.numerator, "denominator": this.denominator}); + } + else if (isString(other)) { + other = Qty(other); } - return Qty$1({"scalar": this.scalar / other, "numerator": this.numerator, "denominator": this.denominator}); - } - else if (isString(other)) { - other = Qty$1(other); - } - if (other.scalar === 0) { - throw new QtyError("Divide by zero"); - } + if (other.scalar === 0) { + throw new QtyError("Divide by zero"); + } - if (other.isTemperature()) { - throw new QtyError("Cannot divide with temperatures"); - } - else if (this.isTemperature() && !other.isUnitless()) { - throw new QtyError("Cannot divide with temperatures"); - } + if (other.isTemperature()) { + throw new QtyError("Cannot divide with temperatures"); + } + else if (this.isTemperature() && !other.isUnitless()) { + throw new QtyError("Cannot divide with temperatures"); + } - // Quantities should be multiplied with same units if compatible, with base units else - var op1 = this; - var op2 = other; + // Quantities should be multiplied with same units if compatible, with base units else + var op1 = this; + var op2 = other; - // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den - // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC" - if (op1.isCompatible(op2) && op1.signature !== 400) { - op2 = op2.to(op1); - } - var numden = cleanTerms(op1.numerator.concat(op2.denominator), op1.denominator.concat(op2.numerator)); + // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den + // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC" + if (op1.isCompatible(op2) && op1.signature !== 400) { + op2 = op2.to(op1); + } + var numden = cleanTerms(op1.numerator.concat(op2.denominator), op1.denominator.concat(op2.numerator)); - return Qty$1({"scalar": op1.scalar / op2.scalar, "numerator": numden[0], "denominator": numden[1]}); - }, + return Qty({"scalar": op1.scalar / op2.scalar, "numerator": numden[0], "denominator": numden[1]}); + }, - // Returns a Qty that is the inverse of this Qty, - inverse: function() { - if (this.isTemperature()) { - throw new QtyError("Cannot divide with temperatures"); - } - if (this.scalar === 0) { - throw new QtyError("Divide by zero"); + // Returns a Qty that is the inverse of this Qty, + inverse: function() { + if (this.isTemperature()) { + throw new QtyError("Cannot divide with temperatures"); + } + if (this.scalar === 0) { + throw new QtyError("Divide by zero"); + } + return Qty({"scalar": 1 / this.scalar, "numerator": this.denominator, "denominator": this.numerator}); } - return Qty$1({"scalar": 1 / this.scalar, "numerator": this.denominator, "denominator": this.numerator}); - } -}); - -function cleanTerms(num, den) { - num = num.filter(function(val) { - return val !== UNITY; - }); - den = den.filter(function(val) { - return val !== UNITY; }); - var combined = {}; + function cleanTerms(num, den) { + num = num.filter(function(val) { + return val !== UNITY; + }); + den = den.filter(function(val) { + return val !== UNITY; + }); + + var combined = {}; - var k; - for (var i = 0; i < num.length; i++) { - if (PREFIX_VALUES[num[i]]) { - k = [num[i], num[i + 1]]; - i++; - } - else { - k = num[i]; - } - if (k && k !== UNITY) { - if (combined[k]) { - combined[k][0]++; + var k; + for (var i = 0; i < num.length; i++) { + if (PREFIX_VALUES[num[i]]) { + k = [num[i], num[i + 1]]; + i++; } else { - combined[k] = [1, k]; + k = num[i]; + } + if (k && k !== UNITY) { + if (combined[k]) { + combined[k][0]++; + } + else { + combined[k] = [1, k]; + } } } - } - for (var j = 0; j < den.length; j++) { - if (PREFIX_VALUES[den[j]]) { - k = [den[j], den[j + 1]]; - j++; - } - else { - k = den[j]; - } - if (k && k !== UNITY) { - if (combined[k]) { - combined[k][0]--; + for (var j = 0; j < den.length; j++) { + if (PREFIX_VALUES[den[j]]) { + k = [den[j], den[j + 1]]; + j++; } else { - combined[k] = [-1, k]; + k = den[j]; + } + if (k && k !== UNITY) { + if (combined[k]) { + combined[k][0]--; + } + else { + combined[k] = [-1, k]; + } } } - } - num = []; - den = []; + num = []; + den = []; - for (var prop in combined) { - if (combined.hasOwnProperty(prop)) { - var item = combined[prop]; - var n; - if (item[0] > 0) { - for (n = 0; n < item[0]; n++) { - num.push(item[1]); + for (var prop in combined) { + if (combined.hasOwnProperty(prop)) { + var item = combined[prop]; + var n; + if (item[0] > 0) { + for (n = 0; n < item[0]; n++) { + num.push(item[1]); + } } - } - else if (item[0] < 0) { - for (n = 0; n < -item[0]; n++) { - den.push(item[1]); + else if (item[0] < 0) { + for (n = 0; n < -item[0]; n++) { + den.push(item[1]); + } } } } - } - - if (num.length === 0) { - num = UNITY_ARRAY; - } - if (den.length === 0) { - den = UNITY_ARRAY; - } - // Flatten - num = num.reduce(function(a,b) { - return a.concat(b); - }, []); - den = den.reduce(function(a,b) { - return a.concat(b); - }, []); - - return [num, den]; -} - -assign(Qty$1.prototype, { - eq: function(other) { - return this.compareTo(other) === 0; - }, - - lt: function(other) { - return this.compareTo(other) === -1; - }, - - lte: function(other) { - return this.eq(other) || this.lt(other); - }, - - gt: function(other) { - return this.compareTo(other) === 1; - }, - - gte: function(other) { - return this.eq(other) || this.gt(other); - }, - - // Compare two Qty objects. Throws an exception if they are not of compatible types. - // Comparisons are done based on the value of the quantity in base SI units. - // - // NOTE: We cannot compare inverses as that breaks the general compareTo contract: - // if a.compareTo(b) < 0 then b.compareTo(a) > 0 - // if a.compareTo(b) == 0 then b.compareTo(a) == 0 - // - // Since "10S" == ".1ohm" (10 > .1) and "10ohm" == ".1S" (10 > .1) - // Qty("10S").inverse().compareTo("10ohm") == -1 - // Qty("10ohm").inverse().compareTo("10S") == -1 - // - // If including inverses in the sort is needed, I suggest writing: Qty.sort(qtyArray,units) - compareTo: function(other) { - if (isString(other)) { - return this.compareTo(Qty$1(other)); - } - if (!this.isCompatible(other)) { - throwIncompatibleUnits(this.units(), other.units()); - } - if (this.baseScalar < other.baseScalar) { - return -1; - } - else if (this.baseScalar === other.baseScalar) { - return 0; + if (num.length === 0) { + num = UNITY_ARRAY; } - else if (this.baseScalar > other.baseScalar) { - return 1; + if (den.length === 0) { + den = UNITY_ARRAY; } - }, - // Return true if quantities and units match - // Unit("100 cm").same(Unit("100 cm")) # => true - // Unit("100 cm").same(Unit("1 m")) # => false - same: function(other) { - return (this.scalar === other.scalar) && (this.units() === other.units()); + // Flatten + num = num.reduce(function(a,b) { + return a.concat(b); + }, []); + den = den.reduce(function(a,b) { + return a.concat(b); + }, []); + + return [num, den]; } -}); - -assign(Qty$1.prototype, { - // returns true if no associated units - // false, even if the units are "unitless" like 'radians, each, etc' - isUnitless: function() { - return [this.numerator, this.denominator].every(function(item) { - return compareArray(item, UNITY_ARRAY); - }); - }, - /* - check to see if units are compatible, but not the scalar part - this check is done by comparing signatures for performance reasons - if passed a string, it will create a unit object with the string and then do the comparison - this permits a syntax like: - unit =~ "mm" - if you want to do a regexp on the unit string do this ... - unit.units =~ /regexp/ - */ - isCompatible: function(other) { - if (isString(other)) { - return this.isCompatible(Qty$1(other)); - } + assign(Qty.prototype, { + eq: function(other) { + return this.compareTo(other) === 0; + }, - if (!(isQty(other))) { - return false; - } + lt: function(other) { + return this.compareTo(other) === -1; + }, - if (other.signature !== undefined) { - return this.signature === other.signature; - } - else { - return false; - } - }, + lte: function(other) { + return this.eq(other) || this.lt(other); + }, - /* - check to see if units are inverse of each other, but not the scalar part - this check is done by comparing signatures for performance reasons - if passed a string, it will create a unit object with the string and then do the comparison - this permits a syntax like: - unit =~ "mm" - if you want to do a regexp on the unit string do this ... - unit.units =~ /regexp/ - */ - isInverse: function(other) { - return this.inverse().isCompatible(other); - }, + gt: function(other) { + return this.compareTo(other) === 1; + }, - // Returns 'true' if the Unit is represented in base units - isBase: function() { - if (this._isBase !== undefined) { - return this._isBase; - } - if (this.isDegrees() && this.numerator[0].match(/<(kelvin|temp-K)>/)) { - this._isBase = true; - return this._isBase; - } + gte: function(other) { + return this.eq(other) || this.gt(other); + }, - this.numerator.concat(this.denominator).forEach(function(item) { - if (item !== UNITY && BASE_UNITS.indexOf(item) === -1 ) { - this._isBase = false; + // Compare two Qty objects. Throws an exception if they are not of compatible types. + // Comparisons are done based on the value of the quantity in base SI units. + // + // NOTE: We cannot compare inverses as that breaks the general compareTo contract: + // if a.compareTo(b) < 0 then b.compareTo(a) > 0 + // if a.compareTo(b) == 0 then b.compareTo(a) == 0 + // + // Since "10S" == ".1ohm" (10 > .1) and "10ohm" == ".1S" (10 > .1) + // Qty("10S").inverse().compareTo("10ohm") == -1 + // Qty("10ohm").inverse().compareTo("10S") == -1 + // + // If including inverses in the sort is needed, I suggest writing: Qty.sort(qtyArray,units) + compareTo: function(other) { + if (isString(other)) { + return this.compareTo(Qty(other)); } - }, this); - if (this._isBase === false) { - return this._isBase; + if (!this.isCompatible(other)) { + throwIncompatibleUnits(this.units(), other.units()); + } + if (this.baseScalar < other.baseScalar) { + return -1; + } + else if (this.baseScalar === other.baseScalar) { + return 0; + } + else if (this.baseScalar > other.baseScalar) { + return 1; + } + }, + + // Return true if quantities and units match + // Unit("100 cm").same(Unit("100 cm")) # => true + // Unit("100 cm").same(Unit("1 m")) # => false + same: function(other) { + return (this.scalar === other.scalar) && (this.units() === other.units()); } - this._isBase = true; - return this._isBase; - } -}); + }); -function NestedMap() {} + assign(Qty.prototype, { + // returns true if no associated units + // false, even if the units are "unitless" like 'radians, each, etc' + isUnitless: function() { + return [this.numerator, this.denominator].every(function(item) { + return compareArray(item, UNITY_ARRAY); + }); + }, -NestedMap.prototype.get = function(keys) { + /* + check to see if units are compatible, but not the scalar part + this check is done by comparing signatures for performance reasons + if passed a string, it will create a unit object with the string and then do the comparison + this permits a syntax like: + unit =~ "mm" + if you want to do a regexp on the unit string do this ... + unit.units =~ /regexp/ + */ + isCompatible: function(other) { + if (isString(other)) { + return this.isCompatible(Qty(other)); + } - // Allows to pass key1, key2, ... instead of [key1, key2, ...] - if (arguments.length > 1) { - // Slower with Firefox but faster with Chrome than - // Array.prototype.slice.call(arguments) - // See http://jsperf.com/array-apply-versus-array-prototype-slice-call - keys = Array.apply(null, arguments); - } + if (!(isQty(other))) { + return false; + } - return keys.reduce(function(map, key, index) { - if (map) { + if (other.signature !== undefined) { + return this.signature === other.signature; + } + else { + return false; + } + }, - var childMap = map[key]; + /* + check to see if units are inverse of each other, but not the scalar part + this check is done by comparing signatures for performance reasons + if passed a string, it will create a unit object with the string and then do the comparison + this permits a syntax like: + unit =~ "mm" + if you want to do a regexp on the unit string do this ... + unit.units =~ /regexp/ + */ + isInverse: function(other) { + return this.inverse().isCompatible(other); + }, - if (index === keys.length - 1) { - return childMap ? childMap.data : undefined; + // Returns 'true' if the Unit is represented in base units + isBase: function() { + if (this._isBase !== undefined) { + return this._isBase; } - else { - return childMap; + if (this.isDegrees() && this.numerator[0].match(/<(kelvin|temp-K)>/)) { + this._isBase = true; + return this._isBase; } - } - }, - this); -}; -NestedMap.prototype.set = function(keys, value) { + this.numerator.concat(this.denominator).forEach(function(item) { + if (item !== UNITY && BASE_UNITS.indexOf(item) === -1 ) { + this._isBase = false; + } + }, this); + if (this._isBase === false) { + return this._isBase; + } + this._isBase = true; + return this._isBase; + } + }); - if (arguments.length > 2) { - keys = Array.prototype.slice.call(arguments, 0, -1); - value = arguments[arguments.length - 1]; - } + function NestedMap() {} - return keys.reduce(function(map, key, index) { + NestedMap.prototype.get = function(keys) { - var childMap = map[key]; - if (childMap === undefined) { - childMap = map[key] = {}; + // Allows to pass key1, key2, ... instead of [key1, key2, ...] + if (arguments.length > 1) { + // Slower with Firefox but faster with Chrome than + // Array.prototype.slice.call(arguments) + // See http://jsperf.com/array-apply-versus-array-prototype-slice-call + keys = Array.apply(null, arguments); } - if (index === keys.length - 1) { - childMap.data = value; - return value; - } - else { - return childMap; - } - }, this); -}; - -/** - * Default formatter - * - * @param {number} scalar - * @param {string} units - * - * @returns {string} formatted result - */ -function defaultFormatter(scalar, units) { - return (scalar + " " + units).trim(); -} - -/** - * - * Configurable Qty default formatter - * - * @type {function} - * - * @param {number} scalar - * @param {string} units - * - * @returns {string} formatted result - */ -Qty$1.formatter = defaultFormatter; - -assign(Qty$1.prototype, { - - // returns the 'unit' part of the Unit object without the scalar - units: function() { - if (this._units !== undefined) { - return this._units; - } + return keys.reduce(function(map, key, index) { + if (map) { - var numIsUnity = compareArray(this.numerator, UNITY_ARRAY), - denIsUnity = compareArray(this.denominator, UNITY_ARRAY); - if (numIsUnity && denIsUnity) { - this._units = ""; - return this._units; + var childMap = map[key]; + + if (index === keys.length - 1) { + return childMap ? childMap.data : undefined; + } + else { + return childMap; + } + } + }, + this); + }; + + NestedMap.prototype.set = function(keys, value) { + + if (arguments.length > 2) { + keys = Array.prototype.slice.call(arguments, 0, -1); + value = arguments[arguments.length - 1]; } - var numUnits = stringifyUnits(this.numerator), - denUnits = stringifyUnits(this.denominator); - this._units = numUnits + (denIsUnity ? "" : ("/" + denUnits)); - return this._units; - }, + return keys.reduce(function(map, key, index) { + + var childMap = map[key]; + if (childMap === undefined) { + childMap = map[key] = {}; + } + + if (index === keys.length - 1) { + childMap.data = value; + return value; + } + else { + return childMap; + } + }, this); + }; /** - * Stringifies the quantity - * Deprecation notice: only units parameter is supported. - * - * @param {(number|string|Qty)} targetUnitsOrMaxDecimalsOrPrec - - * target units if string, - * max number of decimals if number, - * passed to #toPrec before converting if Qty + * Default formatter * - * @param {number=} maxDecimals - Maximum number of decimals of - * formatted output + * @param {number} scalar + * @param {string} units * - * @returns {string} reparseable quantity as string + * @returns {string} formatted result */ - toString: function(targetUnitsOrMaxDecimalsOrPrec, maxDecimals) { - var targetUnits; - if (isNumber(targetUnitsOrMaxDecimalsOrPrec)) { - targetUnits = this.units(); - maxDecimals = targetUnitsOrMaxDecimalsOrPrec; - } - else if (isString(targetUnitsOrMaxDecimalsOrPrec)) { - targetUnits = targetUnitsOrMaxDecimalsOrPrec; - } - else if (isQty(targetUnitsOrMaxDecimalsOrPrec)) { - return this.toPrec(targetUnitsOrMaxDecimalsOrPrec).toString(maxDecimals); - } - - var out = this.to(targetUnits); - - var outScalar = maxDecimals !== undefined ? round(out.scalar, maxDecimals) : out.scalar; - out = (outScalar + " " + out.units()).trim(); - return out; - }, + function defaultFormatter(scalar, units) { + return (scalar + " " + units).trim(); + } /** - * Format the quantity according to optional passed target units - * and formatter * - * @param {string} [targetUnits=current units] - - * optional units to convert to before formatting + * Configurable Qty default formatter * - * @param {function} [formatter=Qty.formatter] - - * delegates formatting to formatter callback. - * formatter is called back with two parameters (scalar, units) - * and should return formatted result. - * If unspecified, formatting is delegated to default formatter - * set to Qty.formatter + * @type {function} * - * @example - * var roundingAndLocalizingFormatter = function(scalar, units) { - * // localize or limit scalar to n max decimals for instance - * // return formatted result - * }; - * var qty = Qty('1.1234 m'); - * qty.format(); // same units, default formatter => "1.234 m" - * qty.format("cm"); // converted to "cm", default formatter => "123.45 cm" - * qty.format(roundingAndLocalizingFormatter); // same units, custom formatter => "1,2 m" - * qty.format("cm", roundingAndLocalizingFormatter); // convert to "cm", custom formatter => "123,4 cm" + * @param {number} scalar + * @param {string} units * - * @returns {string} quantity as string + * @returns {string} formatted result */ - format: function(targetUnits, formatter) { - if (arguments.length === 1) { - if (typeof targetUnits === "function") { - formatter = targetUnits; - targetUnits = undefined; + Qty.formatter = defaultFormatter; + + assign(Qty.prototype, { + + // returns the 'unit' part of the Unit object without the scalar + units: function() { + if (this._units !== undefined) { + return this._units; } - } - formatter = formatter || Qty$1.formatter; - var targetQty = this.to(targetUnits); - return formatter.call(this, targetQty.scalar, targetQty.units()); - } -}); - -var stringifiedUnitsCache = new NestedMap(); -/** - * Returns a string representing a normalized unit array - * - * @param {string[]} units Normalized unit array - * @returns {string} String representing passed normalized unit array and - * suitable for output - * - */ -function stringifyUnits(units) { - - var stringified = stringifiedUnitsCache.get(units); - if (stringified) { - return stringified; - } + var numIsUnity = compareArray(this.numerator, UNITY_ARRAY), + denIsUnity = compareArray(this.denominator, UNITY_ARRAY); + if (numIsUnity && denIsUnity) { + this._units = ""; + return this._units; + } - var isUnity = compareArray(units, UNITY_ARRAY); - if (isUnity) { - stringified = "1"; - } - else { - stringified = simplify(getOutputNames(units)).join("*"); - } + var numUnits = stringifyUnits(this.numerator), + denUnits = stringifyUnits(this.denominator); + this._units = numUnits + (denIsUnity ? "" : ("/" + denUnits)); + return this._units; + }, - // Cache result - stringifiedUnitsCache.set(units, stringified); + /** + * Stringifies the quantity + * Deprecation notice: only units parameter is supported. + * + * @param {(number|string|Qty)} targetUnitsOrMaxDecimalsOrPrec - + * target units if string, + * max number of decimals if number, + * passed to #toPrec before converting if Qty + * + * @param {number=} maxDecimals - Maximum number of decimals of + * formatted output + * + * @returns {string} reparseable quantity as string + */ + toString: function(targetUnitsOrMaxDecimalsOrPrec, maxDecimals) { + var targetUnits; + if (isNumber(targetUnitsOrMaxDecimalsOrPrec)) { + targetUnits = this.units(); + maxDecimals = targetUnitsOrMaxDecimalsOrPrec; + } + else if (isString(targetUnitsOrMaxDecimalsOrPrec)) { + targetUnits = targetUnitsOrMaxDecimalsOrPrec; + } + else if (isQty(targetUnitsOrMaxDecimalsOrPrec)) { + return this.toPrec(targetUnitsOrMaxDecimalsOrPrec).toString(maxDecimals); + } + + var out = this.to(targetUnits); + + var outScalar = maxDecimals !== undefined ? round(out.scalar, maxDecimals) : out.scalar; + out = (outScalar + " " + out.units()).trim(); + return out; + }, + + /** + * Format the quantity according to optional passed target units + * and formatter + * + * @param {string} [targetUnits=current units] - + * optional units to convert to before formatting + * + * @param {function} [formatter=Qty.formatter] - + * delegates formatting to formatter callback. + * formatter is called back with two parameters (scalar, units) + * and should return formatted result. + * If unspecified, formatting is delegated to default formatter + * set to Qty.formatter + * + * @example + * var roundingAndLocalizingFormatter = function(scalar, units) { + * // localize or limit scalar to n max decimals for instance + * // return formatted result + * }; + * var qty = Qty('1.1234 m'); + * qty.format(); // same units, default formatter => "1.234 m" + * qty.format("cm"); // converted to "cm", default formatter => "123.45 cm" + * qty.format(roundingAndLocalizingFormatter); // same units, custom formatter => "1,2 m" + * qty.format("cm", roundingAndLocalizingFormatter); // convert to "cm", custom formatter => "123,4 cm" + * + * @returns {string} quantity as string + */ + format: function(targetUnits, formatter) { + if (arguments.length === 1) { + if (typeof targetUnits === "function") { + formatter = targetUnits; + targetUnits = undefined; + } + } + + formatter = formatter || Qty.formatter; + var targetQty = this.to(targetUnits); + return formatter.call(this, targetQty.scalar, targetQty.units()); + } + }); + + var stringifiedUnitsCache = new NestedMap(); + /** + * Returns a string representing a normalized unit array + * + * @param {string[]} units Normalized unit array + * @returns {string} String representing passed normalized unit array and + * suitable for output + * + */ + function stringifyUnits(units) { - return stringified; -} + var stringified = stringifiedUnitsCache.get(units); + if (stringified) { + return stringified; + } -function getOutputNames(units) { - var unitNames = [], token, tokenNext; - for (var i = 0; i < units.length; i++) { - token = units[i]; - tokenNext = units[i + 1]; - if (PREFIX_VALUES[token]) { - unitNames.push(OUTPUT_MAP[token] + OUTPUT_MAP[tokenNext]); - i++; + var isUnity = compareArray(units, UNITY_ARRAY); + if (isUnity) { + stringified = "1"; } else { - unitNames.push(OUTPUT_MAP[token]); + stringified = simplify(getOutputNames(units)).join("*"); } - } - return unitNames; -} -function simplify (units) { - // this turns ['s','m','s'] into ['s2','m'] + // Cache result + stringifiedUnitsCache.set(units, stringified); + + return stringified; + } - var unitCounts = units.reduce(function(acc, unit) { - var unitCounter = acc[unit]; - if (!unitCounter) { - acc.push(unitCounter = acc[unit] = [unit, 0]); + function getOutputNames(units) { + var unitNames = [], token, tokenNext; + for (var i = 0; i < units.length; i++) { + token = units[i]; + tokenNext = units[i + 1]; + if (PREFIX_VALUES[token]) { + unitNames.push(OUTPUT_MAP[token] + OUTPUT_MAP[tokenNext]); + i++; + } + else { + unitNames.push(OUTPUT_MAP[token]); + } } + return unitNames; + } - unitCounter[1]++; + function simplify (units) { + // this turns ['s','m','s'] into ['s2','m'] - return acc; - }, []); + var unitCounts = units.reduce(function(acc, unit) { + var unitCounter = acc[unit]; + if (!unitCounter) { + acc.push(unitCounter = acc[unit] = [unit, 0]); + } - return unitCounts.map(function(unitCount) { - return unitCount[0] + (unitCount[1] > 1 ? unitCount[1] : ""); - }); -} + unitCounter[1]++; + + return acc; + }, []); + + return unitCounts.map(function(unitCount) { + return unitCount[0] + (unitCount[1] > 1 ? unitCount[1] : ""); + }); + } -Qty$1.version = "1.7.0"; + Qty.version = "1.7.1"; -return Qty$1; + return Qty; }))); diff --git a/package.json b/package.json index 4e176ae..1e14ec8 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "module": "./build/quantities.esm.js", "readmeFilename": "README.md", "description": "JavaScript library for quantity calculation and unit conversion", - "version": "1.7.0", + "version": "1.7.1", "homepage": "http://gentooboontoo.github.io/js-quantities/", "repository": { "type": "git", diff --git a/src/quantities.js b/src/quantities.js index 04d37f9..1c46a1e 100644 --- a/src/quantities.js +++ b/src/quantities.js @@ -6,6 +6,6 @@ import "./quantities/predicates.js"; import "./quantities/conversion.js"; import "./quantities/format.js"; -Qty.version = "1.7.0"; +Qty.version = "1.7.1"; export default Qty;