UNPKG

mathjs

Version:

Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif

1,734 lines (1,653 loc) 101 kB
import _defineProperty from "@babel/runtime/helpers/defineProperty"; import _extends from "@babel/runtime/helpers/extends"; function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } import { isComplex, isUnit, typeOf } from '../../utils/is.js'; import { factory } from '../../utils/factory.js'; import { memoize } from '../../utils/function.js'; import { endsWith } from '../../utils/string.js'; import { clone, hasOwnProperty } from '../../utils/object.js'; import { createBigNumberPi as createPi } from '../../utils/bignumber/constants.js'; var name = 'Unit'; var dependencies = ['?on', 'config', 'addScalar', 'subtractScalar', 'multiplyScalar', 'divideScalar', 'pow', 'abs', 'fix', 'round', 'equal', 'isNumeric', 'format', 'number', 'Complex', 'BigNumber', 'Fraction']; export var createUnitClass = /* #__PURE__ */factory(name, dependencies, _ref => { var { on, config, addScalar, subtractScalar, multiplyScalar, divideScalar, pow, abs, fix, round, equal, isNumeric, format, number: _number, Complex, BigNumber: _BigNumber, Fraction: _Fraction } = _ref; var toNumber = _number; /** * A unit can be constructed in the following ways: * * const a = new Unit(value, valuelessUnit) * const b = new Unit(null, valuelessUnit) * const c = Unit.parse(str) * * Example usage: * * const a = new Unit(5, 'cm') // 50 mm * const b = Unit.parse('23 kg') // 23 kg * const c = math.in(a, new Unit(null, 'm') // 0.05 m * const d = new Unit(9.81, "m/s^2") // 9.81 m/s^2 * * @class Unit * @constructor Unit * @param {number | BigNumber | Fraction | Complex | boolean} [value] A value like 5.2 * @param {string | Unit} valuelessUnit A unit without value. Can have prefix, like "cm" */ function Unit(value, valuelessUnit) { if (!(this instanceof Unit)) { throw new Error('Constructor must be called with the new operator'); } if (!(value === null || value === undefined || isNumeric(value) || isComplex(value))) { throw new TypeError('First parameter in Unit constructor must be number, BigNumber, Fraction, Complex, or undefined'); } this.fixPrefix = false; // if true, function format will not search for the // best prefix but leave it as initially provided. // fixPrefix is set true by the method Unit.to // The justification behind this is that if the constructor is explicitly called, // the caller wishes the units to be returned exactly as supplied. this.skipAutomaticSimplification = true; if (valuelessUnit === undefined) { this.units = []; this.dimensions = BASE_DIMENSIONS.map(x => 0); } else if (typeof valuelessUnit === 'string') { var u = Unit.parse(valuelessUnit); this.units = u.units; this.dimensions = u.dimensions; } else if (isUnit(valuelessUnit) && valuelessUnit.value === null) { // clone from valuelessUnit this.fixPrefix = valuelessUnit.fixPrefix; this.skipAutomaticSimplification = valuelessUnit.skipAutomaticSimplification; this.dimensions = valuelessUnit.dimensions.slice(0); this.units = valuelessUnit.units.map(u => _extends({}, u)); } else { throw new TypeError('Second parameter in Unit constructor must be a string or valueless Unit'); } this.value = this._normalize(value); } /** * Attach type information */ Object.defineProperty(Unit, 'name', { value: 'Unit' }); Unit.prototype.constructor = Unit; Unit.prototype.type = 'Unit'; Unit.prototype.isUnit = true; // private variables and functions for the Unit parser var text, index, c; function skipWhitespace() { while (c === ' ' || c === '\t') { next(); } } function isDigitDot(c) { return c >= '0' && c <= '9' || c === '.'; } function isDigit(c) { return c >= '0' && c <= '9'; } function next() { index++; c = text.charAt(index); } function revert(oldIndex) { index = oldIndex; c = text.charAt(index); } function parseNumber() { var number = ''; var oldIndex = index; if (c === '+') { next(); } else if (c === '-') { number += c; next(); } if (!isDigitDot(c)) { // a + or - must be followed by a digit revert(oldIndex); return null; } // get number, can have a single dot if (c === '.') { number += c; next(); if (!isDigit(c)) { // this is no legal number, it is just a dot revert(oldIndex); return null; } } else { while (isDigit(c)) { number += c; next(); } if (c === '.') { number += c; next(); } } while (isDigit(c)) { number += c; next(); } // check for exponential notation like "2.3e-4" or "1.23e50" if (c === 'E' || c === 'e') { // The grammar branches here. This could either be part of an exponent or the start of a unit that begins with the letter e, such as "4exabytes" var tentativeNumber = ''; var tentativeIndex = index; tentativeNumber += c; next(); if (c === '+' || c === '-') { tentativeNumber += c; next(); } // Scientific notation MUST be followed by an exponent (otherwise we assume it is not scientific notation) if (!isDigit(c)) { // The e or E must belong to something else, so return the number without the e or E. revert(tentativeIndex); return number; } // We can now safely say that this is scientific notation. number = number + tentativeNumber; while (isDigit(c)) { number += c; next(); } } return number; } function parseUnit() { var unitName = ''; // Alphanumeric characters only; matches [a-zA-Z0-9] while (isDigit(c) || Unit.isValidAlpha(c)) { unitName += c; next(); } // Must begin with [a-zA-Z] var firstC = unitName.charAt(0); if (Unit.isValidAlpha(firstC)) { return unitName; } else { return null; } } function parseCharacter(toFind) { if (c === toFind) { next(); return toFind; } else { return null; } } /** * Parse a string into a unit. The value of the unit is parsed as number, * BigNumber, or Fraction depending on the math.js config setting `number`. * * Throws an exception if the provided string does not contain a valid unit or * cannot be parsed. * @memberof Unit * @param {string} str A string like "5.2 inch", "4e2 cm/s^2" * @return {Unit} unit */ Unit.parse = function (str, options) { options = options || {}; text = str; index = -1; c = ''; if (typeof text !== 'string') { throw new TypeError('Invalid argument in Unit.parse, string expected'); } var unit = new Unit(); unit.units = []; var powerMultiplierCurrent = 1; var expectingUnit = false; // A unit should follow this pattern: // [number] ...[ [*/] unit[^number] ] // unit[^number] ... [ [*/] unit[^number] ] // Rules: // number is any floating point number. // unit is any alphanumeric string beginning with an alpha. Units with names like e3 should be avoided because they look like the exponent of a floating point number! // The string may optionally begin with a number. // Each unit may optionally be followed by ^number. // Whitespace or a forward slash is recommended between consecutive units, although the following technically is parseable: // 2m^2kg/s^2 // it is not good form. If a unit starts with e, then it could be confused as a floating point number: // 4erg next(); skipWhitespace(); // Optional number at the start of the string var valueStr = parseNumber(); var value = null; if (valueStr) { if (config.number === 'BigNumber') { value = new _BigNumber(valueStr); } else if (config.number === 'Fraction') { try { // not all numbers can be turned in Fractions, for example very small numbers not value = new _Fraction(valueStr); } catch (err) { value = parseFloat(valueStr); } } else { // number value = parseFloat(valueStr); } skipWhitespace(); // Whitespace is not required here // handle multiplication or division right after the value, like '1/s' if (parseCharacter('*')) { powerMultiplierCurrent = 1; expectingUnit = true; } else if (parseCharacter('/')) { powerMultiplierCurrent = -1; expectingUnit = true; } } // Stack to keep track of powerMultipliers applied to each parentheses group var powerMultiplierStack = []; // Running product of all elements in powerMultiplierStack var powerMultiplierStackProduct = 1; while (true) { skipWhitespace(); // Check for and consume opening parentheses, pushing powerMultiplierCurrent to the stack // A '(' will always appear directly before a unit. while (c === '(') { powerMultiplierStack.push(powerMultiplierCurrent); powerMultiplierStackProduct *= powerMultiplierCurrent; powerMultiplierCurrent = 1; next(); skipWhitespace(); } // Is there something here? var uStr = void 0; if (c) { var oldC = c; uStr = parseUnit(); if (uStr === null) { throw new SyntaxError('Unexpected "' + oldC + '" in "' + text + '" at index ' + index.toString()); } } else { // End of input. break; } // Verify the unit exists and get the prefix (if any) var res = _findUnit(uStr); if (res === null) { // Unit not found. throw new SyntaxError('Unit "' + uStr + '" not found.'); } var power = powerMultiplierCurrent * powerMultiplierStackProduct; // Is there a "^ number"? skipWhitespace(); if (parseCharacter('^')) { skipWhitespace(); var p = parseNumber(); if (p === null) { // No valid number found for the power! throw new SyntaxError('In "' + str + '", "^" must be followed by a floating-point number'); } power *= p; } // Add the unit to the list unit.units.push({ unit: res.unit, prefix: res.prefix, power }); for (var i = 0; i < BASE_DIMENSIONS.length; i++) { unit.dimensions[i] += (res.unit.dimensions[i] || 0) * power; } // Check for and consume closing parentheses, popping from the stack. // A ')' will always follow a unit. skipWhitespace(); while (c === ')') { if (powerMultiplierStack.length === 0) { throw new SyntaxError('Unmatched ")" in "' + text + '" at index ' + index.toString()); } powerMultiplierStackProduct /= powerMultiplierStack.pop(); next(); skipWhitespace(); } // "*" and "/" should mean we are expecting something to come next. // Is there a forward slash? If so, negate powerMultiplierCurrent. The next unit or paren group is in the denominator. expectingUnit = false; if (parseCharacter('*')) { // explicit multiplication powerMultiplierCurrent = 1; expectingUnit = true; } else if (parseCharacter('/')) { // division powerMultiplierCurrent = -1; expectingUnit = true; } else { // implicit multiplication powerMultiplierCurrent = 1; } // Replace the unit into the auto unit system if (res.unit.base) { var baseDim = res.unit.base.key; UNIT_SYSTEMS.auto[baseDim] = { unit: res.unit, prefix: res.prefix }; } } // Has the string been entirely consumed? skipWhitespace(); if (c) { throw new SyntaxError('Could not parse: "' + str + '"'); } // Is there a trailing slash? if (expectingUnit) { throw new SyntaxError('Trailing characters: "' + str + '"'); } // Is the parentheses stack empty? if (powerMultiplierStack.length !== 0) { throw new SyntaxError('Unmatched "(" in "' + text + '"'); } // Are there any units at all? if (unit.units.length === 0 && !options.allowNoUnits) { throw new SyntaxError('"' + str + '" contains no units'); } unit.value = value !== undefined ? unit._normalize(value) : null; return unit; }; /** * create a copy of this unit * @memberof Unit * @return {Unit} Returns a cloned version of the unit */ Unit.prototype.clone = function () { var unit = new Unit(); unit.fixPrefix = this.fixPrefix; unit.skipAutomaticSimplification = this.skipAutomaticSimplification; unit.value = clone(this.value); unit.dimensions = this.dimensions.slice(0); unit.units = []; for (var i = 0; i < this.units.length; i++) { unit.units[i] = {}; for (var p in this.units[i]) { if (hasOwnProperty(this.units[i], p)) { unit.units[i][p] = this.units[i][p]; } } } return unit; }; /** * Return the type of the value of this unit * * @memberof Unit * @return {string} type of the value of the unit */ Unit.prototype.valueType = function () { return typeOf(this.value); }; /** * Return whether the unit is derived (such as m/s, or cm^2, but not N) * @memberof Unit * @return {boolean} True if the unit is derived * @private */ Unit.prototype._isDerived = function () { if (this.units.length === 0) { return false; } return this.units.length > 1 || Math.abs(this.units[0].power - 1.0) > 1e-15; }; /** * Normalize a value, based on its currently set unit(s) * @memberof Unit * @param {number | BigNumber | Fraction | boolean} value * @return {number | BigNumber | Fraction | boolean} normalized value * @private */ Unit.prototype._normalize = function (value) { if (value === null || value === undefined || this.units.length === 0) { return value; } var res = value; var convert = Unit._getNumberConverter(typeOf(value)); // convert to Fraction or BigNumber if needed for (var i = 0; i < this.units.length; i++) { var unitValue = convert(this.units[i].unit.value); var unitPrefixValue = convert(this.units[i].prefix.value); var unitPower = convert(this.units[i].power); res = multiplyScalar(res, pow(multiplyScalar(unitValue, unitPrefixValue), unitPower)); } return res; }; /** * Denormalize a value, based on its currently set unit(s) * @memberof Unit * @param {number} value * @param {number} [prefixValue] Optional prefix value to be used (ignored if this is a derived unit) * @return {number} denormalized value * @private */ Unit.prototype._denormalize = function (value, prefixValue) { if (value === null || value === undefined || this.units.length === 0) { return value; } var res = value; var convert = Unit._getNumberConverter(typeOf(value)); // convert to Fraction or BigNumber if needed for (var i = 0; i < this.units.length; i++) { var unitValue = convert(this.units[i].unit.value); var unitPrefixValue = convert(this.units[i].prefix.value); var unitPower = convert(this.units[i].power); res = divideScalar(res, pow(multiplyScalar(unitValue, unitPrefixValue), unitPower)); } return res; }; /** * Find a unit from a string * @memberof Unit * @param {string} str A string like 'cm' or 'inch' * @returns {Object | null} result When found, an object with fields unit and * prefix is returned. Else, null is returned. * @private */ var _findUnit = memoize(str => { // First, match units names exactly. For example, a user could define 'mm' as 10^-4 m, which is silly, but then we would want 'mm' to match the user-defined unit. if (hasOwnProperty(UNITS, str)) { var unit = UNITS[str]; var prefix = unit.prefixes['']; return { unit, prefix }; } for (var _name in UNITS) { if (hasOwnProperty(UNITS, _name)) { if (endsWith(str, _name)) { var _unit = UNITS[_name]; var prefixLen = str.length - _name.length; var prefixName = str.substring(0, prefixLen); var _prefix = hasOwnProperty(_unit.prefixes, prefixName) ? _unit.prefixes[prefixName] : undefined; if (_prefix !== undefined) { // store unit, prefix, and value return { unit: _unit, prefix: _prefix }; } } } } return null; }, { hasher: args => args[0], limit: 100 }); /** * Test if the given expression is a unit. * The unit can have a prefix but cannot have a value. * @memberof Unit * @param {string} name A string to be tested whether it is a value less unit. * The unit can have prefix, like "cm" * @return {boolean} true if the given string is a unit */ Unit.isValuelessUnit = function (name) { return _findUnit(name) !== null; }; /** * check if this unit has given base unit * If this unit is a derived unit, this will ALWAYS return false, since by definition base units are not derived. * @memberof Unit * @param {BASE_UNIT | string | undefined} base */ Unit.prototype.hasBase = function (base) { if (typeof base === 'string') { base = BASE_UNITS[base]; } if (!base) { return false; } // All dimensions must be the same for (var i = 0; i < BASE_DIMENSIONS.length; i++) { if (Math.abs((this.dimensions[i] || 0) - (base.dimensions[i] || 0)) > 1e-12) { return false; } } return true; }; /** * Check if this unit has a base or bases equal to another base or bases * For derived units, the exponent on each base also must match * @memberof Unit * @param {Unit} other * @return {boolean} true if equal base */ Unit.prototype.equalBase = function (other) { // All dimensions must be the same for (var i = 0; i < BASE_DIMENSIONS.length; i++) { if (Math.abs((this.dimensions[i] || 0) - (other.dimensions[i] || 0)) > 1e-12) { return false; } } return true; }; /** * Check if this unit equals another unit * @memberof Unit * @param {Unit} other * @return {boolean} true if both units are equal */ Unit.prototype.equals = function (other) { return this.equalBase(other) && equal(this.value, other.value); }; /** * Multiply this unit with another one or with a scalar * @memberof Unit * @param {Unit} other * @return {Unit} product of this unit and the other unit */ Unit.prototype.multiply = function (_other) { var res = this.clone(); var other = isUnit(_other) ? _other : new Unit(_other); for (var i = 0; i < BASE_DIMENSIONS.length; i++) { // Dimensions arrays may be of different lengths. Default to 0. res.dimensions[i] = (this.dimensions[i] || 0) + (other.dimensions[i] || 0); } // Append other's units list onto res for (var _i = 0; _i < other.units.length; _i++) { // Make a shallow copy of every unit var inverted = _objectSpread({}, other.units[_i]); res.units.push(inverted); } // If at least one operand has a value, then the result should also have a value if (this.value !== null || other.value !== null) { var valThis = this.value === null ? this._normalize(1) : this.value; var valOther = other.value === null ? other._normalize(1) : other.value; res.value = multiplyScalar(valThis, valOther); } else { res.value = null; } if (isUnit(_other)) { res.skipAutomaticSimplification = false; } return getNumericIfUnitless(res); }; /** * Divide a number by this unit * * @memberof Unit * @param {numeric} numerator * @param {unit} result of dividing numerator by this unit */ Unit.prototype.divideInto = function (numerator) { return new Unit(numerator).divide(this); }; /** * Divide this unit by another one * @memberof Unit * @param {Unit | numeric} other * @return {Unit} result of dividing this unit by the other unit */ Unit.prototype.divide = function (_other) { var res = this.clone(); var other = isUnit(_other) ? _other : new Unit(_other); for (var i = 0; i < BASE_DIMENSIONS.length; i++) { // Dimensions arrays may be of different lengths. Default to 0. res.dimensions[i] = (this.dimensions[i] || 0) - (other.dimensions[i] || 0); } // Invert and append other's units list onto res for (var _i2 = 0; _i2 < other.units.length; _i2++) { // Make a shallow copy of every unit var inverted = _objectSpread(_objectSpread({}, other.units[_i2]), {}, { power: -other.units[_i2].power }); res.units.push(inverted); } // If at least one operand has a value, the result should have a value if (this.value !== null || other.value !== null) { var valThis = this.value === null ? this._normalize(1) : this.value; var valOther = other.value === null ? other._normalize(1) : other.value; res.value = divideScalar(valThis, valOther); } else { res.value = null; } if (isUnit(_other)) { res.skipAutomaticSimplification = false; } return getNumericIfUnitless(res); }; /** * Calculate the power of a unit * @memberof Unit * @param {number | Fraction | BigNumber} p * @returns {Unit} The result: this^p */ Unit.prototype.pow = function (p) { var res = this.clone(); for (var i = 0; i < BASE_DIMENSIONS.length; i++) { // Dimensions arrays may be of different lengths. Default to 0. res.dimensions[i] = (this.dimensions[i] || 0) * p; } // Adjust the power of each unit in the list for (var _i3 = 0; _i3 < res.units.length; _i3++) { res.units[_i3].power *= p; } if (res.value !== null) { res.value = pow(res.value, p); // only allow numeric output, we don't want to return a Complex number // if (!isNumeric(res.value)) { // res.value = NaN // } // Update: Complex supported now } else { res.value = null; } res.skipAutomaticSimplification = false; return getNumericIfUnitless(res); }; /** * Return the numeric value of this unit if it is dimensionless, has a value, and config.predictable == false; or the original unit otherwise * @param {Unit} unit * @returns {number | Fraction | BigNumber | Unit} The numeric value of the unit if conditions are met, or the original unit otherwise */ function getNumericIfUnitless(unit) { if (unit.equalBase(BASE_UNITS.NONE) && unit.value !== null && !config.predictable) { return unit.value; } else { return unit; } } /** * Calculate the absolute value of a unit * @memberof Unit * @param {number | Fraction | BigNumber} x * @returns {Unit} The result: |x|, absolute value of x */ Unit.prototype.abs = function () { var ret = this.clone(); if (ret.value !== null) { if (ret._isDerived() || ret.units.length === 0 || ret.units[0].unit.offset === 0) { ret.value = abs(ret.value); } else { // To give the correct, but unexpected, results for units with an offset. // For example, abs(-283.15 degC) = -263.15 degC !!! // We must take the offset into consideration here var convert = ret._numberConverter(); // convert to Fraction or BigNumber if needed var unitValue = convert(ret.units[0].unit.value); var nominalOffset = convert(ret.units[0].unit.offset); var unitOffset = multiplyScalar(unitValue, nominalOffset); ret.value = subtractScalar(abs(addScalar(ret.value, unitOffset)), unitOffset); } } for (var i in ret.units) { if (ret.units[i].unit.name === 'VA' || ret.units[i].unit.name === 'VAR') { ret.units[i].unit = UNITS.W; } } return ret; }; /** * Convert the unit to a specific unit name. * @memberof Unit * @param {string | Unit} valuelessUnit A unit without value. Can have prefix, like "cm" * @returns {Unit} Returns a clone of the unit with a fixed prefix and unit. */ Unit.prototype.to = function (valuelessUnit) { var value = this.value === null ? this._normalize(1) : this.value; var other; if (typeof valuelessUnit === 'string') { other = Unit.parse(valuelessUnit); } else if (isUnit(valuelessUnit)) { other = valuelessUnit.clone(); } else { throw new Error('String or Unit expected as parameter'); } if (!this.equalBase(other)) { throw new Error("Units do not match ('".concat(other.toString(), "' != '").concat(this.toString(), "')")); } if (other.value !== null) { throw new Error('Cannot convert to a unit with a value'); } if (this.value === null || this._isDerived() || this.units.length === 0 || other.units.length === 0 || this.units[0].unit.offset === other.units[0].unit.offset) { other.value = clone(value); } else { /* Need to adjust value by difference in offset to convert */ var convert = Unit._getNumberConverter(typeOf(value)); // convert to Fraction or BigNumber if needed var thisUnitValue = this.units[0].unit.value; var thisNominalOffset = this.units[0].unit.offset; var thisUnitOffset = multiplyScalar(thisUnitValue, thisNominalOffset); var otherUnitValue = other.units[0].unit.value; var otherNominalOffset = other.units[0].unit.offset; var otherUnitOffset = multiplyScalar(otherUnitValue, otherNominalOffset); other.value = addScalar(value, convert(subtractScalar(thisUnitOffset, otherUnitOffset))); } other.fixPrefix = true; other.skipAutomaticSimplification = true; return other; }; /** * Return the value of the unit when represented with given valueless unit * @memberof Unit * @param {string | Unit} valuelessUnit For example 'cm' or 'inch' * @return {number} Returns the unit value as number. */ // TODO: deprecate Unit.toNumber? It's always better to use toNumeric Unit.prototype.toNumber = function (valuelessUnit) { return toNumber(this.toNumeric(valuelessUnit)); }; /** * Return the value of the unit in the original numeric type * @memberof Unit * @param {string | Unit} valuelessUnit For example 'cm' or 'inch' * @return {number | BigNumber | Fraction} Returns the unit value */ Unit.prototype.toNumeric = function (valuelessUnit) { var other; if (valuelessUnit) { // Allow getting the numeric value without converting to a different unit other = this.to(valuelessUnit); } else { other = this.clone(); } if (other._isDerived() || other.units.length === 0) { return other._denormalize(other.value); } else { return other._denormalize(other.value, other.units[0].prefix.value); } }; /** * Get a string representation of the unit. * @memberof Unit * @return {string} */ Unit.prototype.toString = function () { return this.format(); }; /** * Get a JSON representation of the unit * @memberof Unit * @returns {Object} Returns a JSON object structured as: * `{"mathjs": "Unit", "value": 2, "unit": "cm", "fixPrefix": false}` */ Unit.prototype.toJSON = function () { return { mathjs: 'Unit', value: this._denormalize(this.value), unit: this.units.length > 0 ? this.formatUnits() : null, fixPrefix: this.fixPrefix }; }; /** * Instantiate a Unit from a JSON object * @memberof Unit * @param {Object} json A JSON object structured as: * `{"mathjs": "Unit", "value": 2, "unit": "cm", "fixPrefix": false}` * @return {Unit} */ Unit.fromJSON = function (json) { var _json$unit; var unit = new Unit(json.value, (_json$unit = json.unit) !== null && _json$unit !== void 0 ? _json$unit : undefined); unit.fixPrefix = json.fixPrefix || false; return unit; }; /** * Returns the string representation of the unit. * @memberof Unit * @return {string} */ Unit.prototype.valueOf = Unit.prototype.toString; /** * Simplify this Unit's unit list and return a new Unit with the simplified list. * The returned Unit will contain a list of the "best" units for formatting. */ Unit.prototype.simplify = function () { var ret = this.clone(); var proposedUnitList = []; // Search for a matching base var matchingBase; for (var key in currentUnitSystem) { if (hasOwnProperty(currentUnitSystem, key)) { if (ret.hasBase(BASE_UNITS[key])) { matchingBase = key; break; } } } if (matchingBase === 'NONE') { ret.units = []; } else { var matchingUnit; if (matchingBase) { // Does the unit system have a matching unit? if (hasOwnProperty(currentUnitSystem, matchingBase)) { matchingUnit = currentUnitSystem[matchingBase]; } } if (matchingUnit) { ret.units = [{ unit: matchingUnit.unit, prefix: matchingUnit.prefix, power: 1.0 }]; } else { // Multiple units or units with powers are formatted like this: // 5 (kg m^2) / (s^3 mol) // Build an representation from the base units of the current unit system var missingBaseDim = false; for (var i = 0; i < BASE_DIMENSIONS.length; i++) { var baseDim = BASE_DIMENSIONS[i]; if (Math.abs(ret.dimensions[i] || 0) > 1e-12) { if (hasOwnProperty(currentUnitSystem, baseDim)) { proposedUnitList.push({ unit: currentUnitSystem[baseDim].unit, prefix: currentUnitSystem[baseDim].prefix, power: ret.dimensions[i] || 0 }); } else { missingBaseDim = true; } } } // Is the proposed unit list "simpler" than the existing one? if (proposedUnitList.length < ret.units.length && !missingBaseDim) { // Replace this unit list with the proposed list ret.units = proposedUnitList; } } } return ret; }; /** * Returns a new Unit in the SI system with the same value as this one */ Unit.prototype.toSI = function () { var ret = this.clone(); var proposedUnitList = []; // Multiple units or units with powers are formatted like this: // 5 (kg m^2) / (s^3 mol) // Build an representation from the base units of the SI unit system for (var i = 0; i < BASE_DIMENSIONS.length; i++) { var baseDim = BASE_DIMENSIONS[i]; if (Math.abs(ret.dimensions[i] || 0) > 1e-12) { if (hasOwnProperty(UNIT_SYSTEMS.si, baseDim)) { proposedUnitList.push({ unit: UNIT_SYSTEMS.si[baseDim].unit, prefix: UNIT_SYSTEMS.si[baseDim].prefix, power: ret.dimensions[i] || 0 }); } else { throw new Error('Cannot express custom unit ' + baseDim + ' in SI units'); } } } // Replace this unit list with the proposed list ret.units = proposedUnitList; ret.fixPrefix = true; ret.skipAutomaticSimplification = true; if (this.value !== null) { ret.value = null; return this.to(ret); } return ret; }; /** * Get a string representation of the units of this Unit, without the value. The unit list is formatted as-is without first being simplified. * @memberof Unit * @return {string} */ Unit.prototype.formatUnits = function () { var strNum = ''; var strDen = ''; var nNum = 0; var nDen = 0; for (var i = 0; i < this.units.length; i++) { if (this.units[i].power > 0) { nNum++; strNum += ' ' + this.units[i].prefix.name + this.units[i].unit.name; if (Math.abs(this.units[i].power - 1.0) > 1e-15) { strNum += '^' + this.units[i].power; } } else if (this.units[i].power < 0) { nDen++; } } if (nDen > 0) { for (var _i4 = 0; _i4 < this.units.length; _i4++) { if (this.units[_i4].power < 0) { if (nNum > 0) { strDen += ' ' + this.units[_i4].prefix.name + this.units[_i4].unit.name; if (Math.abs(this.units[_i4].power + 1.0) > 1e-15) { strDen += '^' + -this.units[_i4].power; } } else { strDen += ' ' + this.units[_i4].prefix.name + this.units[_i4].unit.name; strDen += '^' + this.units[_i4].power; } } } } // Remove leading " " strNum = strNum.substr(1); strDen = strDen.substr(1); // Add parans for better copy/paste back into evaluate, for example, or for better pretty print formatting if (nNum > 1 && nDen > 0) { strNum = '(' + strNum + ')'; } if (nDen > 1 && nNum > 0) { strDen = '(' + strDen + ')'; } var str = strNum; if (nNum > 0 && nDen > 0) { str += ' / '; } str += strDen; return str; }; /** * Get a string representation of the Unit, with optional formatting options. * @memberof Unit * @param {Object | number | Function} [options] Formatting options. See * lib/utils/number:format for a * description of the available * options. * @return {string} */ Unit.prototype.format = function (options) { // Simplfy the unit list, unless it is valueless or was created directly in the // constructor or as the result of to or toSI var simp = this.skipAutomaticSimplification || this.value === null ? this.clone() : this.simplify(); // Apply some custom logic for handling VA and VAR. The goal is to express the value of the unit as a real value, if possible. Otherwise, use a real-valued unit instead of a complex-valued one. var isImaginary = false; if (typeof simp.value !== 'undefined' && simp.value !== null && isComplex(simp.value)) { // TODO: Make this better, for example, use relative magnitude of re and im rather than absolute isImaginary = Math.abs(simp.value.re) < 1e-14; } for (var i in simp.units) { if (hasOwnProperty(simp.units, i)) { if (simp.units[i].unit) { if (simp.units[i].unit.name === 'VA' && isImaginary) { simp.units[i].unit = UNITS.VAR; } else if (simp.units[i].unit.name === 'VAR' && !isImaginary) { simp.units[i].unit = UNITS.VA; } } } } // Now apply the best prefix // Units must have only one unit and not have the fixPrefix flag set if (simp.units.length === 1 && !simp.fixPrefix) { // Units must have integer powers, otherwise the prefix will change the // outputted value by not-an-integer-power-of-ten if (Math.abs(simp.units[0].power - Math.round(simp.units[0].power)) < 1e-14) { // Apply the best prefix simp.units[0].prefix = simp._bestPrefix(); } } var value = simp._denormalize(simp.value); var str = simp.value !== null ? format(value, options || {}) : ''; var unitStr = simp.formatUnits(); if (simp.value && isComplex(simp.value)) { str = '(' + str + ')'; // Surround complex values with ( ) to enable better parsing } if (unitStr.length > 0 && str.length > 0) { str += ' '; } str += unitStr; return str; }; /** * Calculate the best prefix using current value. * @memberof Unit * @returns {Object} prefix * @private */ Unit.prototype._bestPrefix = function () { if (this.units.length !== 1) { throw new Error('Can only compute the best prefix for single units with integer powers, like kg, s^2, N^-1, and so forth!'); } if (Math.abs(this.units[0].power - Math.round(this.units[0].power)) >= 1e-14) { throw new Error('Can only compute the best prefix for single units with integer powers, like kg, s^2, N^-1, and so forth!'); } // find the best prefix value (resulting in the value of which // the absolute value of the log10 is closest to zero, // though with a little offset of 1.2 for nicer values: you get a // sequence 1mm 100mm 500mm 0.6m 1m 10m 100m 500m 0.6km 1km ... // Note: the units value can be any numeric type, but to find the best // prefix it's enough to work with limited precision of a regular number // Update: using mathjs abs since we also allow complex numbers var absValue = this.value !== null ? abs(this.value) : 0; var absUnitValue = abs(this.units[0].unit.value); var bestPrefix = this.units[0].prefix; if (absValue === 0) { return bestPrefix; } var power = this.units[0].power; var bestDiff = Math.log(absValue / Math.pow(bestPrefix.value * absUnitValue, power)) / Math.LN10 - 1.2; if (bestDiff > -2.200001 && bestDiff < 1.800001) return bestPrefix; // Allow the original prefix bestDiff = Math.abs(bestDiff); var prefixes = this.units[0].unit.prefixes; for (var p in prefixes) { if (hasOwnProperty(prefixes, p)) { var prefix = prefixes[p]; if (prefix.scientific) { var diff = Math.abs(Math.log(absValue / Math.pow(prefix.value * absUnitValue, power)) / Math.LN10 - 1.2); if (diff < bestDiff || diff === bestDiff && prefix.name.length < bestPrefix.name.length) { // choose the prefix with the smallest diff, or if equal, choose the one // with the shortest name (can happen with SHORTLONG for example) bestPrefix = prefix; bestDiff = diff; } } } } return bestPrefix; }; /** * Returns an array of units whose sum is equal to this unit * @memberof Unit * @param {Array} [parts] An array of strings or valueless units. * * Example: * * const u = new Unit(1, 'm') * u.splitUnit(['feet', 'inch']) * [ 3 feet, 3.3700787401575 inch ] * * @return {Array} An array of units. */ Unit.prototype.splitUnit = function (parts) { var x = this.clone(); var ret = []; for (var i = 0; i < parts.length; i++) { // Convert x to the requested unit x = x.to(parts[i]); if (i === parts.length - 1) break; // Get the numeric value of this unit var xNumeric = x.toNumeric(); // Check to see if xNumeric is nearly equal to an integer, // since fix can incorrectly round down if there is round-off error var xRounded = round(xNumeric); var xFixed = void 0; var isNearlyEqual = equal(xRounded, xNumeric); if (isNearlyEqual) { xFixed = xRounded; } else { xFixed = fix(x.toNumeric()); } var y = new Unit(xFixed, parts[i].toString()); ret.push(y); x = subtractScalar(x, y); } // This little bit fixes a bug where the remainder should be 0 but is a little bit off. // But instead of comparing x, the remainder, with zero--we will compare the sum of // all the parts so far with the original value. If they are nearly equal, // we set the remainder to 0. var testSum = 0; for (var _i5 = 0; _i5 < ret.length; _i5++) { testSum = addScalar(testSum, ret[_i5].value); } if (equal(testSum, this.value)) { x.value = 0; } ret.push(x); return ret; }; var PREFIXES = { NONE: { '': { name: '', value: 1, scientific: true } }, SHORT: { '': { name: '', value: 1, scientific: true }, da: { name: 'da', value: 1e1, scientific: false }, h: { name: 'h', value: 1e2, scientific: false }, k: { name: 'k', value: 1e3, scientific: true }, M: { name: 'M', value: 1e6, scientific: true }, G: { name: 'G', value: 1e9, scientific: true }, T: { name: 'T', value: 1e12, scientific: true }, P: { name: 'P', value: 1e15, scientific: true }, E: { name: 'E', value: 1e18, scientific: true }, Z: { name: 'Z', value: 1e21, scientific: true }, Y: { name: 'Y', value: 1e24, scientific: true }, R: { name: 'R', value: 1e27, scientific: true }, Q: { name: 'Q', value: 1e30, scientific: true }, d: { name: 'd', value: 1e-1, scientific: false }, c: { name: 'c', value: 1e-2, scientific: false }, m: { name: 'm', value: 1e-3, scientific: true }, u: { name: 'u', value: 1e-6, scientific: true }, n: { name: 'n', value: 1e-9, scientific: true }, p: { name: 'p', value: 1e-12, scientific: true }, f: { name: 'f', value: 1e-15, scientific: true }, a: { name: 'a', value: 1e-18, scientific: true }, z: { name: 'z', value: 1e-21, scientific: true }, y: { name: 'y', value: 1e-24, scientific: true }, r: { name: 'r', value: 1e-27, scientific: true }, q: { name: 'q', value: 1e-30, scientific: true } }, LONG: { '': { name: '', value: 1, scientific: true }, deca: { name: 'deca', value: 1e1, scientific: false }, hecto: { name: 'hecto', value: 1e2, scientific: false }, kilo: { name: 'kilo', value: 1e3, scientific: true }, mega: { name: 'mega', value: 1e6, scientific: true }, giga: { name: 'giga', value: 1e9, scientific: true }, tera: { name: 'tera', value: 1e12, scientific: true }, peta: { name: 'peta', value: 1e15, scientific: true }, exa: { name: 'exa', value: 1e18, scientific: true }, zetta: { name: 'zetta', value: 1e21, scientific: true }, yotta: { name: 'yotta', value: 1e24, scientific: true }, ronna: { name: 'ronna', value: 1e27, scientific: true }, quetta: { name: 'quetta', value: 1e30, scientific: true }, deci: { name: 'deci', value: 1e-1, scientific: false }, centi: { name: 'centi', value: 1e-2, scientific: false }, milli: { name: 'milli', value: 1e-3, scientific: true }, micro: { name: 'micro', value: 1e-6, scientific: true }, nano: { name: 'nano', value: 1e-9, scientific: true }, pico: { name: 'pico', value: 1e-12, scientific: true }, femto: { name: 'femto', value: 1e-15, scientific: true }, atto: { name: 'atto', value: 1e-18, scientific: true }, zepto: { name: 'zepto', value: 1e-21, scientific: true }, yocto: { name: 'yocto', value: 1e-24, scientific: true }, ronto: { name: 'ronto', value: 1e-27, scientific: true }, quecto: { name: 'quecto', value: 1e-30, scientific: true } }, SQUARED: { '': { name: '', value: 1, scientific: true }, da: { name: 'da', value: 1e2, scientific: false }, h: { name: 'h', value: 1e4, scientific: false }, k: { name: 'k', value: 1e6, scientific: true }, M: { name: 'M', value: 1e12, scientific: true }, G: { name: 'G', value: 1e18, scientific: true }, T: { name: 'T', value: 1e24, scientific: true }, P: { name: 'P', value: 1e30, scientific: true }, E: { name: 'E', value: 1e36, scientific: true }, Z: { name: 'Z', value: 1e42, scientific: true }, Y: { name: 'Y', value: 1e48, scientific: true }, R: { name: 'R', value: 1e54, scientific: true }, Q: { name: 'Q', value: 1e60, scientific: true }, d: { name: 'd', value: 1e-2, scientific: false }, c: { name: 'c', value: 1e-4, scientific: false }, m: { name: 'm', value: 1e-6, scientific: true }, u: { name: 'u', value: 1e-12, scientific: true }, n: { name: 'n', value: 1e-18, scientific: true }, p: { name: 'p', value: 1e-24, scientific: true }, f: { name: 'f', value: 1e-30, scientific: true }, a: { name: 'a', value: 1e-36, scientific: true }, z: { name: 'z', value: 1e-42, scientific: true }, y: { name: 'y', value: 1e-48, scientific: true }, r: { name: 'r', value: 1e-54, scientific: true }, q: { name: 'q', value: 1e-60, scientific: true } }, CUBIC: { '': { name: '', value: 1, scientific: true }, da: { name: 'da', value: 1e3, scientific: false }, h: { name: 'h', value: 1e6, scientific: false }, k: { name: 'k', value: 1e9, scientific: true }, M: { name: 'M', value: 1e18, scientific: true }, G: { name: 'G', value: 1e27, scientific: true }, T: { name: 'T', value: 1e36, scientific: true }, P: { name: 'P', value: 1e45, scientific: true }, E: { name: 'E', value: 1e54, scientific: true }, Z: { name: 'Z', value: 1e63, scientific: true }, Y: { name: 'Y', value: 1e72, scientific: true }, R: { name: 'R', value: 1e81, scientific: true }, Q: { name: 'Q', value: 1e90, scientific: true }, d: { name: 'd', value: 1e-3, scientific: false }, c: { name: 'c', value: 1e-6, scientific: false }, m: { name: 'm', value: 1e-9, scientific: true }, u: { name: 'u', value: 1e-18, scientific: true }, n: { name: 'n', value: 1e-27, scientific: true }, p: { name: 'p', value: 1e-36, scientific: true }, f: { name: 'f', value: 1e-45, scientific: true }, a: { name: 'a', value: 1e-54, scientific: true }, z: { name: 'z', value: 1e-63, scientific: true }, y: { name: 'y', value: 1e-72, scientific: true }, r: { name: 'r', value: 1e-81, scientific: true }, q: { name: 'q', value: 1e-90, scientific: true } }, BINARY_SHORT_SI: { '': { name: '', value: 1, scientific: true }, k: { name: 'k', value: 1e3, scientific: true }, M: { name: 'M', value: 1e6, scientific: true }, G: { name: 'G', value: 1e9, scientific: true }, T: { name: 'T', value: 1e12, scientific: true }, P: { name: 'P', value: 1e15, scientific: true },