<|start_filename|>src/utils.js<|end_filename|>

/**
   * Normalize a value, based on an array of unit pieces
   * @param {unit[]} unitPieces
   * @param {number | *} value
   * @param {object} type
   * @return {number | *} normalized value
   * @private
   */
export function normalize (unitPieces, value, type) {
  let unitValue, unitOffset, unitPower, unitPrefixValue

  if (value === null || value === undefined || unitPieces.length === 0) {
    return value
  } else if (isCompound(unitPieces)) {
    // units is a compound unit, so do not apply offsets.
    // For example, with J kg^-1 degC^-1 you would NOT want to apply the offset.
    let result = value

    for (let i = 0; i < unitPieces.length; i++) {
      unitValue = type.conv(unitPieces[i].unit.value, value)
      unitPrefixValue = type.conv(unitPieces[i].unit.prefixes[unitPieces[i].prefix], value)
      unitPower = type.conv(unitPieces[i].power, value)
      result = type.mul(result, type.pow(type.mul(unitValue, unitPrefixValue), unitPower))
    }

    return result
  } else {
    // units is a single unit of power 1, like kg or degC
    unitValue = type.conv(unitPieces[0].unit.value, value)
    unitOffset = type.conv(unitPieces[0].unit.offset, value)
    unitPrefixValue = type.conv(unitPieces[0].unit.prefixes[unitPieces[0].prefix], value)
    return type.mul(type.add(value, unitOffset), type.mul(unitValue, unitPrefixValue))
  }
}

/**
   * Denormalize a value, based on an array of atomic units
   * @param {unit[]} unitPieces Array of atomic units (as in, Unit.units)
   * @param {number} value
   * @param {object} type
   * @return {number} denormalized value
   * @private
   */
export function denormalize (unitPieces, value, type) {
  let unitValue, unitOffset, unitPower, unitPrefixValue

  if (value === null || value === undefined || unitPieces.length === 0) {
    return value
  } else if (isCompound(unitPieces)) {
    // unit is a compound unit, so do not apply offsets.
    // For example, with J kg^-1 degC^-1 you would NOT want to apply the offset.
    let result = value

    for (let i = 0; i < unitPieces.length; i++) {
      unitValue = type.conv(unitPieces[i].unit.value, value)
      unitPrefixValue = type.conv(unitPieces[i].unit.prefixes[unitPieces[i].prefix], value)
      unitPower = type.conv(unitPieces[i].power, value)
      result = type.div(result, type.pow(type.mul(unitValue, unitPrefixValue), unitPower))
    }

    return result
  } else {
    // unit is a single unit of power 1, like kg or degC

    unitValue = type.conv(unitPieces[0].unit.value, value)
    unitPrefixValue = type.conv(unitPieces[0].unit.prefixes[unitPieces[0].prefix], value)
    unitOffset = type.conv(unitPieces[0].unit.offset, value)

    return type.sub(type.div(type.div(value, unitValue), unitPrefixValue), unitOffset)
  }
}

/**
   * Return whether the given array of unit pieces is compound (contains multiple units, such as m/s, or cm^2, but not N)
   * @param {unit[]} units Array of unit pieces
   * @return {boolean} True if the unit is compound
   */
export function isCompound (units) {
  if (units.length === 0) {
    return false
  }
  return units.length > 1 || Math.abs(units[0].power - 1.0) > 1e-15
}


<|start_filename|>src/BuiltIns.js<|end_filename|>
// A base quantity is a physical quantity in a subset of a given system of quantities that is chosen by convention, where no quantity in the set can be expressed in terms of the others.
export const baseQuantities = ['MASS', 'LENGTH', 'TIME', 'CURRENT', 'TEMPERATURE', 'LUMINOUS_INTENSITY', 'AMOUNT_OF_SUBSTANCE', 'ANGLE', 'BIT', 'SOLID_ANGLE']

// A derived quantity is a quantity in a system of quantities that is a defined in terms of the base quantities of that system.
export const quantities = {
  UNITLESS: '',
  ABSEMENT: 'LENGTH TIME',
  ACCELERATION: 'LENGTH TIME^-2',
  ANGULAR_ACCELERATION: 'TIME^-2 ANGLE',
  ANGULAR_MOMENTUM: 'MASS LENGTH^2 TIME^-1 ANGLE',
  ANGULAR_VELOCITY: 'TIME^-1 ANGLE',
  AREA: 'LENGTH^2',
  AREA_DENSITY: 'MASS LENGTH^-2',
  BIT_RATE: 'TIME^-1 BIT',
  CAPACITANCE: 'MASS^-1 LENGTH^-2 TIME^4 CURRENT^2',
  CURRENT_DENSITY: 'LENGTH^-2 CURRENT',
  DYNAMIC_VISCOSITY: 'MASS LENGTH^-1 TIME^-1',
  ELECTRIC_CHARGE: 'TIME CURRENT',
  ELECTRIC_CHARGE_DENSITY: 'LENGTH^-3 TIME CURRENT',
  ELECTRIC_DISPLACEMENT: 'LENGTH^-2 TIME CURRENT',
  ELECTRIC_FIELD_STRENGTH: 'MASS LENGTH TIME^-3 CURRENT^-1',
  ELECTRICAL_CONDUCTANCE: 'MASS^-1 LENGTH^-2 TIME^3 CURRENT^2',
  ELECTRICAL_CONDUCTIVITY: 'MASS^-1 LENGTH^-3 TIME^3 CURRENT^2',
  ELECTRIC_POTENTIAL: 'MASS LENGTH^2 TIME^-3 CURRENT^-1',
  RESISTANCE: 'MASS LENGTH^2 TIME^-3 CURRENT^-2',
  ELECTRICAL_RESISTIVITY: 'MASS LENGTH^3 TIME^-3 CURRENT^-2',
  ENERGY: 'MASS LENGTH^2 TIME^-2',
  ENTROPY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1',
  FORCE: 'MASS LENGTH TIME^-2',
  FREQUENCY: 'TIME^-1',
  HEAT_CAPACITY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1',
  HEAT_FLUX_DENSITY: 'MASS TIME^-3',
  ILLUMINANCE: 'LENGTH^-2 LUMINOUS_INTENSITY',
  IMPEDANCE: 'MASS LENGTH^2 TIME^-3 CURRENT^-2',
  IMPULSE: 'MASS LENGTH TIME^-1',
  INDUCTANCE: 'MASS LENGTH^2 TIME^-2 CURRENT^-2',
  IRRADIANCE: 'MASS TIME^-3',
  JERK: 'LENGTH TIME^-3',
  KINEMATIC_VISCOSITY: 'LENGTH^2 TIME^-1',
  LINEAR_DENSITY: 'MASS LENGTH^-1',
  LUMINOUS_FLUX: 'LUMINOUS_INTENSITY SOLID_ANGLE',
  MAGNETIC_FIELD_STRENGTH: 'LENGTH^-1 CURRENT',
  MAGNETIC_FLUX: 'MASS LENGTH^2 TIME^-2 CURRENT^-1',
  MAGNETIC_FLUX_DENSITY: 'MASS TIME^-2 CURRENT^-1',
  MOLAR_CONCENTRATION: 'LENGTH^-3 AMOUNT_OF_SUBSTANCE',
  MOLAR_ENERGY: 'MASS LENGTH^2 TIME^-2 AMOUNT_OF_SUBSTANCE^-1',
  MOLAR_ENTROPY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1 AMOUNT_OF_SUBSTANCE^-1',
  MOLAR_HEAT_CAPACITY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1 AMOUNT_OF_SUBSTANCE^-1',
  MOMENT_OF_INERTIA: 'MASS LENGTH^2',
  MOMENTUM: 'MASS LENGTH TIME^-1',
  PERMEABILITY: 'MASS LENGTH TIME^-2 CURRENT^-2',
  PERMITTIVITY: 'MASS^-1 LENGTH^-3 TIME^4 CURRENT^2 ',
  POWER: 'MASS LENGTH^2 TIME^-3',
  PRESSURE: 'MASS LENGTH^-1 TIME^-2',
  RELUCTANCE: 'MASS^-1 LENGTH^-2 TIME^2 CURRENT^2',
  SPECIFIC_ENERGY: 'LENGTH^2 TIME^-2',
  SPECIFIC_HEAT_CAPACITY: 'LENGTH^2 TIME^-2 TEMPERATURE^-1',
  SPECIFIC_VOLUME: 'MASS^-1 LENGTH^3',
  SPIN: 'MASS LENGTH^2 TIME^-1',
  SURFACE_TENSION: 'MASS TIME^-2',
  TEMPERATURE_GRADIENT: 'LENGTH^-1 TEMPERATURE',
  THERMAL_CONDUCTIVITY: 'MASS LENGTH TIME^-3 TEMPERATURE^-1',
  TORQUE: 'MASS LENGTH^2 TIME^-2', // TODO: Should this have a radian in it somewhere?
  VELOCITY: 'LENGTH TIME^-1',
  VOLUME: 'LENGTH^3',
  VOLUMETRIC_FLOW_RATE: 'LENGTH^3 TIME^-1'
}

// A unit system is a set of dimensionally independent base units plus a set of derived units, formed by multiplication and division of the base units, that are by convention used with the unit system.
export const unitSystems = {
  si: {
    AMOUNT_OF_SUBSTANCE: 'mol',
    ANGLE: 'rad',
    BIT: 'b',
    CAPACITANCE: 'F',
    CURRENT: 'A',
    ELECTRIC_CHARGE: 'C',
    ELECTRICAL_CONDUCTANCE: 'S',
    ELECTRIC_POTENTIAL: 'V',
    ENERGY: 'J',
    FORCE: 'N',
    FREQUENCY: 'Hz',
    IMPEDANCE: 'ohm',
    INDUCTANCE: 'H',
    LENGTH: 'm',
    LUMINOUS_INTENSITY: 'cd',
    LUMINOUS_FLUX: 'lm',
    ILLUMINANCE: 'lx',
    MAGNETIC_FLUX: 'Wb',
    MAGNETIC_FLUX_DENSITY: 'T',
    MASS: 'kg',
    POWER: 'W',
    PRESSURE: 'Pa',
    RESISTANCE: 'ohm',
    SOLID_ANGLE: 'sr',
    TEMPERATURE: 'K',
    TIME: 's'
  }
}

unitSystems.cgs = Object.assign({}, unitSystems.si)
unitSystems.cgs.LENGTH = 'cm'
unitSystems.cgs.MASS = 'g'
unitSystems.cgs.FORCE = 'dyn'
unitSystems.cgs.ENERGY = 'erg'

// These maybe are not canonical
unitSystems.us = Object.assign({}, unitSystems.si)
unitSystems.us.LENGTH = 'ft'
unitSystems.us.MASS = 'lbm'
unitSystems.us.TEMPERATURE = 'degF'
unitSystems.us.FORCE = 'lbf'
unitSystems.us.ENERGY = 'BTU'
unitSystems.us.POWER = 'hp'
unitSystems.us.PRESSURE = 'psi'

// Units may or may not use one of the prefix sets (SHORT, LONG, etc).
export const prefixes = {
  NONE: {
    '': 1
  },
  SHORT: {
    '': 1,

    'da': 1e1,
    'h': 1e2,
    'k': 1e3,
    'M': 1e6,
    'G': 1e9,
    'T': 1e12,
    'P': 1e15,
    'E': 1e18,
    'Z': 1e21,
    'Y': 1e24,

    'd': 1e-1,
    'c': 1e-2,
    'm': 1e-3,
    'u': 1e-6,
    'n': 1e-9,
    'p': 1e-12,
    'f': 1e-15,
    'a': 1e-18,
    'z': 1e-21,
    'y': 1e-24
  },
  LONG: {
    '': 1,

    'deca': 1e1,
    'hecto': 1e2,
    'kilo': 1e3,
    'mega': 1e6,
    'giga': 1e9,
    'tera': 1e12,
    'peta': 1e15,
    'exa': 1e18,
    'zetta': 1e21,
    'yotta': 1e24,

    'deci': 1e-1,
    'centi': 1e-2,
    'milli': 1e-3,
    'micro': 1e-6,
    'nano': 1e-9,
    'pico': 1e-12,
    'femto': 1e-15,
    'atto': 1e-18,
    'zepto': 1e-21,
    'yocto': 1e-24
  },
  SQUARED: {
    '': 1,

    'da': 1e2,
    'h': 1e4,
    'k': 1e6,
    'M': 1e12,
    'G': 1e18,
    'T': 1e24,
    'P': 1e30,
    'E': 1e36,
    'Z': 1e42,
    'Y': 1e48,

    'd': 1e-2,
    'c': 1e-4,
    'm': 1e-6,
    'u': 1e-12,
    'n': 1e-18,
    'p': 1e-24,
    'f': 1e-30,
    'a': 1e-36,
    'z': 1e-42,
    'y': 1e-48
  },
  CUBIC: {
    '': 1,

    'da': 1e3,
    'h': 1e6,
    'k': 1e9,
    'M': 1e18,
    'G': 1e27,
    'T': 1e36,
    'P': 1e45,
    'E': 1e54,
    'Z': 1e63,
    'Y': 1e72,

    'd': 1e-3,
    'c': 1e-6,
    'm': 1e-9,
    'u': 1e-18,
    'n': 1e-27,
    'p': 1e-36,
    'f': 1e-45,
    'a': 1e-54,
    'z': 1e-63,
    'y': 1e-72
  },
  BINARY_SHORT_SI: {
    '': 1,
    'k': 1e3,
    'M': 1e6,
    'G': 1e9,
    'T': 1e12,
    'P': 1e15,
    'E': 1e18,
    'Z': 1e21,
    'Y': 1e24
  },
  BINARY_SHORT_IEC: {
    '': 1,
    'Ki': 1024,
    'Mi': Math.pow(1024, 2),
    'Gi': Math.pow(1024, 3),
    'Ti': Math.pow(1024, 4),
    'Pi': Math.pow(1024, 5),
    'Ei': Math.pow(1024, 6),
    'Zi': Math.pow(1024, 7),
    'Yi': Math.pow(1024, 8)
  },
  BINARY_LONG_SI: {
    '': 1,
    'kilo': 1e3,
    'mega': 1e6,
    'giga': 1e9,
    'tera': 1e12,
    'peta': 1e15,
    'exa': 1e18,
    'zetta': 1e21,
    'yotta': 1e24
  },
  BINARY_LONG_IEC: {
    '': 1,
    'kibi': 1024,
    'mebi': Math.pow(1024, 2),
    'gibi': Math.pow(1024, 3),
    'tebi': Math.pow(1024, 4),
    'pebi': Math.pow(1024, 5),
    'exi': Math.pow(1024, 6),
    'zebi': Math.pow(1024, 7),
    'yobi': Math.pow(1024, 8)
  },
  BTU: {
    '': 1,
    'MM': 1e6
  }
}

// Additional prefix sets
prefixes.SHORT_LONG = Object.assign({}, prefixes.SHORT, prefixes.LONG)
prefixes.BINARY_SHORT = Object.assign({}, prefixes.BINARY_SHORT_SI, prefixes.BINARY_SHORT_IEC)
prefixes.BINARY_LONG = Object.assign({}, prefixes.BINARY_LONG_SI, prefixes.BINARY_LONG_IEC)

// Units are a set measure of a particular quantity. Below, each key of UNITS is a different unit. Each unit may either included a specified `quantity`, taken from the list of QUANTITIES above, or it may be defined in terms of other units, in which case the quantity is determined from the definition. The unit may also include `prefixes`, which specify which prefix set will be used for parsing the unit, and `commonPrefixes`, which specifies which prefixes will be used when formatting that unit.
export const units = {
  '': {
    quantity: 'UNITLESS',
    value: 1
  },

  // length
  meter: {
    quantity: 'LENGTH',
    prefixes: 'LONG',
    commonPrefixes: ['nano', 'micro', 'milli', 'centi', '', 'kilo'],
    value: 1,
    aliases: ['meters']
  },
  inch: {
    value: '0.0254 meter',
    aliases: ['inches', 'in']
  },
  foot: {
    value: '12 inch',
    aliases: ['ft', 'feet']
  },
  yard: {
    value: '3 foot',
    aliases: ['yd', 'yards']
  },
  mile: {
    value: '5280 ft',
    aliases: ['mi', 'miles']
  },
  link: {
    value: '7.92 in',
    aliases: ['li', 'links']
  },
  rod: {
    value: '25 link',
    aliases: ['rd', 'rods']
  },
  chain: {
    value: '100 link',
    aliases: ['ch', 'chains']
  },
  m: {
    prefixes: 'SHORT',
    commonPrefixes: ['n', 'u', 'm', 'c', '', 'k'],
    value: '1 meter'
  },
  angstrom: {
    value: '1e-10 m',
    aliases: ['angstroms']
  },

  mil: {
    value: '1e-3 inch'
  },

  // Area
  m2: {
    prefixes: 'SQUARED',
    commonPrefixes: ['m', 'c', '', 'k'],
    value: '1 m^2'
  },
  sqin: '1 in^2',
  sqft: '1 ft^2',
  sqyd: '1 yd^2',
  sqmi: '1 mi^2',
  sqrd: '1 rod^2',
  sqch: '1 chain^2',
  sqmil: '1 mil^2',
  acre: '10 chain^2',
  hectare: '1e4 m^2',

  // Volume
  m3: {
    prefixes: 'CUBIC',
    commonPrefixes: ['m', 'c', '', 'k'],
    value: '1 m^3'
  },
  L: {
    prefixes: 'SHORT',
    commonPrefixes: ['n', 'u', 'm', ''],
    value: '1e-3 m^3',
    aliases: ['l', 'lt']
  }, // litre
  litre: {
    prefixes: 'LONG',
    commonPrefixes: ['nano', 'micro', 'milli', ''],
    value: '1 L',
    aliases: ['liter', 'liters', 'litres']
  },
  cuin: '1 in^3',
  cuft: '1 ft^3',
  cuyd: '1 yd^3',
  teaspoon: {
    value: '4.92892159375 mL',
    aliases: ['teaspoons', 'tsp']
  },
  tablespoon: {
    value: '3 teaspoon',
    aliases: ['tablespoons', 'tbsp']
  },
  drop: '0.05 mL',
  gtt: '0.05 mL',

  // Liquid volume
  minim: {
    value: '0.0125 teaspoon',
    aliases: ['minims']
  },
  fluidounce: {
    value: '0.125 cups',
    aliases: ['floz', 'fluidounces']
  },
  fluiddram: {
    value: '0.125 floz',
    aliases: ['fldr', 'fluiddrams']
  },
  cc: '1 cm^3',
  cup: {
    value: '236.5882365 mL',
    aliases: ['cp', 'cups']
  },
  pint: {
    value: '2 cup',
    aliases: ['pt', 'pints']
  },
  quart: {
    value: '4 cup',
    aliases: ['qt', 'quarts']
  },
  gallon: {
    value: '16 cup',
    aliases: ['gal', 'gallons']
  },
  oilbarrel: {
    value: '42 gal',
    aliases: ['obl', 'oilbarrels']
  },

  // Mass
  g: {
    quantity: 'MASS',
    prefixes: 'SHORT',
    commonPrefixes: ['n', 'u', 'm', '', 'k'],
    value: 0.001
  },
  gram: {
    prefixes: 'LONG',
    commonPrefixes: ['nano', 'micro', 'milli', '', 'kilo'],
    value: '1 g'
  },

  poundmass: {
    value: '0.45359237 kg',
    aliases: ['lb', 'lbs', 'lbm', 'poundmasses']
  },
  ton: '2000 lbm',
  tonne: {
    prefixes: 'LONG',
    commonPrefixes: ['', 'kilo', 'mega', 'giga'],
    value: '1000 kg'
  },
  t: {
    prefixes: 'SHORT',
    // kt could be confused with knot (speed),
    value: '1 tonne'
  },
  grain: {
    value: '64.79891 mg',
    aliases: ['gr']
  },
  ounce: {
    value: '0.0625 lbm',
    aliases: ['oz', 'ounces']
  },
  dram: {
    value: '0.0625 oz',
    aliases: ['dr']
  },
  hundredweight: {
    value: '100 lbm',
    aliases: ['cwt', 'hundredweights']
  },
  stick: {
    value: '4 oz',
    aliases: ['sticks']
  },
  stone: '14 lbm',

  // Time
  s: {
    quantity: 'TIME',
    prefixes: 'SHORT',
    commonPrefixes: ['f', 'p', 'n', 'u', 'm', ''],
    value: 1,
    aliases: ['sec']
  },
  min: {
    value: '60 s',
    aliases: ['minute', 'minutes']
  },
  h: {
    value: '60 min',
    aliases: ['hr', 'hrs', 'hour', 'hours']
  },
  second: {
    prefixes: 'LONG',
    commonPrefixes: ['femto', 'pico', 'nano', 'micro', 'milli', ''],
    value: '1 s',
    aliases: ['seconds']
  },
  day: {
    value: '24 hr',
    aliases: ['days']
  },
  week: {
    value: '7 day',
    aliases: ['weeks']
  },
  month: {
    value: '30.4375 day', // 1/12th of Julian year
    aliases: ['months']
  },
  year: {
    value: '365.25 day', // Julian year
    aliases: ['years']
  },
  decade: {
    value: '10 year', // Julian decade
    aliases: ['decades']
  },
  century: {
    value: '100 year', // Julian century
    aliases: ['centuries']
  },
  millennium: {
    value: '1000 year', // Julian millennium
    aliases: ['millennia']
  },

  // Frequency
  hertz: {
    prefixes: 'LONG',
    commonPrefixes: ['', 'kilo', 'mega', 'giga', 'tera'],
    value: '1/s'
  },
  Hz: {
    prefixes: 'SHORT',
    commonPrefixes: ['', 'k', 'M', 'G', 'T'],
    value: '1 hertz'
  },

  // Angle
  rad: {
    quantity: 'ANGLE',
    prefixes: 'SHORT',
    commonPrefixes: ['m', ''],
    value: 1
  },
  radian: {
    prefixes: 'LONG',
    commonPrefixes: ['milli', ''],
    value: '1 rad',
    aliases: ['radians']
  },
  sr: {
    quantity: 'SOLID_ANGLE',
    prefixes: 'SHORT',
    commonPrefixes: ['u', 'm', ''],
    value: 1
  },
  steradian: {
    prefixes: 'LONG',
    commonPrefixes: ['micro', 'milli', ''],
    value: '1 sr',
    aliases: ['steradians']
  },
  deg: {
    value: [Math.PI / 180, 'rad'],
    aliases: ['degree', 'degrees']
  },
  grad: {
    prefixes: 'SHORT',
    commonPrefixes: ['c'],
    value: [Math.PI / 200, 'rad']
  },
  gradian: {
    prefixes: 'LONG',
    commonPrefixes: ['centi', ''],
    value: [Math.PI / 200, 'rad'],
    aliases: ['gradians']
  },
  cycle: {
    value: [2 * Math.PI, 'rad'],
    aliases: ['cycles']
  },
  arcmin: {
    value: '0.016666666666666666 deg',
    aliases: ['arcminute', 'arcminutes']
  },
  arcsec: {
    value: '0.016666666666666666 arcmin',
    aliases: ['arcsecond', 'arcseconds']
  },

  // Electric current
  A: {
    quantity: 'CURRENT',
    prefixes: 'SHORT',
    commonPrefixes: ['u', 'm', '', 'k'],
    value: 1
  },
  ampere: {
    prefixes: 'LONG',
    commonPrefixes: ['micro', 'milli', '', 'kilo'],
    value: '1 A',
    aliases: ['amperes']
  },

  // Temperature
  // K(C) = °C + 273.15
  // K(F) = (°F + 459.67) / 1.8
  // K(R) = °R / 1.8
  K: {
    quantity: 'TEMPERATURE',
    prefixes: 'SHORT',
    commonPrefixes: ['n', 'u', 'm', ''],
    value: 1
  },
  kelvin: {
    prefixes: 'LONG',
    commonPrefixes: ['nano', 'micro', 'milli', ''],
    value: '1 K'
  },
  degC: {
    value: '1 K',
    offset: 273.15,
    aliases: ['celsius']
  },
  degR: {
    value: [1 / 1.8, 'K'],
    aliases: ['rankine', 'R']
  },
  degF: {
    value: '1 R',
    offset: 459.67,
    aliases: ['fahrenheit']
  },

  // amount of substance
  mol: {
    quantity: 'AMOUNT_OF_SUBSTANCE',
    prefixes: 'SHORT',
    commonPrefixes: ['', 'k'],
    value: 1
  },
  mole: {
    prefixes: 'LONG',
    commonPrefixes: ['', 'kilo'],
    value: '1 mol',
    aliases: ['moles']
  },

  // luminous intensity
  cd: {
    quantity: 'LUMINOUS_INTENSITY',
    value: 1,
    prefixes: 'SHORT',
    commonPrefixes: ['', 'm']
  },
  candela: {
    value: '1 cd',
    prefixes: 'LONG',
    commonPrefixes: ['', 'milli']
  },

  // luminous flux
  lumen: {
    prefixes: 'LONG',
    value: '1 cd sr',
    aliases: ['lumens']
  },
  lm: {
    prefixes: 'SHORT',
    value: '1 lumen'
  },

  // illuminance
  lux: {
    prefixes: 'LONG',
    value: '1 cd/m^2'
  },
  lx: {
    prefixes: 'SHORT',
    value: '1 lux'
  },

  // Force
  N: {
    prefixes: 'SHORT',
    commonPrefixes: ['u', 'm', '', 'k', 'M'], // These could be debatable
    value: '1 kg m/s^2'
  },
  newton: {
    prefixes: 'LONG',
    commonPrefixes: ['micro', 'milli', '', 'kilo', 'mega'],
    value: '1 N',
    aliases: ['newtons']
  },
  dyn: {
    prefixes: 'SHORT',
    commonPrefixes: ['m', 'k', 'M'],
    value: '1 g cm/s^2'
  },
  dyne: {
    prefixes: 'LONG',
    commonPrefixes: ['milli', 'kilo', 'mega'],
    value: '1 dyn'
  },
  lbf: {
    value: '4.4482216152605 N',
    aliases: ['poundforce']
  },
  kip: {
    value: '1000 lbf',
    aliases: ['kips']
  },

  // Energy
  J: {
    prefixes: 'SHORT',
    commonPrefixes: ['m', '', 'k', 'M', 'G'],
    value: '1 N m'
  },
  joule: {
    prefixes: 'LONG',
    commonPrefixes: ['milli', '', 'kilo', 'mega', 'giga'],
    value: '1 J',
    aliases: ['joules']
  },
  erg: {
    value: '1 dyn cm'
  },
  Wh: {
    prefixes: 'SHORT',
    commonPrefixes: ['k', 'M', 'G', 'T'],
    value: '1 W hr'
  },
  BTU: {
    prefixes: 'BTU',
    commonPrefixes: ['', 'MM'],
    value: '1055.05585262 J',
    aliases: ['BTUs']
  },
  eV: {
    prefixes: 'SHORT',
    commonPrefixes: ['u', 'm', '', 'k', 'M', 'G'],
    value: '1.602176565e-19 J'
  },
  electronvolt: {
    prefixes: 'LONG',
    commonPrefixes: ['micro', 'milli', '', 'kilo', 'mega', 'giga'],
    value: '1 eV',
    aliases: ['electronvolts']
  },

  // Power
  W: {
    prefixes: 'SHORT',
    commonPrefixes: ['p', 'n', 'u', 'm', '', 'k', 'M', 'G', 'T', 'P'],
    value: '1 J/s'
  },
  watt: {
    prefixes: 'LONG',
    commonPrefixes: ['pico', 'nano', 'micro', 'milli', '', 'kilo', 'mega', 'tera', 'peta'],
    value: '1 W',
    aliases: ['watts']
  },
  hp: '550 ft lbf / s',

  // Electrical power units
  VA: {
    prefixes: 'SHORT',
    commonPrefixes: ['', 'k'],
    value: '1 W'
  },

  // Pressure
  Pa: {
    prefixes: 'SHORT',
    commonPrefixes: ['', 'k', 'M', 'G'], // 'h' is sometimes used but not often
    value: '1 N / m^2'
  },
  psi: {
    value: '1 lbf/in^2'
    // kpsi is sometimes used
  },
  atm: '101325 Pa',
  bar: {
    prefixes: 'SHORT_LONG',
    commonPrefixes: ['m', ''],
    value: '1e5 Pa'
  },
  torr: {
    prefixes: 'LONG',
    commonPrefixes: ['milli', ''],
    value: '133.32236842105263 Pa'
  },
  Torr: {
    prefixes: 'SHORT',
    commonPrefixes: ['m', ''],
    value: '1 torr'
  },
  mmHg: {
    value: '133.322387415 Pa',
    aliases: ['mmhg']
  },
  inH2O: {
    value: '249.082 Pa',
    aliases: ['inh2o', 'inAq']
  },

  // Electric charge
  C: {
    prefixes: 'SHORT',
    commonPrefixes: ['p', 'n', 'u', 'm', ''],
    value: '1 A s'
  },
  coulomb: {
    prefixes: 'LONG',
    commonPrefixes: ['pico', 'nano', 'micro', 'milli', ''],
    value: '1 C',
    aliases: ['coulombs']
  },

  // Electric potential
  V: {
    prefixes: 'SHORT',
    commonPrefixes: ['m', '', 'k', 'M'],
    value: '1 W/A'
  },
  volt: {
    prefixes: 'LONG',
    commonPrefixes: ['milli', '', 'kilo', 'mega'],
    value: '1 V',
    aliases: ['volts']
  },
  // Electric capacitance
  F: {
    prefixes: 'SHORT',
    commonPrefixes: ['p', 'n', 'u', 'm', ''],
    value: '1 C/V'
  },
  farad: {
    prefixes: 'LONG',
    commonPrefixes: ['pico', 'nano', 'micro', 'milli', ''],
    value: '1 F',
    aliases: ['farads']
  },

  // Electric resistance
  ohm: {
    prefixes: 'SHORT_LONG', // Both Mohm and megaohm are acceptable
    commonPrefixes: ['', 'k', 'M'],
    value: '1 V/A',
    aliases: ['ohms']
  },
  /*
   * Unicode breaks in browsers if charset is not specified
   * TODO: Allow with config option?
  Ω: {
    prefixes: 'SHORT',
    commonPrefixes: ['', 'k', 'M'],
    value: '1 ohm',
  },
  */
  // Electric inductance
  H: {
    prefixes: 'SHORT',
    commonPrefixes: ['u', 'm', ''],
    value: '1 V s / A'
  },
  henry: {
    prefixes: 'LONG',
    commonPrefixes: ['micro', 'milli', ''], // Just guessing here
    value: '1 H',
    aliases: ['henries']
  },
  // Electric conductance
  S: {
    prefixes: 'SHORT',
    commonPrefixes: ['u', 'm', ''],
    value: '1 / ohm'
  },
  siemens: {
    prefixes: 'LONG',
    commonPrefixes: ['micro', 'milli', ''],
    value: '1 S'
  },
  // Magnetic flux
  Wb: {
    prefixes: 'SHORT',
    commonPrefixes: ['n', 'u', 'm', ''],
    value: '1 V s'
  },
  weber: {
    prefixes: 'LONG',
    commonPrefixes: ['nano', 'micro', 'milli', ''],
    value: '1 Wb',
    aliases: ['webers']
  },
  // Magnetic flux density
  T: {
    prefixes: 'SHORT',
    commonPrefixes: ['n', 'u', 'm', ''],
    value: '1 N s / C m'
  },
  tesla: {
    prefixes: 'LONG',
    commonPrefixes: ['nano', 'micro', 'milli', ''],
    value: '1 T',
    aliases: ['teslas']
  },

  // Binary
  // TODO: Figure out how to do SI vs. IEC while formatting
  b: {
    quantity: 'BIT',
    prefixes: 'BINARY_SHORT',
    value: 1
  },
  bits: {
    prefixes: 'BINARY_LONG',
    value: '1 b',
    aliases: ['bit']
  },
  B: {
    prefixes: 'BINARY_SHORT',
    value: '8 b'
  },
  bytes: {
    prefixes: 'BINARY_LONG',
    value: '1 B',
    aliases: ['byte']
  }
}


<|start_filename|>test/Unit.test.js<|end_filename|>
import assert from 'assert'
import approx from './approx'
import unit from '../src/Unit.js'
import Decimal from 'decimal.js'

function configCustomUnits (units) {
  return unit.config({
    definitions: {
      units
    }
  })
}

let unitDec
let typeNoPow, typeNoGt, typeNoComp, typeFunnyFormat, typeNoRound

before(() => {
  Decimal.set({ precision: 32 })

  // Use strict type checking to make sure no numbers sneak in
  const isDec = a => assert(a instanceof Decimal)

  let typeComplete = {
    conv: a => Decimal(a),
    clone: a => { isDec(a); return Decimal(a) },
    add: (a, b) => {
      isDec(a)
      isDec(b)
      return Decimal.add(a, b)
    },
    sub: (a, b) => {
      isDec(a)
      isDec(b)
      return Decimal.sub(a, b)
    },
    mul: (a, b) => {
      isDec(a)
      isDec(b)
      return Decimal.mul(a, b)
    },
    div: (a, b) => {
      isDec(a)
      isDec(b)
      return Decimal.div(a, b)
    },
    pow: (a, b) => {
      isDec(a)
      isDec(b)
      return Decimal.pow(a, b)
    },
    eq: (a, b) => {
      isDec(a)
      isDec(b)
      return a.equals(b)
    },
    lt: (a, b) => a.lt(b),
    le: (a, b) => a.lte(b),
    gt: (a, b) => a.gt(b),
    ge: (a, b) => a.gte(b),
    abs: (a) => a.abs(),
    round: (a) => a.round(),
    trunc: (a) => Decimal.trunc(a)
  }

  typeNoPow = Object.assign({}, typeComplete)
  delete typeNoPow.pow

  typeNoGt = Object.assign({}, typeComplete)
  delete typeNoGt.gt

  typeNoRound = Object.assign({}, typeComplete)
  delete typeNoRound.round

  typeNoComp = Object.assign({}, typeComplete)
  delete typeNoComp.eq
  delete typeNoComp.lt
  delete typeNoComp.le
  delete typeNoComp.gt
  delete typeNoComp.ge

  typeFunnyFormat = { format: (a, b, c) => b + a.toString().split('').reverse().join(c) }

  unitDec = require('../index.js').config({ type: typeComplete })

  // These will be tested below
  // unitDecNoPow = require('../index.js').config({ type: typeNoPow })
  // unitDecNoGt = require('../index.js').config({ type: typeNoGt })
  // unitDecNoEq = require('../index.js').config({ type: typeNoEq })
})

describe('unitmath', () => {
  describe('unitmath namespace', () => {
    it('should be a function', () => {
      assert.strictEqual(typeof unit, 'function')
    })

    it('should return a unit', () => {
      assert.strictEqual(unit(1, 'm').type, 'Unit')
    })

    it('should have a config method', () => {
      assert.strictEqual(typeof unit.config, 'function')
    })

    it('should be frozen', () => {
      assert.throws(() => { unit.config = 42 })
      assert.throws(() => { unit.foo = 42 })
    })

    it('should have the correct default format config options', () => {
      let optionsToCheckEquality = {
        parentheses: false,
        precision: 15,
        prefix: 'auto',
        prefixMin: 0.1,
        prefixMax: 1000,
        prefixesToChooseFrom: 'common',
        simplify: 'auto',
        simplifyThreshold: 2,
        system: 'auto',
        subsystem: 'auto',
        definitions: {
          skipBuiltIns: false,
          units: {},
          prefixes: {},
          baseQuantities: [],
          quantities: {},
          unitSystems: {}
        }
      }
      let actualOptions = unit.config()
      for (let key in optionsToCheckEquality) {
        assert.deepStrictEqual(optionsToCheckEquality[key], actualOptions[key], `config option ${key} has the wrong default value`)
      }
    })
  })

  describe('config', () => {
    it('should return current config when called with no arguments', () => {
      assert.strictEqual(typeof unit.config(), 'object')
    })

    it('should clone the options argument', () => {
      let options = { prefix: 'always' }
      let newUnit = unit.config(options)
      assert.notStrictEqual(options, newUnit.config())
    })

    it('should freeze the options', () => {
      let newUnit = unit.config({})
      let options = newUnit.config()
      assert.throws(() => { options.prefix = 'always' })
    })

    it('should set new config options', () => {
      let newUnit = unit.config({ prefix: 'always' })
      assert.strictEqual(unit.config().prefix, 'auto')
      assert.strictEqual(newUnit.config().prefix, 'always')
      assert.strictEqual(newUnit.config().simplify, 'auto')
    })

    it('should throw on invalid options', () => {
      assert.throws(() => unit.config({ prefix: 'invalidOption' }), /Invalid option for prefix: 'invalidOption'/)
      assert.throws(() => unit.config({ simplify: 'bad' }), /Invalid option for simplify: 'bad'/)
    })

    describe('custom definitions', () => {
      it('should create new units', () => {
        let newUnit = configCustomUnits({
          furlongsPerFortnight: { value: '1 furlong/fortnight' },
          furlong: '220 yards',
          fortnight: { value: [2, 'weeks'] }
        })

        assert.strictEqual(newUnit('1 furlongsPerFortnight').to('yards/week').toString(), '110 yards / week')
      })

      it.skip('should use custom units when simplifying', () => {
        let newUnit = configCustomUnits({
          mph: { value: '1 mi/hr', autoAddToSystem: 'auto' }
        })
        assert.strictEqual(newUnit('5 mi').div('2 hr').toString(), '2.5 mph')
      })

      it.skip('should use custom units derived from other custom units when simplifying', () => {
        const newUnit = configCustomUnits({
          widget: { value: '5 kg bytes', autoAddToSystem: 'auto' },
          woggle: { value: '4 widget^2', autoAddToSystem: 'auto' },
          gadget: { value: '5 N/woggle', autoAddToSystem: 'auto' },
          whimsy: { value: '8 gadget hours', autoAddToSystem: 'auto' }
        })
        assert.strictEqual(newUnit(1000, 'N h kg^-2 bytes^-2').toString(), '2500 whimsy')
      })

      it.skip('should apply prefixes and offset to custom units', () => {
        const newUnit = configCustomUnits({
          wiggle: { value: '4 rad^2/s', offset: 1, prefixes: 'LONG', autoAddToSystem: 'auto' }
        })
        assert.strictEqual(newUnit('8000 rad^2/s').toString(), '1 kilowiggle')
      })

      it('should only allow valid names for units', () => {
        assert.throws(() => configCustomUnits({ 'not_a_valid_unit': '3.14 kg' }), /Unit name contains non-alpha/)
        assert.throws(() => configCustomUnits({ '5tartsWithNumber': '42 ft' }), /Unit name contains non-alpha/)
        assert.throws(() => configCustomUnits({ 5: '5 day' }), /Unit name contains non-alpha/)
      })

      it('should throw on invalid unit value type', () => {
        assert.throws(() => configCustomUnits({ myUnit: 42 }), /Unit definition for 'myUnit' must be a string/)

        assert.throws(() => configCustomUnits({ myUnit: { value: 42 } }), /Unit definition for 'myUnit' must be a string/)
      })

      it('should throw on invalid unit quantity', () => {
        assert.throws(() => configCustomUnits({
          myUnit: {
            quantity: 'ESSENCE_OF_FOO',
            value: 100
          }
        }), /Unknown quantity specified for unit myUnit: ESSENCE_OF_FOO/)
      })

      it('should override existing units', () => {
        let newUnit = configCustomUnits({
          poundmass: {
            value: '0.5 kg',
            aliases: ['lb', 'lbs', 'lbm', 'poundmasses']
          }
        })
        assert.strictEqual(unit('1 lb').to('kg').toString(), '0.45359237 kg')
        assert.strictEqual(newUnit('1 lb').to('kg').toString(), '0.5 kg')
      })

      it('should remove existing units if value is falsey', () => {
        let newUnit = configCustomUnits({ henry: null })
        assert.doesNotThrow(() => unit('henry'))
        assert.throws(() => newUnit('henry'), /Unit "henry" not found/)
      })

      it('should throw if not all units could be created', () => {
        assert.throws(() => configCustomUnits({
          myUnit: '1 theirUnit',
          theirUnit: '1 myUnit'
        }), /Error: Could not create the following units: myUnit, theirUnit. Reasons follow: SyntaxError: Unit "theirUnit" not found. SyntaxError: Unit "myUnit" not found./)

        assert.throws(() => configCustomUnits({
          myUnit: 'q038hfqi3hdq0'
        }), /Error: Could not create the following units: myUnit. Reasons follow: SyntaxError: Unit "q038hfqi3hdq0" not found./)

        assert.throws(() => configCustomUnits({
          myUnit: '8 m^'
        }), /In "8 m\^", "\^" must be followed by a floating-point number/)
      })

      it('should throw if an alias would override an existing unit', () => {
        assert.throws(() => configCustomUnits({
          myUnit: {
            value: '1 m',
            aliases: ['m']
          }
        }), /Alias 'm' would override an existing unit/)
      })

      it('should throw on unknown prefix', () => {
        assert.throws(() => configCustomUnits({
          myUnit: {
            value: '45 s',
            prefixes: 'MADE_UP_PREFIXES'
          }
        }), /Unknown prefixes 'MADE_UP_PREFIXES' for unit 'myUnit'/)
      })

      it('should create new prefixes', () => {
        // TODO: Mutating individual units in the definitions can have bad side effects!
        let meter = Object.assign({}, unit.definitions().units.meter)
        meter.prefixes = 'FUNNY'
        meter.commonPrefixes = ['', 'A', 'B', 'C', 'D', 'E', 'F', 'G']
        let newUnit = unit.config({
          prefixMin: 1,
          prefixMax: 2,
          definitions: {
            units: {
              meter
            },
            prefixes: {
              FUNNY: { '': 1, 'A': 2, 'B': 4, 'C': 8, 'D': 16, 'E': 32, 'F': 64, 'G': 128 }
            }
          }
        })

        assert.strictEqual(newUnit('6 meter').toString(), '1.5 Bmeter')
        assert.strictEqual(newUnit('10 Cmeter').toString(), '1.25 Fmeter')
      })

      it('should only allow common prefixes that are included in prefixes', () => {
        let meter = Object.assign({}, unit.definitions().units.meter)
        meter.commonPrefixes = ['', 'invalidPrefix']
        assert.throws(() => configCustomUnits({
          meter
        }), /common prefix.*was not found among the allowable prefixes/)
      })

      it('should create new base quantities and derived quantities', () => {
        let newUnit = unit.config({
          definitions: {
            baseQuantities: ['ESSENCE_OF_FOO'],
            quantities: {
              'FOOLUME': 'ESSENCE_OF_FOO^3',
              'FOOLOCITY': 'ESSENCE_OF_FOO TIME^-1'
            },
            units: {
              foo: {
                quantity: 'ESSENCE_OF_FOO',
                value: 1,
                prefixes: 'LONG'
              },
              fib: '5 foo/hr',
              flab: '1 foo^3'
            },
            unitSystems: {
              si: {
                ESSENCE_OF_FOO: 'foo',
                FOOLUME: 'flab',
                FOOLOCITY: 'fib'
              }
            }
          }
        })

        assert.deepStrictEqual(newUnit('fib').getQuantities(), ['FOOLOCITY'])
        assert.deepStrictEqual(newUnit('flab').getQuantities(), ['FOOLUME'])
        assert.strictEqual(newUnit('1 megafoo/s').simplify().toString(), '720000000 fib')
        assert.strictEqual(newUnit('1 megafoo/s').to('fib').toString(), '720000000 fib')
        assert.strictEqual(newUnit('3 foo').pow(3).toString(), '27 flab')
      })

      it('should throw on duplicate base quantities', () => {
        assert.throws(() => unit.config({
          definitions: {
            baseQuantities: ['MASS']
          }
        }), /Duplicate base quantity: MASS/)
      })

      it('should throw on invalid quantity definitions', () => {
        assert.throws(() => unit.config({
          definitions: {
            quantities: {
              'FOONESS': 'LENGTH^UMPTEENTHPOWER'
            }
          }
        }), /Error processing quantity FOONESS: could not determine value of the exponent in string LENGTH\^UMPTEENTHPOWER/)

        assert.throws(() => unit.config({
          definitions: {
            quantities: {
              'AWESOME': 'SAUCE'
            }
          }
        }), /Error processing quantity AWESOME: base quantity SAUCE not found/)
      })

      it('should extend, but not replace, individual unit systems', () => {
        // TODO: Still no good way to say that mi/hr should be replaced by mph unless mi is explicitly designated as a part of the unit system
        let newUnit = unit.config({
          definitions: {
            units: { mph: '1 mi/hr' },
            unitSystems: {
              us: { VELOCITY: 'mph', LENGTH: 'mi' }
            }
          }
        })
        assert.deepStrictEqual(newUnit('70 mi').div('60 min').toString(), '70 mph')
        assert.deepStrictEqual(newUnit.definitions().unitSystems.us.MASS, 'lbm')
      })

      it('should throw on unknown unit systems', () => {
        assert.throws(() => {
          unit.config({ system: 'nervous' })
        }, /Unknown unit system/)
      })

      it('should not allow unknown units in unit systems', () => {
        assert.throws(() => {
          unit.config({
            definitions: {
              unitSystems: { si: { LENGTH: 'fiddlesticks' } }
            }
          })
        }, /Unknown unit.*for quantity.*in unit system/)
        assert.doesNotThrow(() => {
          unit.config({
            definitions: {
              unitSystems: { si: { LENGTH: 'chain' } }
            }
          })
        })
      })

      it('should not allow unknown quantities in unit systems', () => {
        assert.throws(() => {
          unit.config({
            definitions: {
              unitSystems: { si: { ESSENCE_OF_FOO: 'meter' } }
            }
          })
        }, /Unit system.*mentions quantity.*which does not exist/)
      })

      it('should not allow inconsistent units/quantities in unit systems', () => {
        assert.throws(() => {
          unit.config({
            definitions: {
              unitSystems: { si: { LENGTH: 'day' } }
            }
          })
        }, /In system.*quantity.*is inconsistent with unit/)
      })

      it('should add new unit systems', () => {
        let newUnit = unit.config({
          system: 'custom',
          definitions: {
            unitSystems: {
              custom: { LENGTH: 'rod' }
            }
          }
        })
        assert.strictEqual(newUnit('66 ft').simplify().toString(), '4 rod')
      })

      it('should skip builtins if so desired', () => {
        let newUnit = unit.config({
          prefixMin: 0.200001,
          prefixMax: 4.99999,
          definitions: {
            skipBuiltIns: true,
            baseQuantities: ['ESSENCE_OF_FOO'],
            quantities: {
              'FOOLUME': 'ESSENCE_OF_FOO^3'
            },
            units: {
              foo: {
                quantity: 'ESSENCE_OF_FOO',
                value: 1,
                prefixes: 'PREFOO',
                commonPrefixes: ['fff', 'ff', 'f', '', 'F', 'FF', 'FFF']
              },
              fib: { value: '5 foo', prefixes: 'PREFOO', commonPrefixes: ['fff', 'ff', 'f', '', 'F', 'FF', 'FFF'] },
              flab: { value: '1 foo^3', prefixes: 'PREFOO', commonPrefixes: ['fff', 'ff', 'f', '', 'F', 'FF', 'FFF'] }
            },
            unitSystems: {
              fooSys: {
                ESSENCE_OF_FOO: 'foo',
                FOOLUME: 'flab'
              }
            },
            prefixes: {
              PREFOO: {
                'fff': 0.008,
                'ff': 0.04,
                'f': 0.2,
                '': 1,
                'F': 5,
                'FF': 25,
                'FFF': 125
              }
            }
          }
        })

        assert.strictEqual(newUnit('40 fib').format(), '1.6 FFfib')
      })

      describe('autoAddToSystem', () => {
        it.skip('should add the unit to the specified system', () => {
          let newUnit = unit.config({
            system: 'us',
            definitions: {
              units: {
                mph: {
                  value: '1 mile/hour',
                  autoAddToSystem: 'us'
                }
              }
            }
          })
          assert.strictEqual(newUnit.definitions().unitSystems.us.VELOCITY, 'mph')
        })

        it.skip('should auto-select system when \'auto\'', () => {
          let newUnit = unit.config({
            definitions: {
              units: {
                mph: {
                  value: '1 mile/hour',
                  autoAddToSystem: 'auto'
                }
              }
            }
          })
          assert.strictEqual(newUnit.definitions().unitSystems.us.VELOCITY, 'mph')
        })

        it.skip('should format matching Units using the newly added unit only if autoAddToSystem was set', () => {
          let newUnit = unit.config({
            definitions: {
              units: {
                mph: {
                  value: '1 mile/hour',
                  autoAddToSystem: 'us'
                }
              }
            }
          })
          let newUnitNoAuto = unit.config({
            definitions: {
              units: {
                mph: '1 mile/hour'
              }
            }
          })
          assert.strictEqual(unit('1 mile/hour').simplify().toString(), '1 mile/hour')
          assert.strictEqual(newUnitNoAuto('1 mile/hour').simplify().toString(), '1 mile/hour')
          assert.strictEqual(newUnit('1 mile/hour').simplify().toString(), '1 mph')
        })

        it.skip('should create new quantities', () => {
          let newUnit = unit.config({
            definitions: {
              units: {
                snap: {
                  value: '1 m/s^4',
                  autoAddToSystem: 'auto'
                }
              }
            }
          })
          assert.deepStrictEqual(newUnit('1 m/s^4').getQuantities(), ['snap_QUANTITY'])
          assert.strictEqual(newUnit('1 m/s^4').simplify().toString(), '1 snap')
        })

        it.skip('should not override existing quantities', () => {
          let newUnit = unit.config({
            definitions: {
              units: {
                snap: {
                  value: '1 m/s^4',
                  autoAddToSystem: 'auto'
                }
              },
              quantities: {
                JOUNCE: ['LENGTH/TIME^4']
              }
            }
          })
          assert.deepStrictEqual(newUnit('1 m/s^4').getQuantities(), ['JOUNCE'])
          assert.strictEqual(newUnit('1 m/s^4').simplify().toString(), '1 snap')
        })

        it('should not override existing unitSystem', () => {
          let newUnit = unit.config({
            definitions: {
              units: {
                tenmeter: {
                  value: '10 m',
                  autoAddToSystem: 'si'
                }
              }
            }
          })
          assert.strictEqual(newUnit('100 J').div('10 N').simplify().toString(), '10 m') // Not '1 tenmeter'
        })

        it.skip('should work as global option: definitions.autoAddToSystem', () => {
          let newUnit = unit.config({
            definitions: {
              units: {
                mph: '1 mile/hour'
              },
              autoAddToSystem: 'us'
            }
          })
          assert.strictEqual(newUnit('1 mile/hour').simplify().toString(), '1 mph')
        })

        it.skip('should throw if autoAddToSystem is not a string or known unit system', () => {
          assert.throws(() => {
            unit.config({ definitions: { units: { mph: { value: '1 mile/hour', autoAddToSystem: false } } } })
          }, /autoAddToSystem must be a string: either a known unit system or 'auto'/)
          assert.throws(() => {
            unit.config({ definitions: { units: { mph: { value: '1 mile/hour', autoAddToSystem: 'nervous' } } } })
          }, /autoAddToSystem must be a string: either a known unit system or 'auto'/)
        })
      })
    })

    describe('newly returned namespace', () => {
      it('should be a new unitmath namespace', () => {
        let newUnit = unit.config({})
        assert.notStrictEqual(unit, newUnit)
      })
      it('should create unit instances in a separate prototype chain', () => {
        let newUnit = unit.config({})
        assert.notStrictEqual(unit(1, 'm').add, newUnit(1, 'm').add)
      })
      it('should have a clone of the config options from the first namespace', () => {
        let newUnit = unit.config({})
        assert.notStrictEqual(unit.config(), newUnit.config())
      })
    })
  })

  describe('definitions', () => {
    it('should return the original built-in unit definitions', () => {
      let defs = unit.definitions()

      assert.strictEqual(defs.units.inch.value, '0.0254 meter')
      assert.deepStrictEqual(defs.units.foot.aliases, ['ft', 'feet'])
      assert.strictEqual(defs.units.kelvin.prefixes, 'LONG')
      assert.strictEqual(defs.prefixes.LONG.giga, 1e9)
      assert.strictEqual(defs.prefixes.SHORT_LONG.giga, 1e9)
      assert.strictEqual(defs.unitSystems.si.FORCE, 'N')
      assert.strictEqual(defs.baseQuantities[0], 'MASS')
      assert.strictEqual(defs.quantities.AREA, 'LENGTH^2')

      // TODO: Add custom unit below so that the units get reprocessed (in case we cache unit definitions in the future)
      let defs2 = unit.config({}).definitions()

      assert.strictEqual(defs2.units.inch.value, '0.0254 meter')
      assert.deepStrictEqual(defs2.units.foot.aliases, ['ft', 'feet'])
      assert.strictEqual(defs2.units.kelvin.prefixes, 'LONG')
      assert.strictEqual(defs2.prefixes.LONG.giga, 1e9)
      assert.strictEqual(defs2.prefixes.SHORT_LONG.giga, 1e9)
      assert.strictEqual(defs2.unitSystems.si.FORCE, 'N')
      assert.strictEqual(defs2.baseQuantities[0], 'MASS')
      assert.strictEqual(defs2.quantities.AREA, 'LENGTH^2')
    })
  })

  describe('unit instance', () => {
    it('should have prototype methods add, mul, etc.', () => {
      let u1 = unit(1, 'm')
      let u2 = unit(2, 'kg')
      let fns = ['add', 'mul']
      fns.forEach(fn => {
        assert.strictEqual(typeof u1[fn], 'function')
      })
      assert.strictEqual(u1.add, u2.add)
    })

    it('should be frozen', () => {
      assert(Object.isFrozen(unit(1, 'm')))
    })
  })

  describe('factory function', () => {
    it('should create unit correctly', () => {
      let unit1 = unit()
      assert.strictEqual(unit1.value, null)
      assert.strictEqual(unit1.units.length, 0)

      unit1 = unit(5000, 'cm')
      assert.strictEqual(unit1.value, 5000)
      assert.strictEqual(unit1.units[0].unit.name, 'm')

      unit1 = unit(5, 'kg')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')

      unit1 = unit('kg')
      assert.strictEqual(unit1.value, null)
      assert.strictEqual(unit1.units[0].unit.name, 'g')

      unit1 = unit('10 Hz')
      assert.strictEqual(unit1.value, 10)
      assert.strictEqual(unit1.units[0].unit.name, 'Hz')

      unit1 = unit(9.81, 'kg m/s^2')
      assert.strictEqual(unit1.value, 9.81)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'm')
      assert.strictEqual(unit1.units[2].unit.name, 's')

      unit1 = unit('9.81 kg m/s^2')
      assert.strictEqual(unit1.value, 9.81)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'm')
      assert.strictEqual(unit1.units[2].unit.name, 's')

      unit1 = unit('9.81', 'kg m/s^2')
      assert.strictEqual(unit1.value, 9.81)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'm')
      assert.strictEqual(unit1.units[2].unit.name, 's')
    })

    it('should combine duplicate units', () => {
      assert.deepStrictEqual(unit('3 kg kg'), unit('3 kg^2'))
      assert.deepStrictEqual(unit('3 kg/kg'), unit('3'))
      assert.deepStrictEqual(unit('3 kg m / s s'), unit('3 kg m / s^2'))
      assert.deepStrictEqual(unit('3 m cm'), unit('0.03 m^2'))
      approx.deepEqual(unit('3 cm m / s minute'), unit('300 cm^2 / s minute'))
    })

    it('should ignore properties on Object.prototype', () => {
      Object.prototype.foo = unit._unitStore.defs.units['meter'] // eslint-disable-line no-extend-native

      assert.throws(() => { console.log(unit(1, 'foo')) }, /Unit "foo" not found/)

      delete Object.prototype.foo
    })

    it('should throw an error if called with wrong type of arguments', () => {
      assert.throws(() => { console.log(unit(0, 'bla')) }, /Unit.*not found/)
      assert.throws(() => { console.log(unit(0, 3)) }, /you must supply a single/)
    })
  })

  describe('exists', () => {
    it('should return true if the string contains unit plus a prefix', () => {
      assert.strictEqual(unit.exists('cm'), true)
      assert.strictEqual(unit.exists('inch'), true)
      assert.strictEqual(unit.exists('kb'), true)
      assert.strictEqual(unit.exists('bla'), false)
      assert.strictEqual(unit.exists('5cm'), false)
    })

    it('should throw on invalid parameter', () => {
      assert.throws(() => unit.exists(42), /parameter must be a string/)
    })
  })

  describe('getQuantity', () => {
    it('should return the QUANTITY matching this unit', () => {
      assert.deepStrictEqual(unit(5, 'kg m s K A rad bits').getQuantities(), [])
      assert.deepStrictEqual(unit(5).getQuantities(), ['UNITLESS'])
      assert.deepStrictEqual(unit(5, 'm s').getQuantities(), ['ABSEMENT'])
      assert.deepStrictEqual(unit(5, 'm/s^2').getQuantities(), ['ACCELERATION'])
      assert.deepStrictEqual(unit(5, 'deg').getQuantities(), ['ANGLE'])
      assert.deepStrictEqual(unit(5, 'rad/s^2').getQuantities(), ['ANGULAR_ACCELERATION'])
      assert.deepStrictEqual(unit(5, '5 kg m^2 rad/s').getQuantities(), ['ANGULAR_MOMENTUM'])
      assert.deepStrictEqual(unit(5, '5 rad/s').getQuantities(), ['ANGULAR_VELOCITY'])
      assert.deepStrictEqual(unit(5, 'mol').getQuantities(), ['AMOUNT_OF_SUBSTANCE'])
      assert.deepStrictEqual(unit(5, 'm^2').getQuantities(), ['AREA'])
      assert.deepStrictEqual(unit(5, 'kg/m^2').getQuantities(), ['AREA_DENSITY'])
      assert.deepStrictEqual(unit(5, 'kb').getQuantities(), ['BIT'])
      assert.deepStrictEqual(unit(5, 'Gb/s').getQuantities(), ['BIT_RATE'])
      assert.deepStrictEqual(unit(5, 'C/V').getQuantities(), ['CAPACITANCE'])
      assert.deepStrictEqual(unit(5, 'A/m^2').getQuantities(), ['CURRENT_DENSITY'])
      assert.deepStrictEqual(unit(5, 'A').getQuantities(), ['CURRENT'])
      assert.deepStrictEqual(unit(5, 'Pa s').getQuantities(), ['DYNAMIC_VISCOSITY'])
      assert.deepStrictEqual(unit(5, 'C').getQuantities(), ['ELECTRIC_CHARGE'])
      assert.deepStrictEqual(unit(5, 'C/m^3').getQuantities(), ['ELECTRIC_CHARGE_DENSITY'])
      assert.deepStrictEqual(unit(5, 'C/m^2').getQuantities(), ['ELECTRIC_DISPLACEMENT'])
      assert.deepStrictEqual(unit(5, 'V/m').getQuantities(), ['ELECTRIC_FIELD_STRENGTH'])
      assert.deepStrictEqual(unit(5, 'siemens').getQuantities(), ['ELECTRICAL_CONDUCTANCE'])
      assert.deepStrictEqual(unit(5, 'siemens/m').getQuantities(), ['ELECTRICAL_CONDUCTIVITY'])
      assert.deepStrictEqual(unit(5, 'V').getQuantities(), ['ELECTRIC_POTENTIAL'])
      assert.deepStrictEqual(unit(5, 'ohm').getQuantities(), ['RESISTANCE', 'IMPEDANCE'])
      assert.deepStrictEqual(unit(5, 'ohm m').getQuantities(), ['ELECTRICAL_RESISTIVITY'])
      assert.deepStrictEqual(unit(5, 'kg m^2 / s^2').getQuantities(), ['ENERGY', 'TORQUE'])
      assert.deepStrictEqual(unit(5, 'J / K').getQuantities(), ['ENTROPY', 'HEAT_CAPACITY'])
      assert.deepStrictEqual(unit(5, 'kg m / s^2').getQuantities(), ['FORCE'])
      assert.deepStrictEqual(unit(5, 's^-1').getQuantities(), ['FREQUENCY'])
      assert.deepStrictEqual(unit(5, 'W/m^2').getQuantities(), ['HEAT_FLUX_DENSITY', 'IRRADIANCE'])
      assert.deepStrictEqual(unit(5, 'N s').getQuantities(), ['IMPULSE', 'MOMENTUM'])
      assert.deepStrictEqual(unit(5, 'henry').getQuantities(), ['INDUCTANCE'])
      assert.deepStrictEqual(unit(5, 'm/s^3').getQuantities(), ['JERK'])
      assert.deepStrictEqual(unit(5, 'm^2/s').getQuantities(), ['KINEMATIC_VISCOSITY'])
      assert.deepStrictEqual(unit(5, 'cm').getQuantities(), ['LENGTH'])
      assert.deepStrictEqual(unit(5, 'kg/m').getQuantities(), ['LINEAR_DENSITY'])
      assert.deepStrictEqual(unit(5, 'candela').getQuantities(), ['LUMINOUS_INTENSITY'])
      assert.deepStrictEqual(unit(5, 'A/m').getQuantities(), ['MAGNETIC_FIELD_STRENGTH'])
      assert.deepStrictEqual(unit(5, 'tesla m^2').getQuantities(), ['MAGNETIC_FLUX'])
      assert.deepStrictEqual(unit(5, 'tesla').getQuantities(), ['MAGNETIC_FLUX_DENSITY'])
      assert.deepStrictEqual(unit(5, 'lbm').getQuantities(), ['MASS'])
      assert.deepStrictEqual(unit(5, 'mol/m^3').getQuantities(), ['MOLAR_CONCENTRATION'])
      assert.deepStrictEqual(unit(5, 'J/mol').getQuantities(), ['MOLAR_ENERGY'])
      assert.deepStrictEqual(unit(5, 'J/mol K').getQuantities(), ['MOLAR_ENTROPY', 'MOLAR_HEAT_CAPACITY'])
      assert.deepStrictEqual(unit(5, 'H/m').getQuantities(), ['PERMEABILITY'])
      assert.deepStrictEqual(unit(5, 'F/m').getQuantities(), ['PERMITTIVITY'])
      assert.deepStrictEqual(unit(5, 'kg m^2 / s^3').getQuantities(), ['POWER'])
      assert.deepStrictEqual(unit(5, 'kg / m s^2').getQuantities(), ['PRESSURE'])
      assert.deepStrictEqual(unit(5, 'H^-1').getQuantities(), ['RELUCTANCE'])
      assert.deepStrictEqual(unit(5, 'H^-1').getQuantities(), ['RELUCTANCE'])
      assert.deepStrictEqual(unit(5, 'J/kg').getQuantities(), ['SPECIFIC_ENERGY'])
      assert.deepStrictEqual(unit(5, 'J/kg K').getQuantities(), ['SPECIFIC_HEAT_CAPACITY'])
      assert.deepStrictEqual(unit(5, 'm^3/kg').getQuantities(), ['SPECIFIC_VOLUME'])
      assert.deepStrictEqual(unit(5, 'kg m^2/s').getQuantities(), ['SPIN'])
      assert.deepStrictEqual(unit(5, 'J/m^2').getQuantities(), ['SURFACE_TENSION'])
      assert.deepStrictEqual(unit(5, 'K').getQuantities(), ['TEMPERATURE'])
      assert.deepStrictEqual(unit(5, 'K/m').getQuantities(), ['TEMPERATURE_GRADIENT'])
      assert.deepStrictEqual(unit(5, 'W/m K').getQuantities(), ['THERMAL_CONDUCTIVITY'])
      assert.deepStrictEqual(unit(5, 'day').getQuantities(), ['TIME'])
      assert.deepStrictEqual(unit(5, 'm/s').getQuantities(), ['VELOCITY'])
      assert.deepStrictEqual(unit(5, 'm^3').getQuantities(), ['VOLUME'])
      assert.deepStrictEqual(unit(5, 'm^3/s').getQuantities(), ['VOLUMETRIC_FLOW_RATE'])
    })
  })

  describe('hasQuantity', () => {
    it('should test whether a unit has a certain dimension', () => {
      assert.strictEqual(unit(5, 'cm').hasQuantity('ANGLE'), false)
      assert.strictEqual(unit(5, 'cm').hasQuantity('LENGTH'), true)
      assert.strictEqual(unit(5, 'kg m / s ^ 2').hasQuantity('FORCE'), true)
    })

    it('should return false for an unknown quantity', () => {
      assert.strictEqual(unit(5, 'cm').hasQuantity('FOOLOCITY'), false)
    })

    it('should work if passed a quantity array directly from the compiled definitions', () => {
      assert.strictEqual(unit(5, 'kg m / s ^ 2').hasQuantity(unit._unitStore.defs.quantities['FORCE']), true)
    })
  })

  describe('equalQuantity', () => {
    it('should test whether two units are of the same quantity', () => {
      assert.strictEqual(unit(5, 'cm').equalQuantity(unit(10, 'm')), true)
      assert.strictEqual(unit(5, 'cm').equalQuantity(unit(10, 'kg')), false)
      assert.strictEqual(unit(5, 'N').equalQuantity(unit(10, 'kg m / s ^ 2')), true)
      assert.strictEqual(unit(8.314, 'J / mol K').equalQuantity(unit(0.02366, 'ft^3 psi / mol degF')), true)
    })
  })

  describe('equals', () => {
    it('should test whether two units are equal', () => {
      assert.strictEqual(unit(100, 'cm').equals(unit(1, 'm')), true)
      assert.strictEqual(unit(100, 'cm').equals(unit(2, 'm')), false)
      assert.strictEqual(unit(100, 'cm').equals(unit(1, 'kg')), false)
      assert.strictEqual(unit(100, 'ft lbf').equals(unit(1200, 'in lbf')), true)
      assert.strictEqual(unit(100, 'N').equals(unit(100, 'kg m / s ^ 2')), true)
      assert.strictEqual(unit(100, 'N').equals(unit(100, 'kg m / s')), false)
      assert.strictEqual(unit(100, 'Hz').equals(unit(100, 's ^ -1')), true)
    })

    it('should work with valueless units', () => {
      assert.strictEqual(unit('cm').equals(unit('cm')), true)
      assert.strictEqual(unit('cm').equals(unit('m')), false)
      assert.strictEqual(unit('cm/s').equals(unit('cm/s')), true)
    })

    it('should return false if one unit has a value and the other does not', () => {
      assert.strictEqual(unit('cm/s').equals(unit('1 cm/s')), false)
    })

    it('should convert parameter to a unit', () => {
      assert(unit(100, 'cm').equals('1 m'))
      assert(unit('3 kg / kg').equals(3))
    })
  })

  describe('compare', () => {
    it('should compare two units', () => {
      assert.strictEqual(unit('30 min').compare('1 hour'), -1)
      assert.strictEqual(unit('60 min').compare('1 hour'), 0)
      assert.strictEqual(unit('90 min').compare('1 hour'), 1)
    })

    it('should work with valueless units', () => {
      assert.strictEqual(unit('kg/hr').compare('kg/min'), -1)
      assert.strictEqual(unit('kg/hr').compare('kg/hr'), 0)
      assert.strictEqual(unit('kg/hr').compare('g/hr'), 1)
    })

    it('should throw if dimensions do not match', () => {
      assert.throws(() => unit(100, 'N').compare(unit(100, 'kg m / s')), /Cannot compare units.*dimensions do not match/)
      assert.throws(() => unit(100, 'cm').compare(unit(1, 'kg')), /Cannot compare units.*dimensions do not match/)
    })

    it('should convert parameter to a unit', () => {
      assert.strictEqual(unit(100, 'cm').compare('2 m'), -1)
      assert.strictEqual(unit('3 kg / kg').compare(2), 1)
    })

    it('should consistently compare NaNs', () => {
      assert.strictEqual(unit(100, 'cm').compare(unit(NaN, 'cm')), -1)
      assert.strictEqual(unit(NaN, 'cm').compare(unit(100, 'cm')), 1)
      assert.strictEqual(unit(NaN, 'cm').compare(unit(NaN, 'cm')), 1)
    })

    it('should sort Infinities and NaNs correctly', () => {
      const units = [
        unit(10, 'ft'),
        unit(Infinity, 'm'),
        unit(NaN, 'm'),
        unit(1, 'in'),
        unit(-Infinity, 'm'),
        unit(1, 'ft'),
        unit(Infinity, 'm'),
        unit(NaN, 'm'),
        unit(-Infinity, 'm'),
        unit(10, 'in')
      ]

      units.sort((a, b) => a.compare(b))
      assert.deepStrictEqual(
        units.map(u => u.toString()),
        [
          '-Infinity m', '-Infinity m',
          '1 in', '10 in',
          '1 ft', '10 ft',
          'Infinity m', 'Infinity m',
          'NaN m', 'NaN m'
        ]
      )
    })
  })

  describe('lessThan', () => {
    it('should test whether one unit is less than another', () => {
      assert.strictEqual(unit(100, 'cm').lessThan(unit(1, 'm')), false)
      assert.strictEqual(unit(100, 'cm').lessThan(unit(2, 'm')), true)
      // assert.strictEqual(unit(100, 'ft lbf').lessThan(unit(1200, 'in lbf')), false)  // Round-off error
      assert.strictEqual(unit(100, 'N').lessThan(unit(100, 'kg m / s ^ 2')), false)
      assert.strictEqual(unit(100, 'Hz').lessThan(unit(100, 's ^ -1')), false)
      assert.strictEqual(unit(101, 'Hz').lessThan(unit(100, 's ^ -1')), false)
      assert.strictEqual(unit(99, 'Hz').lessThan(unit(100, 's ^ -1')), true)
    })

    it('should work with valueless units', () => {
      assert.strictEqual(unit('cm').lessThan(unit('cm')), false)
      assert.strictEqual(unit('cm').lessThan(unit('m')), true)
      assert.strictEqual(unit('cm/s').lessThan(unit('cm/min')), false)
    })

    it('should throw if dimensions do not match', () => {
      assert.throws(() => unit(100, 'N').lessThan(unit(100, 'kg m / s')), /Cannot compare units.*dimensions do not match/)
      assert.throws(() => unit(100, 'cm').lessThan(unit(1, 'kg')), /Cannot compare units.*dimensions do not match/)
    })

    it('should throw if one unit has a value and the other does not', () => {
      assert.throws(() => unit('1m').lessThan('km'), /Cannot compare units.*one has a value and the other does not/)
    })

    it('should convert parameter to a unit', () => {
      assert(unit(100, 'cm').lessThan('2 m'))
      assert(unit('3 kg / kg').lessThan(4))
    })
  })

  describe('lessThanOrEqual', () => {
    it('should test whether one unit is less than another', () => {
      assert.strictEqual(unit(100, 'cm').lessThanOrEqual(unit(1, 'm')), true)
      assert.strictEqual(unit(100, 'cm').lessThanOrEqual(unit(2, 'm')), true)
      // assert.strictEqual(unit(100, 'ft lbf').lessThanOrEqual(unit(1200, 'in lbf')), false)  // Round-off error
      assert.strictEqual(unit(100, 'N').lessThanOrEqual(unit(100, 'kg m / s ^ 2')), true)
      assert.strictEqual(unit(100, 'Hz').lessThanOrEqual(unit(100, 's ^ -1')), true)
      assert.strictEqual(unit(101, 'Hz').lessThanOrEqual(unit(100, 's ^ -1')), false)
      assert.strictEqual(unit(99, 'Hz').lessThanOrEqual(unit(100, 's ^ -1')), true)
    })

    it('should work with valueless units', () => {
      assert.strictEqual(unit('cm').lessThanOrEqual(unit('cm')), true)
      assert.strictEqual(unit('cm').lessThanOrEqual(unit('m')), true)
      assert.strictEqual(unit('cm/s').lessThanOrEqual(unit('cm/min')), false)
    })

    it('should throw if dimensions do not match', () => {
      assert.throws(() => unit(100, 'N').lessThanOrEqual(unit(100, 'kg m / s')), /Cannot compare units.*dimensions do not match/)
      assert.throws(() => unit(100, 'cm').lessThanOrEqual(unit(1, 'kg')), /Cannot compare units.*dimensions do not match/)
    })

    it('should throw if one unit has a value and the other does not', () => {
      assert.throws(() => unit('1m').lessThanOrEqual('km'), /Cannot compare units.*one has a value and the other does not/)
    })

    it('should convert parameter to a unit', () => {
      assert(unit(100, 'cm').lessThanOrEqual('2 m'))
      assert(unit('3 kg / kg').lessThanOrEqual(4))
    })
  })

  describe('greaterThan', () => {
    it('should test whether one unit is greater than another', () => {
      assert.strictEqual(unit(100, 'cm').greaterThan(unit(1, 'm')), false)
      assert.strictEqual(unit(100, 'cm').greaterThan(unit(0.5, 'm')), true)
      // assert.strictEqual(unit(100, 'ft lbf').greaterThan(unit(1200, 'in lbf')), false)  // Round-off error
      assert.strictEqual(unit(100, 'N').greaterThan(unit(100, 'kg m / s ^ 2')), false)
      assert.strictEqual(unit(100, 'Hz').greaterThan(unit(100, 's ^ -1')), false)
      assert.strictEqual(unit(101, 'Hz').greaterThan(unit(100, 's ^ -1')), true)
      assert.strictEqual(unit(99, 'Hz').greaterThan(unit(100, 's ^ -1')), false)
    })

    it('should work with valueless units', () => {
      assert.strictEqual(unit('cm').greaterThan(unit('cm')), false)
      assert.strictEqual(unit('cm').greaterThan(unit('m')), false)
      assert.strictEqual(unit('cm/s').greaterThan(unit('cm/min')), true)
    })

    it('should throw if dimensions do not match', () => {
      assert.throws(() => unit(100, 'N').greaterThan(unit(100, 'kg m / s')), /Cannot compare units.*dimensions do not match/)
      assert.throws(() => unit(100, 'cm').greaterThan(unit(1, 'kg')), /Cannot compare units.*dimensions do not match/)
    })

    it('should throw if one unit has a value and the other does not', () => {
      assert.throws(() => unit('1m').greaterThan('km'), /Cannot compare units.*one has a value and the other does not/)
    })

    it('should convert parameter to a unit', () => {
      assert(unit(100, 'cm').greaterThan('0.5 m'))
      assert(unit('3 kg / kg').greaterThan(2))
    })
  })

  describe('greaterThanOrEqual', () => {
    it('should test whether one unit is greater than another', () => {
      assert.strictEqual(unit(100, 'cm').greaterThanOrEqual(unit(1, 'm')), true)
      assert.strictEqual(unit(100, 'cm').greaterThanOrEqual(unit(2, 'm')), false)
      // assert.strictEqual(unit(100, 'ft lbf').greaterThanOrEqual(unit(1200, 'in lbf')), false)  // Round-off error
      assert.strictEqual(unit(100, 'N').greaterThanOrEqual(unit(100, 'kg m / s ^ 2')), true)
      assert.strictEqual(unit(100, 'Hz').greaterThanOrEqual(unit(100, 's ^ -1')), true)
      assert.strictEqual(unit(101, 'Hz').greaterThanOrEqual(unit(100, 's ^ -1')), true)
      assert.strictEqual(unit(99, 'Hz').greaterThanOrEqual(unit(100, 's ^ -1')), false)
    })

    it('should work with valueless units', () => {
      assert.strictEqual(unit('cm').greaterThanOrEqual(unit('cm')), true)
      assert.strictEqual(unit('cm').greaterThanOrEqual(unit('m')), false)
      assert.strictEqual(unit('cm/s').greaterThanOrEqual(unit('cm/min')), true)
    })

    it('should throw if dimensions do not match', () => {
      assert.throws(() => unit(100, 'N').greaterThanOrEqual(unit(100, 'kg m / s')), /Cannot compare units.*dimensions do not match/)
      assert.throws(() => unit(100, 'cm').greaterThanOrEqual(unit(1, 'kg')), /Cannot compare units.*dimensions do not match/)
    })

    it('should throw if one unit has a value and the other does not', () => {
      assert.throws(() => unit('1m').greaterThanOrEqual('km'), /Cannot compare units.*one has a value and the other does not/)
    })

    it('should convert parameter to a unit', () => {
      assert(unit(100, 'cm').greaterThanOrEqual('0.5 m'))
      assert(unit('3 kg / kg').greaterThanOrEqual(2))
    })
  })

  describe('clone', () => {
    it('should clone a unit', () => {
      const u1 = unit(100, 'cm')
      const u2 = u1.clone()
      assert.notStrictEqual(u1, u2)
      assert.deepStrictEqual(u1, u2)

      const u3 = unit(8.314, 'km/hr')
      const u4 = u3.clone()
      assert.notStrictEqual(u3, u4)
      assert.deepStrictEqual(u3, u4)

      const u7 = unit(8.314, 'kg m^2 / s^2 K mol')
      const u8 = u7.clone()
      assert.notStrictEqual(u7, u8)
      assert.deepStrictEqual(u7, u8)

      const u9 = unit(8.314, 'kg m^2 / s^2 K mol').to()
      const u10 = u9.clone()
      assert.notStrictEqual(u9, u10)
      assert.deepStrictEqual(u9, u10)
    })

    it('should freeze the returned unit', () => {
      assert(Object.isFrozen(unit(100, 'cm').clone()))
    })
  })

  describe('to', () => {
    it('should convert a unit using a target unit string', () => {
      const u1 = unit(5000, 'in')
      assert.strictEqual(u1.value, 5000)
      assert.strictEqual(u1.units[0].unit.name, 'in')
      assert.strictEqual(u1.units[0].prefix, '')

      const u2 = u1.to('cm')
      assert.notStrictEqual(u1, u2) // u2 must be a clone
      assert.strictEqual(u2.value, 12700)
      assert.strictEqual(u2.units[0].unit.name, 'm')
      assert.strictEqual(u2.units[0].prefix, 'c')

      const u3 = unit(299792.458, 'km/s')
      assert.strictEqual(u3.value, 299792.458)
      assert.strictEqual(u3.units[0].unit.name, 'm')
      assert.strictEqual(u3.units[1].unit.name, 's')
      assert.strictEqual(u3.units[0].prefix, 'k')

      const u4 = u3.to('m/s')
      assert.notStrictEqual(u3, u4) // u4 must be a clone
      assert.strictEqual(u4.value, 299792458)
      assert.strictEqual(u4.units[0].unit.name, 'm')
      assert.strictEqual(u4.units[1].unit.name, 's')
      assert.strictEqual(u4.units[0].prefix, '')
    })

    it('should convert a unit using a target unit', () => {
      const u1 = unit(5000, 'in')
      assert.strictEqual(u1.value, 5000)
      assert.strictEqual(u1.units[0].unit.name, 'in')
      assert.strictEqual(u1.units[0].prefix, '')

      const u2 = u1.to(unit('cm'))
      assert.notStrictEqual(u1, u2) // u2 must be a clone
      assert.strictEqual(u2.value, 12700)
      assert.strictEqual(u2.units[0].unit.name, 'm')
      assert.strictEqual(u2.units[0].prefix, 'c')

      const u3 = unit(299792.458, 'km/s')
      assert.strictEqual(u3.value, 299792.458)
      assert.strictEqual(u3.units[0].unit.name, 'm')
      assert.strictEqual(u3.units[1].unit.name, 's')
      assert.strictEqual(u3.units[0].prefix, 'k')

      const u4 = u3.to(unit('m/s'))
      assert.notStrictEqual(u3, u4) // u4 must be a clone
      assert.strictEqual(u4.value, 299792458)
      assert.strictEqual(u4.units[0].unit.name, 'm')
      assert.strictEqual(u4.units[1].unit.name, 's')
      assert.strictEqual(u4.units[0].prefix, '')
    })

    it('should convert a valueless unit', () => {
      const u1 = unit(null, 'm')
      assert.strictEqual(u1.value, null)
      assert.strictEqual(u1.units[0].unit.name, 'm')
      assert.strictEqual(u1.units[0].prefix, '')

      const u2 = u1.to(unit(null, 'cm'))
      assert.notStrictEqual(u1, u2) // u2 must be a clone
      assert.strictEqual(u2.value, 100) // u2 must have a value
      assert.strictEqual(u2.units[0].unit.name, 'm')
      assert.strictEqual(u2.units[0].prefix, 'c')

      const u3 = unit(null, 'm/s')
      assert.strictEqual(u3.value, null)
      assert.strictEqual(u3.units[0].unit.name, 'm')
      assert.strictEqual(u3.units[1].unit.name, 's')
      assert.strictEqual(u3.units[0].prefix, '')

      const u4 = u3.to(unit(null, 'cm/s'))
      assert.notStrictEqual(u3, u4) // u4 must be a clone
      assert.strictEqual(u4.value, 100) // u4 must have a value
      assert.strictEqual(u4.units[0].unit.name, 'm')
      assert.strictEqual(u4.units[1].unit.name, 's')
      assert.strictEqual(u4.units[0].prefix, 'c')

      const u5 = unit(null, 'km').to('cm')
      assert.strictEqual(u5.value, 100000)
      assert.strictEqual(u5.units[0].unit.name, 'm')
      assert.strictEqual(u5.units[0].prefix, 'c')
    })

    it('should convert a binary prefixes (1)', () => {
      const u1 = unit(1, 'Kib')
      assert.strictEqual(u1.value, 1)
      assert.strictEqual(u1.units[0].unit.name, 'b')
      assert.strictEqual(u1.units[0].prefix, 'Ki')

      const u2 = u1.to(unit(null, 'b'))
      assert.notStrictEqual(u1, u2) // u2 must be a clone
      assert.strictEqual(u2.value, 1024) // u2 must have a value
      assert.strictEqual(u2.units[0].unit.name, 'b')
      assert.strictEqual(u2.units[0].prefix, '')

      const u3 = unit(1, 'Kib/s')
      assert.strictEqual(u3.value, 1)
      assert.strictEqual(u3.units[0].unit.name, 'b')
      assert.strictEqual(u3.units[1].unit.name, 's')
      assert.strictEqual(u3.units[0].prefix, 'Ki')

      const u4 = u3.to(unit(null, 'b/s'))
      assert.notStrictEqual(u3, u4) // u4 must be a clone
      assert.strictEqual(u4.value, 1024) // u4 must have a value
      assert.strictEqual(u4.units[0].unit.name, 'b')
      assert.strictEqual(u4.units[1].unit.name, 's')
      assert.strictEqual(u4.units[0].prefix, '')
    })

    it('should convert a binary prefixes (2)', () => {
      const u1 = unit(1, 'kb')
      assert.strictEqual(u1.value, 1)
      assert.strictEqual(u1.units[0].unit.name, 'b')
      assert.strictEqual(u1.units[0].prefix, 'k')

      const u2 = u1.to(unit(null, 'b'))
      assert.notStrictEqual(u1, u2) // u2 must be a clone
      assert.strictEqual(u2.value, 1000) // u2 must have a value
      assert.strictEqual(u2.units[0].unit.name, 'b')
      assert.strictEqual(u2.units[0].prefix, '')
    })

    it('the alternate api syntax should also work', () => {
      assert.strictEqual(unit.to('lbm', 'kg').format(), '0.45359237 kg')
      assert.strictEqual(unit.to(unit('10 lbm'), unit('kg')).format(), '4.5359237 kg')
    })

    it('should throw an error when converting to an incompatible unit', () => {
      const u1 = unit(5000, 'cm')
      assert.throws(() => { u1.to('kg') }, /dimensions do not match/)
      const u2 = unit(5000, 'N s')
      assert.throws(() => { u2.to('kg^5 / s') }, /dimensions do not match/)
    })

    it('should throw an error when converting to a unit having a value', () => {
      const u1 = unit(5000, 'cm')
      assert.throws(() => { u1.to(unit(4, 'm')) }, /unit must be valueless/)
    })

    it('should throw an error when converting to an unsupported type of argument', () => {
      const u1 = unit(5000, 'cm')
      assert.throws(() => { u1.to(new Date()) }, /Parameter must be a Unit or a string./)
    })
  })

  describe('getUnits', () => {
    it('should return the units only of a unit', () => {
      assert.deepStrictEqual(unit('42 kg / m s^2').getUnits(), unit('kg / m s^2'))
    })
  })

  describe('toString', () => {
    it('should convert to string when no extra simplification is requested', () => {
      assert.strictEqual(unit(5000, 'cm').to().toString(), '5000 cm')
      assert.strictEqual(unit(5, 'kg').to().toString(), '5 kg')
      assert.strictEqual(unit(2 / 3, 'm').to().toString(), '0.666666666666667 m')
      assert.strictEqual(unit(5, 'N').to().toString(), '5 N')
      assert.strictEqual(unit(5, 'kg^1.0e0 m^1.0e0 s^-2.0e0').to().toString(), '5 kg m / s^2')
      assert.strictEqual(unit(5, 's^-2').to().toString(), '5 s^-2')
      assert.strictEqual(unit(5, 'm / s ^ 2').to().toString(), '5 m / s^2')
      assert.strictEqual(unit(null, 'kg m^2 / s^2 mol').to().toString(), 'kg m^2 / s^2 mol')
      assert.strictEqual(unit(10, 'hertz').to().toString(), '10 hertz')
      assert.strictEqual(unit('3.14 rad').to().toString(), '3.14 rad')
      assert.strictEqual(unit('J / mol K').to().toString(), 'J / mol K')
      assert.strictEqual(unit(2).to().toString(), '2')
      assert.strictEqual(unit().to().toString(), '')
    })

    it('should convert to string properly', () => {
      assert.strictEqual(unit(5, 'kg').toString(), '5 kg')
      assert.strictEqual(unit(2 / 3, 'm').toString(), '0.666666666666667 m')
      assert.strictEqual(unit(5, 'N').toString(), '5 N')
      assert.strictEqual(unit(5, 'kg^1.0e0 m^1.0e0 s^-2.0e0').to().toString(), '5 kg m / s^2')
      assert.strictEqual(unit(5, 's^-2').toString(), '5 s^-2')
      assert.strictEqual(unit(5, 'm / s ^ 2').toString(), '5 m / s^2')
      assert.strictEqual(unit(null, 'kg m^2 / s^2 mol').toString(), 'kg m^2 / s^2 mol')
      assert.strictEqual(unit(10, 'hertz').toString(), '10 hertz')
    })

    it('should render with the best prefix', () => {
      assert.strictEqual(unit(0.000001, 'm').format(8), '1 um')
      assert.strictEqual(unit(0.00001, 'm').format(8), '10 um')
      assert.strictEqual(unit(0.0001, 'm').format(8), '0.1 mm')
      assert.strictEqual(unit(0.0005, 'm').format(8), '0.5 mm')
      assert.strictEqual(unit(0.0006, 'm').toString(), '0.6 mm')
      assert.strictEqual(unit(0.001, 'm').toString(), '0.1 cm')
      assert.strictEqual(unit(0.01, 'm').toString(), '1 cm')
      assert.strictEqual(unit(100000, 'm').toString(), '100 km')
      assert.strictEqual(unit(300000, 'm').toString(), '300 km')
      assert.strictEqual(unit(500000, 'm').toString(), '500 km')
      assert.strictEqual(unit(600000, 'm').toString(), '600 km')
      assert.strictEqual(unit(1000000, 'm').toString(), '1000 km')
      assert.strictEqual(unit(10000000, 'm').toString(), '10000 km')
      assert.strictEqual(unit(2000, 'ohm').toString(), '2 kohm')

      assert.strictEqual(unit(-0.000001, 'm').format(8), '-1 um')
      assert.strictEqual(unit(-0.00001, 'm').format(8), '-10 um')
      assert.strictEqual(unit(-0.0001, 'm').format(8), '-0.1 mm')
      assert.strictEqual(unit(-0.0005, 'm').format(8), '-0.5 mm')
      assert.strictEqual(unit(-0.0006, 'm').toString(), '-0.6 mm')
      assert.strictEqual(unit(-0.001, 'm').toString(), '-0.1 cm')
      assert.strictEqual(unit(-0.01, 'm').toString(), '-1 cm')
      assert.strictEqual(unit(-100000, 'm').toString(), '-100 km')
      assert.strictEqual(unit(-300000, 'm').toString(), '-300 km')
      assert.strictEqual(unit(-500000, 'm').toString(), '-500 km')
      assert.strictEqual(unit(-600000, 'm').toString(), '-600 km')
      assert.strictEqual(unit(-1000000, 'm').toString(), '-1000 km')
      assert.strictEqual(unit(-10000000, 'm').toString(), '-10000 km')
      assert.strictEqual(unit(-2000, 'ohm').toString(), '-2 kohm')
    })

    it('should keep the original prefix when in range', () => {
      assert.strictEqual(unit(0.0999, 'm').toString(), '9.99 cm')
      assert.strictEqual(unit(0.1, 'm').toString(), '0.1 m')
      assert.strictEqual(unit(0.5, 'm').toString(), '0.5 m')
      assert.strictEqual(unit(0.6, 'm').toString(), '0.6 m')
      assert.strictEqual(unit(1, 'm').toString(), '1 m')
      assert.strictEqual(unit(10, 'm').toString(), '10 m')
      assert.strictEqual(unit(100, 'm').toString(), '100 m')
      assert.strictEqual(unit(300, 'm').toString(), '300 m')
      assert.strictEqual(unit(500, 'm').toString(), '500 m')
      assert.strictEqual(unit(600, 'm').toString(), '600 m')
      assert.strictEqual(unit(1000, 'm').toString(), '1000 m')
      assert.strictEqual(unit(1001, 'm').toString(), '1.001 km')
    })

    it('should render best prefix for a single unit raised to integral power', () => {
      assert.strictEqual(unit(3.2e7, 'm^2').toString(), '32 km^2')
      assert.strictEqual(unit(3.2e-7, 'm^2').toString(), '0.32 mm^2')
      assert.strictEqual(unit(15000, 'm^-1').toString(), '150 cm^-1')
      assert.strictEqual(unit(3e-9, 'm^-2').toString(), '0.003 km^-2')
      assert.strictEqual(unit(3e-9, 'm^-1.5').toString(), '3e-9 m^-1.5')
      assert.strictEqual(unit(2, 'kg^0').toString(), '2')
    })

    it('should not change the prefix unless there is a `commonPrefixes` defined for the unit', () => {
      // lumen has prefixes, but no commonPrefixes, so its prefixes should not change
      assert.strictEqual(unit('4 microlumen').format(), '4 microlumen')
      assert.strictEqual(unit('4e-6 lumen').format(), '0.000004 lumen')

      // newton has prefixes and commonPrefixes so its prefix should change
      assert.strictEqual(unit('4 micronewton').format(), '4 micronewton')
      assert.strictEqual(unit('4e-6 newton').format(), '4 micronewton')
    })

    it('should use the prefixesToChooseFrom option', () => {
      assert.strictEqual(unit('4 microlumen').format({ prefixesToChooseFrom: 'all' }), '4 microlumen')
      assert.strictEqual(unit('4e-6 lumen').format({ prefixesToChooseFrom: 'all' }), '4 microlumen')

      assert.strictEqual(unit('4e-6 micronewton').format({ prefixesToChooseFrom: 'all' }), '4 piconewton')
      assert.strictEqual(unit('4e-2 newton').format({ prefixesToChooseFrom: 'all' }), '0.4 decinewton')
      assert.strictEqual(unit('4e+9 newton').format({ prefixesToChooseFrom: 'all' }), '4 giganewton')

      assert.strictEqual(unit('4e-6 micronewton').format({ prefixesToChooseFrom: 'common' }), '0.000004 micronewton')
      assert.strictEqual(unit('4e-2 newton').format({ prefixesToChooseFrom: 'common' }), '40 millinewton')
      assert.strictEqual(unit('4e+9 newton').format({ prefixesToChooseFrom: 'common' }), '4000 meganewton')
    })

    it('should avoid division by zero by not choosing a prefix for very small values', () => {
      assert.strictEqual(unit('1e-40 m').format(), '1e-31 nm')
      assert.strictEqual(unit('1e-60 m').format(), '1e-60 m')
      assert.strictEqual(unit('0 m').format(), '0 m')
      assert.strictEqual(unit('-1e-40 m').format(), '-1e-31 nm')
      assert.strictEqual(unit('-1e-60 m').format(), '-1e-60 m')
    })

    it('should not render best prefix if "fixed" by the `to` method', () => {
      const u = unit(5e-3, 'm')
      assert.strictEqual(u.toString(), '0.5 cm')
      const v = u.to()
      assert.strictEqual(v.toString(), '0.005 m')
    })

    it('should format a unit without value', () => {
      assert.strictEqual(unit(null, 'cm').toString(), 'cm')
      assert.strictEqual(unit(null, 'm').toString(), 'm')
      assert.strictEqual(unit(null, 'kg m/s').toString(), 'kg m / s')
    })

    it('should format a unit with fixed prefix and without value', () => {
      assert.strictEqual(unit(null, 'km').to('cm').toString(), '100000 cm')
      assert.strictEqual(unit(null, 'inch').to('cm').toString(), '2.54 cm')
      assert.strictEqual(unit(null, 'N/m^2').to('lbf/inch^2').toString({ precision: 10 }), '0.0001450377377 lbf / inch^2')
    })

    it('should ignore properties in Object.prototype when finding the best prefix', () => {
      Object.prototype.foo = 'bar' // eslint-disable-line no-extend-native

      assert.strictEqual(unit(5e5, 'cm').format(), '5 km')

      delete Object.prototype.foo
    })
  })

  describe('setValue', () => {
    it('should set a unit\'s value', () => {
      assert.strictEqual(unit('10 lumen').setValue(20).format(), '20 lumen')
      assert.strictEqual(unit('lumen').setValue(20).format(), '20 lumen')
      assert.strictEqual(unit('10 lumen').setValue(null).format(), 'lumen')
      assert.strictEqual(unit('10 lumen').setValue().format(), 'lumen')
    })
  })

  describe('getValue', () => {
    it('should get a unit\'s value', () => {
      assert.strictEqual(unit('20 kg').getValue(), 20)
      assert.strictEqual(unit('3 g').getValue(), 3)
      assert.strictEqual(unit('40 mi/hr').getValue(), 40)
      assert.strictEqual(unit('km/hr').getValue(), null)
    })
  })

  describe('getNormalizedValue', () => {
    it('should get a unit\'s normalized value', () => {
      assert.strictEqual(unit('20 kg').getNormalizedValue(), 20)
      assert.strictEqual(unit('3 g').getNormalizedValue(), 0.003)
      assert.strictEqual(unit('40 mi/hr').getNormalizedValue(), 17.8816)
      assert.strictEqual(unit('km/hr').getNormalizedValue(), null)
    })
  })

  describe('setNormalizedValue', () => {
    it('should set a unit\'s normalized value', () => {
      assert.strictEqual(unit('20 kg').setNormalizedValue(10).format(), '10 kg')
      assert.strictEqual(unit('3 g').setNormalizedValue(5).format(), '5 kg')
      assert.strictEqual(unit('40 mi/hr').setNormalizedValue(10).format(), '22.369362920544 mi / hr')
      assert.strictEqual(unit('km/hr').setNormalizedValue(1).format(), '3.6 km / hr')
    })
  })

  describe('simplify', () => {
    it('should not simplify units fixed by the to() method', () => {
      const unit1 = unit(10, 'kg m/s^2').to()
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'm')
      assert.strictEqual(unit1.units[2].unit.name, 's')
      assert.strictEqual(unit1.toString(), '10 kg m / s^2')
    })

    it('should simplify units without values', () => {
      assert.strictEqual(unit('kg m/s^2').simplify().format(), 'N')
    })

    it('should simplify units when they cancel out', () => {
      const unit1 = unit(2, 'Hz')
      const unit2 = unit(2, 's')
      const unit3 = unit1.mul(unit2)
      assert.strictEqual(unit3.toString(), '4')

      const nounit = unit('40m').mul('40N').div('40J')
      assert.strictEqual(nounit.toString(), '40')

      const nounit2 = unit('2 hours').div('1 hour')
      assert.strictEqual(nounit2.toString(), '2')
    })

    it('should simplify units according to chosen unit system', () => {
      // Simplify explicitly
      let unit1 = unit.config({ system: 'us' })('10 N')
      assert.strictEqual(unit1.simplify().toString(), '2.2480894309971 lbf')

      let unit2 = unit.config({ system: 'cgs' })('10 N')
      assert.strictEqual(unit2.simplify().toString(), '1000 kdyn')

      // Reduce threshold to 0
      let newUnit = unit.config({ simplifyThreshold: 0 })

      let unit3 = newUnit.config({ system: 'us' })('10 N')
      assert.strictEqual(unit3.toString(), '2.2480894309971 lbf')

      let unit4 = newUnit.config({ system: 'cgs' })('10 N')
      assert.strictEqual(unit4.toString(), '1000 kdyn')
    })

    it('should correctly simplify units when unit system is "auto"', () => {
      const unit1 = unit(5, 'lbf min / s')
      assert.strictEqual(unit1.simplify().toString(), '300 lbf')
      assert.strictEqual(unit('150 lbf').div('10 in^2').toString(), '15 psi')
      assert.strictEqual(unit('400 N').div('10 cm^2').toString(), '400 kPa')
    })

    it.skip('should infer the unit system when using non-preferred units which are members of that system', () => {
      // mile and kip are not preferred, but are members of the 'us' system, therefore result should simplify to 'BTU'
      let unit1 = unit('10 mile').mul('10 kip')
      assert.strictEqual(unit1.toString(), '0.678515620705073 MMBTU')
    })

    it.skip('should try to use preexisting units in the simplified expression', () => {
      let unit1 = unit('10 ft hour / minute')
      assert.strictEqual(unit1.toString(), '600 ft')

      unit1 = unit('10 mile hour / minute')
      assert.strictEqual(unit1.toString(), '600 mile')

      unit1 = unit('10 mi hour / minute')
      assert.strictEqual(unit1.toString(), '600 mi')

      unit1 = unit('10 m hour / minute')
      assert.strictEqual(unit1.toString(), '600 m')

      unit1 = unit('10 meter hour / minute')
      assert.strictEqual(unit1.toString(), '600 meter')
    })

    it('should silently fail if the unit system does not specify a unit needed for a base quantity', () => {
      assert.strictEqual(unit('4 W / F').simplify().format(), '4 kg^2 m^4 / s^7 A^2')

      let newUnit = unit.config({
        system: 'noAmps',
        definitions: {
          unitSystems: {
            noAmps: {
              MASS: 'kg',
              LENGTH: 'm',
              TIME: 's'
            }
          }
        }
      })
      assert.strictEqual(newUnit('4 W / F').simplify().format(), '4 W / F')
    })

    it('should not simplify unless complexity is reduced by the threshold', () => {
      assert.strictEqual(unit('10 N m').toString(), '10 N m')
      assert.strictEqual(unit('10 J / m').toString(), '10 N')
      assert.strictEqual(unit('10 m^3 Pa').toString(), '10 J')
      assert.strictEqual(unit('10 s^-1').toString(), '10 Hz')
      assert.strictEqual(unit('10 C/s').toString(), '10 A')

      let newUnit = unit.config({ simplifyThreshold: 10 })
      assert.strictEqual(newUnit('10 N m').toString(), '10 N m')
      assert.strictEqual(newUnit('10 J / m').toString(), '10 J / m')
      assert.strictEqual(newUnit('10 m^3 Pa').toString(), '10 m^3 Pa')

      newUnit = newUnit.config({ simplifyThreshold: 0 })
      assert.strictEqual(newUnit('10 N m').toString(), '10 J')
      assert.strictEqual(newUnit('10 J / m').toString(), '10 N')
      assert.strictEqual(newUnit('10 m^3 Pa').toString(), '10 J')
    })
  })

  describe('precision', () => {
    it('should format units with given precision', () => {
      assert.strictEqual(unit.config({ precision: 3 })(2 / 3, 'm').toString(), '0.667 m')
      assert.strictEqual(unit.config({ precision: 1 })(2 / 3, 'm').toString(), '0.7 m')
      assert.strictEqual(unit.config({ precision: 10 })(2 / 3, 'm').toString(), '0.6666666667 m')
    })
  })

  describe('format', () => {
    it('should accept formatting options as argument to the function', () => {
      assert.strictEqual(unit('1 lb').to('kg').format({ prefix: 'always', prefixMin: 1, precision: 4 }), '453.6 g')
    })

    it('should render parentheses if desired', () => {
      assert.strictEqual(unit('kg m / s^2').to().format({ parentheses: true }), '(kg m) / s^2')
      assert.strictEqual(unit('8.314 J / mol K').to().format({ parentheses: true }), '8.314 J / (mol K)')
    })

    it('should only simplify units with values', () => {
      let unit1 = unit(null, 'kg m mol / s^2 mol')
      assert.strictEqual(unit1.format(), 'kg m / s^2')
      unit1 = unit1.mul(1)
      assert.strictEqual(unit1.format(), '1 N')
    })

    it('should respect the `simplify` option', () => {
      assert.strictEqual(unit('1 kg m / s^2').format(), '1 N')
      assert.strictEqual(unit('1 kg m / s^2').format({ simplify: 'never' }), '1 kg m / s^2')
      assert.strictEqual(unit('1 N m').format(), '1 N m')
      assert.strictEqual(unit('1 N m').format({ simplify: 'always' }), '1 J')
    })
  })

  describe('valueOf', () => {
    it('should output a raw, unsimplified string representation of this unit', () => {
      assert.strictEqual(unit('8.314 J / mol K').valueOf(), '8.314 J / mol K')
      assert.strictEqual(unit('10 h / s').valueOf(), '10 h / s')
      assert.strictEqual(unit('10 h / s').format(), '36000')
    })
  })

  describe('parse', () => {
    it('should parse units correctly', () => {
      let unit1

      unit1 = unit('5kg')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('5 kg')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit(' 5 kg ')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('5e-3kg')
      assert.strictEqual(unit1.value, 0.005)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('5e+3kg')
      assert.strictEqual(unit1.value, 5000)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('5e3kg')
      assert.strictEqual(unit1.value, 5000)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('-5kg')
      assert.strictEqual(unit1.value, -5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('+5kg')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('.5kg')
      assert.strictEqual(unit1.value, 0.5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('-5mg')
      approx.equal(unit1.value, -5)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'm')

      unit1 = unit('5.2mg')
      approx.equal(unit1.value, 5.2)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[0].prefix, 'm')

      unit1 = unit('300 kg/minute')
      approx.equal(unit1.value, 300)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'minute')
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('981 cm/s^2')
      approx.equal(unit1.value, 981)
      assert.strictEqual(unit1.units[0].unit.name, 'm')
      assert.strictEqual(unit1.units[1].unit.name, 's')
      assert.strictEqual(unit1.units[1].power, -2)
      assert.strictEqual(unit1.units[0].prefix, 'c')

      unit1 = unit('981 cm*s^-2')
      approx.equal(unit1.value, 981)
      assert.strictEqual(unit1.units[0].unit.name, 'm')
      assert.strictEqual(unit1.units[1].unit.name, 's')
      assert.strictEqual(unit1.units[1].power, -2)
      assert.strictEqual(unit1.units[0].prefix, 'c')

      unit1 = unit('8.314 kg m^2 / s^2 K mol')
      approx.equal(unit1.value, 8.314)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'm')
      assert.strictEqual(unit1.units[2].unit.name, 's')
      assert.strictEqual(unit1.units[3].unit.name, 'K')
      assert.strictEqual(unit1.units[4].unit.name, 'mol')
      assert.strictEqual(unit1.units[0].power, 1)
      assert.strictEqual(unit1.units[1].power, 2)
      assert.strictEqual(unit1.units[2].power, -2)
      assert.strictEqual(unit1.units[3].power, -1)
      assert.strictEqual(unit1.units[4].power, -1)
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('8.314 kg m^2 / s^2 K mol')
      approx.equal(unit1.value, 8.314)
      assert.strictEqual(unit1.units[0].unit.name, 'g')
      assert.strictEqual(unit1.units[1].unit.name, 'm')
      assert.strictEqual(unit1.units[2].unit.name, 's')
      assert.strictEqual(unit1.units[3].unit.name, 'K')
      assert.strictEqual(unit1.units[4].unit.name, 'mol')
      assert.strictEqual(unit1.units[0].power, 1)
      assert.strictEqual(unit1.units[1].power, 2)
      assert.strictEqual(unit1.units[2].power, -2)
      assert.strictEqual(unit1.units[3].power, -1)
      assert.strictEqual(unit1.units[4].power, -1)
      assert.strictEqual(unit1.units[0].prefix, 'k')

      unit1 = unit('5exabytes')
      approx.equal(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'bytes')

      unit1 = unit('1 / s')
      approx.equal(unit1.value, 1)
      assert.strictEqual(unit1.units[0].unit.name, 's')
      assert.strictEqual(unit1.units[0].power, -1)

      unit1 = unit('1/s')
      approx.equal(unit1.value, 1)
      assert.strictEqual(unit1.units[0].unit.name, 's')
      assert.strictEqual(unit1.units[0].power, -1)

      unit1 = unit('1 * s')
      approx.equal(unit1.value, 1)
      assert.strictEqual(unit1.units[0].unit.name, 's')
      assert.strictEqual(unit1.units[0].power, 1)
    })

    it('should throw error when parsing expressions with invalid characters', () => {
      assert.throws(() => { unit('8.314 J / (mol * K)') }, /Unexpected "\("/)
      assert.throws(() => { unit('8.314 J / mol / K') }, /Unexpected additional "\/"/)
    })

    it('should parse units with correct precedence', () => {
      const unit1 = unit('1  m^3 / kg s^2') // implicit multiplication

      approx.equal(unit1.value, 1)
      assert.strictEqual(unit1.units[0].unit.name, 'm')
      assert.strictEqual(unit1.units[1].unit.name, 'g')
      assert.strictEqual(unit1.units[2].unit.name, 's')
      assert.strictEqual(unit1.units[0].power, 3)
      assert.strictEqual(unit1.units[1].power, -1)
      assert.strictEqual(unit1.units[2].power, -2)
      assert.strictEqual(unit1.units[0].prefix, '')
    })

    it('should throw an exception when parsing an invalid unit', () => {
      assert.throws(() => { unit('.meter') }, /Unexpected "\."/)
      assert.throws(() => { unit('5e') }, /Unit "e" not found/)
      assert.throws(() => { unit('5e.') }, /Unit "e" not found/)
      assert.throws(() => { unit('5e1.3') }, /Unexpected "\."/)
      assert.throws(() => { unit('meter.') }, /Unexpected "\."/)
      assert.throws(() => { unit('meter/') }, /Trailing characters/)
      assert.throws(() => { unit('/meter') }, /Unexpected "\/"/)
      assert.throws(() => { unit('1 */ s') }, /Unexpected "\/"/)
      assert.throws(() => { unit('45 kg 34 m') }, /Unexpected "3"/)
      assert.throws(() => unit('10 m^'), /must be followed by a floating/)
      assert.throws(() => unit('10 m+'), /Unexpected "\+"/)
    })

    it('should parse empty strings and only numbers', () => {
      assert.strictEqual(unit(123).value, 123)
      assert.strictEqual(unit(123).units.length, 0)
      assert.strictEqual(unit('').value, null)
      assert.strictEqual(unit('').units.length, 0)
      assert.strictEqual(unit().value, null)
      assert.strictEqual(unit().units.length, 0)
    })

    it('should parse NaN and +/- Infinity', () => {
      assert.strictEqual(Number.isNaN(unit('NaN').value), true)
      assert.strictEqual(Number.isNaN(unit('NaN kg').value), true)
      assert.strictEqual(unit('NaN kg').units[0].unit.name, 'g')
      assert.strictEqual(Number.isNaN(unit('NaNkg').value), true)
      assert.strictEqual(unit('NaNkg').units[0].unit.name, 'g')

      assert.strictEqual(unit('Infinity').value, Infinity)
      assert.strictEqual(unit('Infinity kg').value, Infinity)
      assert.strictEqual(unit('Infinity kg').units[0].unit.name, 'g')

      assert.strictEqual(unit('-Infinity').value, -Infinity)
      assert.strictEqual(unit('-Infinity kg').value, -Infinity)
      assert.strictEqual(unit('-Infinity kg').units[0].unit.name, 'g')
    })

    it('should throw if parser() receives other than a string', () => {
      assert.throws(() => unit._unitStore.parser(42), /TypeError: Invalid argument in parse/)
    })
  })

  describe('prefixes', () => {
    it('should accept both long and short prefixes for ohm', () => {
      assert.strictEqual(unit('5 ohm').toString(), '5 ohm')
      assert.strictEqual(unit('5 milliohm').toString(), '5 milliohm')
      assert.strictEqual(unit('5 mohm').toString(), '5 mohm')
    })

    it('should accept both long and short prefixes for bar', () => {
      assert.strictEqual(unit('5 bar').toString(), '5 bar')
      assert.strictEqual(unit('5 millibar').toString(), '5 millibar')
      assert.strictEqual(unit('5 mbar').toString(), '5 mbar')
    })
  })

  describe('isCompound', () => {
    it('should return the correct value', () => {
      assert.strictEqual(unit('34').isCompound(), false)
      assert.strictEqual(unit('34 kg').isCompound(), false)
      assert.strictEqual(unit('34 kg/s').isCompound(), true)
      assert.strictEqual(unit('34 kg^2').isCompound(), true)
      assert.strictEqual(unit('34 N').isCompound(), false)
      assert.strictEqual(unit('34 kg m / s^2').isCompound(), true)
    })
  })

  describe('add', () => {
    it('should add two units', () => {
      assert.deepStrictEqual(unit(300, 'm').add(unit(3, 'km')), unit(3300, 'm'))
      approx.deepEqual(unit('2m').add(unit('3ft')), unit('2.9144 m'))
      approx.deepEqual(unit('2').add(unit('3')), unit('5'))
    })

    it('should convert parameter to unit', () => {
      assert.deepStrictEqual(unit('1 hour').add('30 minute'), unit(1.5, 'hour'))
      assert.deepStrictEqual(unit(100, 'cm / m').add(10), unit(1100, 'cm / m'))
    })

    it('should return a frozen unit', () => {
      assert(Object.isFrozen(unit(300, 'm').add(unit(3, 'km'))))
    })

    it('the alternate api syntax should also work', () => {
      assert.deepStrictEqual(unit.add(unit(300, 'm'), unit(3, 'km')), unit(3300, 'm'))
      assert.deepStrictEqual(unit.add('300 m', '3 km'), unit(3300, 'm'))
    })

    it('should throw if one or both units do not have values', () => {
      assert.throws(() => unit('10 m').add('ft'), /Cannot add.*both units must have values/)
      assert.throws(() => unit('m').add('10 ft'), /Cannot add.*both units must have values/)
    })

    it('should throw if units are not consistent', () => {
      assert.throws(() => unit('10 kg').add('8 day'), /Cannot add.*dimensions do not match/)
      assert.throws(() => unit('10 kg').add('8 kg^0.99'), /Cannot add.*dimensions do not match/)
      assert.throws(() => unit('10 kg').add('8'), /Cannot add.*dimensions do not match/)
    })
  })

  describe('sub', () => {
    it('should subtract two units', () => {
      assert.deepStrictEqual(unit(300, 'm').sub(unit(3, 'km')), unit(-2700, 'm'))
      approx.deepEqual(unit('2m').sub(unit('3ft')), unit('1.0856 m'))
    })

    it('should convert parameter to unit', () => {
      assert.deepStrictEqual(unit('1 hour').sub('30 minute'), unit(0.5, 'hour'))
      assert.deepStrictEqual(unit(100, 'm / cm').sub(10), unit('99.9 m / cm'))
    })

    it('should return a frozen unit', () => {
      assert(Object.isFrozen(unit(300, 'm').sub(unit(3, 'km'))))
    })

    it('the alternate api syntax should also work', () => {
      assert.deepStrictEqual(unit.sub(unit(300, 'm'), unit(3, 'km')), unit(-2700, 'm'))
      assert.deepStrictEqual(unit.sub('300 m', '3 km'), unit(-2700, 'm'))
    })

    it('should throw if one or both units do not have values', () => {
      assert.throws(() => unit('10 m').sub('ft'), /Cannot subtract.*both units must have values/)
      assert.throws(() => unit('m').sub('10 ft'), /Cannot subtract.*both units must have values/)
    })

    it('should throw if units are not consistent', () => {
      assert.throws(() => unit('10 kg').sub('8 day'), /Cannot subtract.*dimensions do not match/)
      assert.throws(() => unit('10 kg').sub('8 kg^0.99'), /Cannot subtract.*dimensions do not match/)
      assert.throws(() => unit('10 kg').sub('8'), /Cannot subtract.*dimensions do not match/)
    })
  })

  describe('mul', () => {
    it('should multiply unit\'s values and combine their units', () => {
      assert.deepStrictEqual(unit('2 kg').mul(unit('3 m')), unit('6 kg m'))
      assert.deepStrictEqual(unit('2 m').mul(unit('4 m')), unit('8 m^2'))
      assert.deepStrictEqual(unit('2 ft').mul(unit('4 ft')), unit('8 ft^2'))
      assert.deepStrictEqual(unit('65 mi/h').mul(unit('2 h')), unit('130 mi'))
      assert.deepStrictEqual(unit('2 L').mul(unit('1 s^-1')), unit('2 L / s'))
      assert.deepStrictEqual(unit('2 m/s').mul(unit('0.5 s/m')), unit('1'))
      assert.deepStrictEqual(unit('5 degC').mul(2), unit('283.15 degC'))
    })

    it('should multiply units without values', () => {
      assert.deepStrictEqual(unit('kg').mul('kg'), unit('kg^2'))
      assert.deepStrictEqual(unit('4 kg').mul('kg'), unit('4 kg^2'))
      assert.deepStrictEqual(unit('kg').mul('4 kg'), unit('4 kg^2'))
      assert.deepStrictEqual(unit('m/s').mul('h/m').toString(), 'h / s')
      assert.deepStrictEqual(unit('1 m/s').mul('h/m').toString(), '3600')
    })

    it('should convert parameter to unit', () => {
      assert.deepStrictEqual(unit('1 hour').mul('30 minute'), unit(30, 'hour minute'))
      assert.deepStrictEqual(unit('1 hour').mul(3), unit('3 hour'))
    })

    it('should return a frozen unit', () => {
      assert(Object.isFrozen(unit('2 kg').mul(unit('3 m'))))
    })

    it('the alternate api syntax should also work', () => {
      assert.deepStrictEqual(unit.mul(unit('2 kg'), unit('3 m')), unit('6 kg m'))
      assert.deepStrictEqual(unit.mul('2 kg', '3 m'), unit('6 kg m'))
      assert.deepStrictEqual(unit.mul('2 kg', 3), unit('6 kg'))
      assert.deepStrictEqual(unit.mul(3, '2 kg'), unit('6 kg'))
    })
  })

  describe('div', () => {
    it('should divide unit\'s values and combine their units', () => {
      assert.deepStrictEqual(unit('6 kg').div(unit('3 m')), unit('2 kg m^-1'))
      assert.deepStrictEqual(unit('2 m').div(unit('4 m')), unit('0.5'))
      assert.deepStrictEqual(unit('4 ft').div(unit('2 ft')), unit('2'))
      assert.deepStrictEqual(unit('65 mi/h').div(unit('2 h')), unit('32.5 mi h^-2'))
      assert.deepStrictEqual(unit('2 L').div(unit('1 s^-1')), unit('2 L s'))
      assert.deepStrictEqual(unit('2 m/s').div(unit('0.5 s/m')), unit('4 m^2 s^-2'))
      assert.deepStrictEqual(unit('2 kg').div(unit('500 g')), unit(4))
      assert.deepStrictEqual(unit('2000 g').div(unit('0.5 kg')), unit(4))
    })

    it('should divide units without values', () => {
      assert.deepStrictEqual(unit('kg').div('s'), unit('kg/s'))
      assert.deepStrictEqual(unit('4 kg').div('s'), unit('4 kg/s'))
      assert.deepStrictEqual(unit('kg').div('4 s'), unit('0.25 kg/s'))
    })

    it('should convert parameter to unit', () => {
      assert.deepStrictEqual(unit('1 hour').div('0.5 hour'), unit(2))
      assert.deepStrictEqual(unit('1 hour').div(2), unit(0.5, 'hour'))
    })

    it('should return a frozen unit', () => {
      assert(Object.isFrozen(unit('2 kg').div(unit('3 m'))))
    })

    it('the alternate api syntax should also work', () => {
      assert.deepStrictEqual(unit.div(unit('6 kg'), unit('3 m')), unit('2 kg m^-1'))
      assert.deepStrictEqual(unit.div('6 kg', '3 m'), unit('2 kg m^-1'))
      assert.deepStrictEqual(unit.div('6 kg', 3), unit('2 kg'))
      assert.deepStrictEqual(unit.div(3, '6 kg'), unit('0.5 kg^-1'))
    })
  })

  describe('pow', () => {
    it('should calculate the power of a unit', () => {
      assert(unit('4 N').pow(2).equals(unit('16 N^2')))
      assert(unit('0.25 m/s').pow(-0.5).equals(unit('2 m^-0.5 s^0.5')))
      assert(unit('123 chain').pow(0).equals(unit('1')))
    })

    it('should work with units without values', () => {
      assert.strictEqual(unit('V/m').pow(2).format(), 'V^2 / m^2')
      assert(unit('1 V/m').pow(2).equals(unit('1 V^2/m^2')))
    })

    it('the alternate api syntax should also work', () => {
      assert(unit.pow('4 N', unit(2)).equals(unit('16 N^2')))
      assert(unit.pow(unit('0.25 m/s'), -0.5).equals(unit('2 m^-0.5 s^0.5')))
      assert(unit.pow('123 chain', 0).equals(unit('1')))
    })
  })

  describe('sqrt', () => {
    it('should calculate the square root of a unit', () => {
      assert(unit('16 kg').sqrt().equals(unit('4 kg^0.5')))
      assert(unit('16').sqrt().equals(unit('4')))
      assert(unit('16 m^2/s^2').sqrt().equals(unit('4 m/s')))
      assert.strictEqual(unit('-16 m^2/s^2').sqrt().format(), 'NaN m / s')
    })

    it('the alternate api syntax should also work', () => {
      assert(unit.sqrt('16 kg').equals(unit('4 kg^0.5')))
      assert(unit.sqrt(16).equals(unit('4')))
      assert(unit.sqrt(unit('16 m^2/s^2')).equals(unit('4 m/s')))
    })
  })

  describe('abs', () => {
    it('should return the absolute value of a unit', () => {
      assert.strictEqual(unit('5 m').abs().format(), '5 m')
      assert.strictEqual(unit('-5 m/s').abs().format(), '5 m / s')
      assert.strictEqual(unit('-283.15 degC').abs().format(), '-263.15 degC')
    })

    it('the alternate api syntax should also work', () => {
      assert.strictEqual(unit.abs('5 m').format(), '5 m')
      assert.strictEqual(unit.abs('-5 m/s').format(), '5 m / s')
      assert.strictEqual(unit.abs(unit('-5 m/s')).format(), '5 m / s')
      assert.strictEqual(unit.abs(-5).format(), '5')
      assert.strictEqual(unit.abs('-283.15 degC').format(), '-263.15 degC')
    })
  })

  describe('mul, div, and pow', () => {
    it('should retain the units of their operands without simplifying', () => {
      const unit1 = unit(10, 'N/s')
      const unit2 = unit(10, 'h')
      const unitM = unit1.mul(unit2)
      assert.strictEqual(unitM.units[0].unit.name, 'N')
      assert.strictEqual(unitM.units[1].unit.name, 's')
      assert.strictEqual(unitM.units[2].unit.name, 'h')

      const unit3 = unit(14.7, 'lbf')
      const unit4 = unit(1, 'in in')
      const unitD = unit3.div(unit4)
      assert.strictEqual(unitD.units[0].unit.name, 'lbf')
      assert.strictEqual(unitD.units[1].unit.name, 'in')
      assert.strictEqual(unitD.units[1].power, -2)

      const unit5 = unit(1, 'N h/s')
      const unitP = unit5.pow(-3.5)
      assert.strictEqual(unitP.units[0].unit.name, 'N')
      assert.strictEqual(unitP.units[1].unit.name, 'h')
      assert.strictEqual(unitP.units[2].unit.name, 's')
    })
  })

  describe('plurals', () => {
    it('should support plurals', () => {
      const unit1 = unit(5, 'meters')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'meters')
      assert.strictEqual(unit1.units[0].prefix, '')

      const unit2 = unit(5, 'kilometers')
      assert.strictEqual(unit2.value, 5)
      assert.strictEqual(unit2.units[0].unit.name, 'meters')
      assert.strictEqual(unit2.units[0].prefix, 'kilo')

      const unit3 = unit(5, 'inches')
      approx.equal(unit3.value, 5)
      assert.strictEqual(unit3.units[0].unit.name, 'inches')
      assert.strictEqual(unit3.units[0].prefix, '')

      const unit4 = unit(9.81, 'meters/second^2')
      approx.equal(unit4.value, 9.81)
      assert.strictEqual(unit4.units[0].unit.name, 'meters')
      assert.strictEqual(unit4.units[0].prefix, '')
    })
  })

  describe('aliases', () => {
    it('should support aliases', () => {
      const unit1 = unit(5, 'lt')
      assert.strictEqual(unit1.value, 5)
      assert.strictEqual(unit1.units[0].unit.name, 'lt')
      assert.strictEqual(unit1.units[0].prefix, '')

      const unit2 = unit(1, 'lb')
      assert.strictEqual(unit2.value, 1)
      assert.strictEqual(unit2.units[0].unit.name, 'lb')
      assert.strictEqual(unit2.units[0].prefix, '')
    })
  })

  describe('unitStore', () => {
    describe('defs.quantities', () => {
      it('should contain the correct dimension for each quantity', () => {
        assert.strictEqual(unit._unitStore.defs.baseQuantities.length, 10)
        assert.deepStrictEqual(unit._unitStore.defs.quantities['UNITLESS'], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['LENGTH'], [0, 1, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['TIME'], [0, 0, 1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['CURRENT'], [0, 0, 0, 1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['TEMPERATURE'], [0, 0, 0, 0, 1, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['LUMINOUS_INTENSITY'], [0, 0, 0, 0, 0, 1, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['AMOUNT_OF_SUBSTANCE'], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ANGLE'], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['BIT'], [0, 0, 0, 0, 0, 0, 0, 0, 1, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['SOLID_ANGLE'], [0, 0, 0, 0, 0, 0, 0, 0, 0, 1])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ABSEMENT'], [0, 1, 1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ACCELERATION'], [0, 1, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ANGULAR_ACCELERATION'], [0, 0, -2, 0, 0, 0, 0, 1, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ANGULAR_MOMENTUM'], [1, 2, -1, 0, 0, 0, 0, 1, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ANGULAR_VELOCITY'], [0, 0, -1, 0, 0, 0, 0, 1, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['AREA'], [0, 2, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['AREA_DENSITY'], [1, -2, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['BIT_RATE'], [0, 0, -1, 0, 0, 0, 0, 0, 1, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['CAPACITANCE'], [-1, -2, 4, 2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['CURRENT_DENSITY'], [0, -2, 0, 1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['DYNAMIC_VISCOSITY'], [1, -1, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRIC_CHARGE'], [0, 0, 1, 1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRIC_CHARGE_DENSITY'], [0, -3, 1, 1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRIC_DISPLACEMENT'], [0, -2, 1, 1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRIC_FIELD_STRENGTH'], [1, 1, -3, -1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRICAL_CONDUCTANCE'], [-1, -2, 3, 2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRICAL_CONDUCTIVITY'], [-1, -3, 3, 2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRIC_POTENTIAL'], [1, 2, -3, -1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['RESISTANCE'], [1, 2, -3, -2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ELECTRICAL_RESISTIVITY'], [1, 3, -3, -2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ENERGY'], [1, 2, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ENTROPY'], [1, 2, -2, 0, -1, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['FORCE'], [1, 1, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['FREQUENCY'], [0, 0, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['HEAT_CAPACITY'], [1, 2, -2, 0, -1, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['HEAT_FLUX_DENSITY'], [1, 0, -3, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['ILLUMINANCE'], [0, -2, 0, 0, 0, 1, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['IMPEDANCE'], [1, 2, -3, -2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['IMPULSE'], [1, 1, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['INDUCTANCE'], [1, 2, -2, -2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['IRRADIANCE'], [1, 0, -3, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['JERK'], [0, 1, -3, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['KINEMATIC_VISCOSITY'], [0, 2, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['LINEAR_DENSITY'], [1, -1, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['LUMINOUS_FLUX'], [0, 0, 0, 0, 0, 1, 0, 0, 0, 1])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MAGNETIC_FIELD_STRENGTH'], [0, -1, 0, 1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MAGNETIC_FLUX'], [1, 2, -2, -1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MAGNETIC_FLUX_DENSITY'], [1, 0, -2, -1, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MOLAR_CONCENTRATION'], [0, -3, 0, 0, 0, 0, 1, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MOLAR_ENERGY'], [1, 2, -2, 0, 0, 0, -1, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MOLAR_ENTROPY'], [1, 2, -2, 0, -1, 0, -1, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MOLAR_HEAT_CAPACITY'], [1, 2, -2, 0, -1, 0, -1, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MOMENT_OF_INERTIA'], [1, 2, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['MOMENTUM'], [1, 1, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['PERMEABILITY'], [1, 1, -2, -2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['PERMITTIVITY'], [-1, -3, 4, 2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['POWER'], [1, 2, -3, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['PRESSURE'], [1, -1, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['RELUCTANCE'], [-1, -2, 2, 2, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['SPECIFIC_ENERGY'], [0, 2, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['SPECIFIC_HEAT_CAPACITY'], [0, 2, -2, 0, -1, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['SPECIFIC_VOLUME'], [-1, 3, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['SPIN'], [1, 2, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['SURFACE_TENSION'], [1, 0, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['TEMPERATURE_GRADIENT'], [0, -1, 0, 0, 1, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['THERMAL_CONDUCTIVITY'], [1, 1, -3, 0, -1, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['TORQUE'], [1, 2, -2, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['VELOCITY'], [0, 1, -1, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['VOLUME'], [0, 3, 0, 0, 0, 0, 0, 0, 0, 0])
        assert.deepStrictEqual(unit._unitStore.defs.quantities['VOLUMETRIC_FLOW_RATE'], [0, 3, -1, 0, 0, 0, 0, 0, 0, 0])
      })
    })

    describe('defs.units', () => {
      it('built-in units should be of the correct value and dimension', () => {
        assert.strictEqual(unit(1, 's A').equals(unit(1, 'C')), true)
        assert.strictEqual(unit(1, 'W/A').equals(unit(1, 'V')), true)
        assert.strictEqual(unit(1, 'V/A').equals(unit(1, 'ohm')), true)
        assert.strictEqual(unit(1, 'C/V').equals(unit(1, 'F')), true)
        assert.strictEqual(unit(1, 'J/A').equals(unit(1, 'Wb')), true)
        assert.strictEqual(unit(1, 'Wb/m^2').equals(unit(1, 'T')), true)
        assert.strictEqual(unit(1, 'Wb/A').equals(unit(1, 'H')), true)
        assert.strictEqual(unit(1, 'ohm^-1').equals(unit(1, 'S')), true)
        assert.strictEqual(unit(1, 'eV').equals(unit(1.602176565e-19, 'J')), true)
      })

      it("For each built-in unit, 'name' should match key", () => {
        assert(unit._unitStore.defs.hasOwnProperty('units'))
        for (const key in unit._unitStore.defs.units) {
          assert.strictEqual(key, unit._unitStore.defs.units[key].name)
        }
      })
    })

    describe('defs.unitSystems', () => {
      it('should not have any dimensions that are not present in DIMENSIONS', () => {
        assert(unit._unitStore.defs.hasOwnProperty('unitSystems'))
        for (let sys in unit._unitStore.defs.unitSystems) {
          for (let dim in unit._unitStore.defs.unitSystems[sys]) {
            assert(unit._unitStore.defs.quantities.hasOwnProperty(dim), `${dim} not found in defs.quantities`)
          }
        }
      })
    })

    it.skip('should not reprocess units if only the formatting options have changed', () => {

    })
  })

  describe('angles', () => {
    it('should create angles', () => {
      assert.strictEqual(unit(1, 'radian').equals(unit(1, 'rad')), true)
      assert.strictEqual(unit(1, 'radians').equals(unit(1, 'rad')), true)
      assert(unit(1, 'degree').equals(unit(1, 'deg')))
      assert.strictEqual(unit(1, 'degrees').equals(unit(1, 'deg')), true)
      assert.strictEqual(unit(1, 'gradian').equals(unit(1, 'grad')), true)
      assert.strictEqual(unit(1, 'gradians').equals(unit(1, 'grad')), true)

      assert.strictEqual(unit(1, 'radian').to('rad').equals(unit(1, 'rad')), true)
      assert.strictEqual(unit(1, 'radians').to('rad').equals(unit(1, 'rad')), true)
      assert.deepStrictEqual(unit(1, 'deg').to('rad'), unit(2 * Math.PI / 360, 'rad').to())
      assert.strictEqual(unit(1, 'degree').to('rad').equals(unit(2 * Math.PI / 360, 'rad')), true)
      assert.strictEqual(unit(1, 'degrees').to('rad').equals(unit(2 * Math.PI / 360, 'rad')), true)
      assert.strictEqual(unit(1, 'gradian').to('rad').equals(unit(Math.PI / 200, 'rad')), true)
      assert.strictEqual(unit(1, 'gradians').to('rad').equals(unit(Math.PI / 200, 'rad')), true)
    })

    it('should have correct long/short prefixes', () => {
      assert.strictEqual(unit(0.02, 'rad').toString(), '20 mrad')
      assert.strictEqual(unit(0.02, 'radian').toString(), '20 milliradian')
      assert.strictEqual(unit(0.02, 'radians').toString(), '20 milliradians')

      assert.strictEqual(unit(0.02, 'grad').toString(), '2 cgrad')
      assert.strictEqual(unit(0.02, 'gradian').toString(), '2 centigradian')
      assert.strictEqual(unit(0.02, 'gradians').toString(), '2 centigradians')
    })
  })

  describe('split', () => {
    it('should split a unit into parts', () => {
      assert.strictEqual(unit(1, 'm').split(['ft', 'in']).join(','), '3 ft,3.37007874015748 in')
      assert.strictEqual(unit(-1, 'm').split(['ft', 'in']).join(','), '-3 ft,-3.37007874015748 in')
      assert.strictEqual(unit(1, 'm/s').split(['m/s']).join(','), '1 m / s')
      assert.strictEqual(unit(1, 'm').split(['ft', 'ft']).join(','), '3 ft,0.280839895013124 ft')
      assert.strictEqual(unit(1.23, 'm/s').split([]).join(','), '1.23 m / s')
      assert.strictEqual(unit(1, 'm').split(['in', 'ft']).join(','), '39 in,0.0308398950131236 ft')
      assert.strictEqual(unit(10, 'km').split(['mi', 'ft', 'in']).join(','), '6 mi,1128 ft,4.78740157486361 in')
      assert.strictEqual(unit(1, 'm').split([unit(null, 'ft'), unit(null, 'in')]).join(','), '3 ft,3.37007874015748 in')
      assert.strictEqual(unit('51.4934 deg').split(['deg', 'arcmin', 'arcsec']).map(a => a.toString({ precision: 6 })).join(','), '51 deg,29 arcmin,36.24 arcsec')
    })

    it('should be resistant to round-off error', () => {
      assert.strictEqual((unit(-12, 'in')).split(['ft', 'in']).join(','), '-1 ft,0 in')
      assert.strictEqual((unit(12, 'in')).split(['ft', 'in']).join(','), '1 ft,0 in')
      assert.strictEqual((unit(24, 'in')).split(['ft', 'in']).join(','), '2 ft,0 in')
      assert.strictEqual((unit(36, 'in')).split(['ft', 'in']).join(','), '3 ft,0 in')
      assert.strictEqual((unit(48, 'in')).split(['ft', 'in']).join(','), '4 ft,0 in')
      assert.strictEqual((unit(60, 'in')).split(['ft', 'in']).join(','), '5 ft,0 in')
      assert.strictEqual((unit(36000, 'in')).split(['ft', 'in']).join(','), '3000 ft,0 in')
    })
  })

  describe('toSI', () => {
    it('should return a clone of the unit', () => {
      const u1 = unit('3 ft')
      const u2 = u1.toSI()
      assert.strictEqual(u1 === u2, false)
    })

    it('should return the unit in SI units', () => {
      approx.deepEqual(unit('4 ft').toSI(), unit('1.2192 m').to())
      approx.deepEqual(unit('0.111 ft^2').toSI(), unit('0.01031223744 m^2').to())
    })

    it('should return SI units for valueless units', () => {
      assert.deepStrictEqual(unit('ft/minute').toSI(), unit('m / s').to())
    })

    it('alterate api syntax should work too', () => {
      approx.deepEqual(unit.toSI(unit('4 ft')), unit('1.2192 m').to())
      approx.deepEqual(unit.toSI('4 ft'), unit('1.2192 m').to())
    })

    it('should return SI units for custom units defined from other units', () => {
      let newUnit = configCustomUnits({ foo: '3 kW' })
      assert.strictEqual(newUnit('42 foo').toSI().format(), '126000 kg m^2 / s^3')
    })

    it('should throw if custom unit not defined from existing units', () => {
      let newUnit = unit.config({
        definitions: {
          baseQuantities: ['BAZINESS'],
          units: {
            baz: {
              quantity: 'BAZINESS',
              value: 1
            }
          }
        }
      })
      assert.throws(() => { newUnit('10 baz').toSI() }, /Cannot express unit '10 baz' in SI units. System 'si' does not contain a unit for base quantity 'BAZINESS'/)
    })
  })

  describe('custom types', () => {
    describe('configuration', () => {
      it('should throw if failed to include all custom type functions', () => {
        assert.throws(() => require('../index.js').config({ type: typeNoPow }), /You must supply all required custom type functions/)
      })

      it('should throw if failed to include conditionally required functions', () => {
        assert.throws(() => require('../index.js').config({ type: typeNoGt }), /The following custom type functions are required when prefix is/)
        assert.doesNotThrow(() => require('../index.js').config({ type: typeNoGt, prefix: 'never' }))
      })

      it('should throw if attempting to call a method that depends on a custom type function that was not provided', () => {
        let unitDecNoComp = require('../index.js').config({ type: typeNoComp, prefix: 'never' })
        let unitDecNoRound = require('../index.js').config({ type: typeNoRound })
        assert.throws(() => unitDecNoComp('3 m').equals('4 m'), /When using custom types, equals requires a type.eq function/)
        assert.throws(() => unitDecNoComp('3 m').compare('4 m'), /When using custom types, compare requires a type.gt and a type.lt function/)
        assert.throws(() => unitDecNoComp('3 m').lessThan('4 m'), /When using custom types, lessThan requires a type.lt function/)
        assert.throws(() => unitDecNoComp('3 m').lessThanOrEqual('4 m'), /When using custom types, lessThanOrEqual requires a type.le function/)
        assert.throws(() => unitDecNoComp('3 m').greaterThan('4 m'), /When using custom types, greaterThan requires a type.gt function/)
        assert.throws(() => unitDecNoComp('3 m').greaterThanOrEqual('4 m'), /When using custom types, greaterThanOrEqual requires a type.ge function/)
        assert.throws(() => unitDecNoRound('3 m').split(['ft'], ['in']), /When using custom types, split requires a type.round and a type.trunc function/)
      })
    })

    describe('constructing a unit', () => {
      it('if given a single string, should parse the numeric portion using type.conv', () => {
        let u = unitDec('3.1415926535897932384626433832795 rad')
        assert(u.value instanceof Decimal)
        assert.strictEqual(u.toString(), '3.1415926535897932384626433832795 rad')
      })

      it('if given a number, should convert it to custom type', () => {
        assert(unitDec(3.1415, 'rad').value instanceof Decimal)
      })

      it('should work if given the custom type directly', () => {
        let u = unitDec(Decimal('3.1415926535897932384626433832795'), 'rad')
        assert.strictEqual(u.toString(), '3.1415926535897932384626433832795 rad')
        assert(u.value instanceof Decimal)
      })

      it('should create valueless units', () => {
        let u = unitDec('rad')
        assert.strictEqual(u.toString(), 'rad')
        assert.strictEqual(u.value, null)
      })
    })

    describe('operations', () => {
      it('should add custom typed units', () => {
        let u1 = unitDec('0.3333333333333333333333 kg/m^3')
        let u2 = unitDec('0.6666666666666666666666 kg/m^3')
        let u3 = u1.add(u2)
        assert.strictEqual(u3.toString(), '0.9999999999999999999999 kg / m^3')
        assert(u3.value instanceof Decimal)
      })

      it('should subtract custom typed units', () => {
        let u1 = unitDec('0.3333333333333333333333 kg/m^3')
        let u2 = unitDec('0.6666666666666666666666 kg/m^3')
        let u3 = u1.sub(u2)
        assert.strictEqual(u3.toString(), '-0.3333333333333333333333 kg / m^3')
        assert(u3.value instanceof Decimal)
      })

      it('should multiply custom typed units', () => {
        let u1 = unitDec('0.3333333333333333333333 kg/m^3')
        let u2 = unitDec('3 m^3')
        let u3 = u1.mul(u2)
        assert.strictEqual(u3.toString(), '0.9999999999999999999999 kg')
        assert(u3.value instanceof Decimal)
      })

      it('should divide custom typed units', () => {
        let u1 = unitDec('1 kg')
        let u2 = unitDec('3 m^3')
        let u3 = u1.div(u2)
        assert.strictEqual(u3.toString(), '0.33333333333333333333333333333333 kg / m^3') // 32 3's
        assert(u3.value instanceof Decimal)
      })

      it('should do powers', () => {
        let u1 = unitDec('11 s')
        let u2 = u1.pow(30)
        assert.strictEqual(u2.toString(), '1.7449402268886407318558803753801e+31 s^30')
        assert(u2.value instanceof Decimal)
      })

      it('should do sqrt', () => {
        assert.strictEqual(unitDec('64 m^2/s^2').sqrt().format(), '8 m / s')
        assert.strictEqual(unitDec('2 W').sqrt().format(), '1.4142135623730950488016887242097 W^0.5')
      })

      it('should do split', () => {
        assert.strictEqual(unitDec(1, 'm').split(['ft', 'in']).join(','), '3 ft,3.3700787401574803149606299212595 in')
        assert.strictEqual(unitDec(-1, 'm').split(['ft', 'in']).join(','), '-3 ft,-3.3700787401574803149606299212595 in')
        assert.strictEqual(unitDec(1, 'm/s').split(['m/s']).join(','), '1 m / s')
        assert.strictEqual(unitDec(1, 'm').split(['ft', 'ft']).join(','), '3 ft,0.28083989501312335958005249343829 ft')
        assert.strictEqual(unitDec(1.23, 'm/s').split([]).join(','), '1.23 m / s')
        assert.strictEqual(unitDec(1, 'm').split(['in', 'ft']).join(','), '39 in,0.03083989501312335958005249343832 ft')
        assert.strictEqual(unitDec(10, 'km').split(['mi', 'ft', 'in']).join(','), '6 mi,1128 ft,4.7874015748031496062992125984252 in')
        assert.strictEqual(unitDec(1, 'm').split([unit(null, 'ft'), unit(null, 'in')]).join(','), '3 ft,3.3700787401574803149606299212598 in')
        assert.strictEqual(unitDec(100, 'in').split(['ft', 'in']).join(','), '8 ft,4 in')
        // TODO: Try redefining deg using a more precise value of pi
        assert.strictEqual(unitDec('51.4934 deg').split(['deg', 'arcmin', 'arcsec']).map(a => a.toString({ precision: 6 })).join(','), '51 deg,29 arcmin,36.240000000000072499200000000012 arcsec')
      })

      describe('equals', () => {
        it('should test if two custom typed units are equal', () => {
          assert.strictEqual(unitDec(100, 'cm').equals(unitDec(1, 'm')), true)
          assert.strictEqual(unitDec(100, 'cm').equals(unitDec(2, 'm')), false)
          assert.strictEqual(unitDec(100, 'cm').equals(unitDec(1, 'kg')), false)
          assert.strictEqual(unitDec(100, 'ft lbf').equals(unitDec(1200, 'in lbf')), true)
          assert.strictEqual(unitDec(100, 'N').equals(unitDec(100, 'kg m / s ^ 2')), true)
          assert.strictEqual(unitDec(100, 'N').equals(unitDec(100, 'kg m / s')), false)
          assert.strictEqual(unitDec(100, 'Hz').equals(unitDec(100, 's ^ -1')), true)
        })

        it('should work with valueless units', () => {
          assert.strictEqual(unitDec('cm').equals(unitDec('cm')), true)
          assert.strictEqual(unitDec('cm').equals(unitDec('m')), false)
          assert.strictEqual(unitDec('cm/s').equals(unitDec('cm/s')), true)
        })

        it('should convert parameter to a unit', () => {
          assert(unitDec(100, 'cm').equals('1 m'))
          assert(unitDec('3 kg / kg').equals(3))
        })
      })

      it('should do comparisons', () => {
        assert(unitDec('10 m').lessThan('1 km'))
        assert(unitDec('5 km').lessThanOrEqual('500000 cm'))
        assert(unitDec('5 N').greaterThan('5 dyne'))
        assert(unitDec('10 kg').greaterThanOrEqual('1 kg'))
        assert.strictEqual(unitDec('60 min').compare('2 hour'), -1)
        assert.strictEqual(unitDec('60 min').compare('1 hour'), 0)
        assert.strictEqual(unitDec('60 min').compare('0.5 hour'), 1)
      })

      it('should do setValue', () => {
        assert.strictEqual(unitDec('64 m^2/s^2').setValue(10).format(), '10 m^2 / s^2')
        assert.strictEqual(unitDec('64 m^2/s^2').setValue('1.4142135623730950488016887242097').format(), '1.4142135623730950488016887242097 m^2 / s^2')
        assert.strictEqual(unitDec('64 m^2/s^2').setValue(Decimal('1.4142135623730950488016887242097')).format(), '1.4142135623730950488016887242097 m^2 / s^2')
      })

      // TODO: Test all other custom functions
    })

    describe('formatting', () => {
      it('should choose the best prefix', () => {
        assert.strictEqual(unitDec('0.000001 m').format(8), '1 um')
        assert.strictEqual(unitDec('0.00001 m').format(8), '10 um')
        assert.strictEqual(unitDec('0.0001 m').format(8), '0.1 mm')
        assert.strictEqual(unitDec('0.0005 m').format(8), '0.5 mm')
        assert.strictEqual(unitDec('0.0006 m').toString(), '0.6 mm')
        assert.strictEqual(unitDec('0.001 m').toString(), '0.1 cm')
        assert.strictEqual(unitDec('0.01 m').toString(), '1 cm')
        assert.strictEqual(unitDec('100000 m').toString(), '100 km')
        assert.strictEqual(unitDec('300000 m').toString(), '300 km')
        assert.strictEqual(unitDec('500000 m').toString(), '500 km')
        assert.strictEqual(unitDec('600000 m').toString(), '600 km')
        assert.strictEqual(unitDec('1000000 m').toString(), '1000 km')
        assert.strictEqual(unitDec('10000000 m').toString(), '10000 km')
        assert.strictEqual(unitDec('1232123212321232123212321 m').toString(), '1.232123212321232123212321e+21 km')
        assert.strictEqual(unitDec('2000 ohm').toString(), '2 kohm')
      })

      it('should use custom formatter', () => {
        let unitFunny = require('../index.js').config({ type: typeFunnyFormat })
        assert.strictEqual(unitFunny('3.14159 rad').toString('$', '_'), '$9_5_1_4_1_._3 rad')
      })
    })
  })

  describe('built-in units', () => {
    describe('us customary liquid units', () => {
      assert.strictEqual(unit('1 cup').to('mL').toString(), '236.5882365 mL')
      assert.strictEqual(unit('1 cup').to('gallons').toString(), '0.0625 gallons')
      assert.strictEqual(unit('1 cup').to('quarts').toString(), '0.25 quarts')
      assert.strictEqual(unit('1 cup').to('pints').toString(), '0.5 pints')
      assert.strictEqual(unit('1 cup').to('floz').toString(), '8 floz')
      assert.strictEqual(unit('1 cup').to('tablespoons').toString(), '16 tablespoons')
      assert.strictEqual(unit('1 cup').to('teaspoons').toString(), '48 teaspoons')
      assert.strictEqual(unit('1 cup').to('fluiddrams').toString(), '64 fluiddrams')
    })
  })
})


<|start_filename|>src/Unit.js<|end_filename|>
import createUnitStore from './UnitStore.js'
import { normalize, denormalize, isCompound as _isCompound } from './utils.js'

// TODO: Make things behave nicely when performing operations between units that exist in different namespaces (ahhhhh!)

/**
 * Create a clone of the this unit factory function, but with the specified options.
 * @param {Object} options Configuration options to set on the new instance.
 * @returns {Function} A new instance of the unit factory function with the specified options.
 */
let _config = function _config (options) {
  options = Object.assign({}, options)

  // Check to make sure options are valid

  const validPrefix = ['never', 'auto', 'always']
  if (!validPrefix.includes(options.prefix)) {
    throw new Error(`Invalid option for prefix: '${options.prefix}'. Valid options are ${validPrefix.join(', ')}`)
  }

  const validSimplify = ['never', 'auto', 'always']
  if (!validSimplify.includes(options.simplify)) {
    throw new Error(`Invalid option for simplify: '${options.simplify}'. Valid options are ${validSimplify.join(', ')}`)
  }

  // options.system will be checked in createUnitStore

  // Check to see if all required options.type functions have been set
  const requiredTypeFns = ['conv', 'clone', 'add', 'sub', 'mul', 'div', 'pow']
  let allRequiredTypeFnsPresent = true
  let oneRequiredTypeFnsPresent = false
  for (const fn of requiredTypeFns) {
    if (options.type[fn]._IS_UNITMATH_DEFAULT_FUNCTION) {
      allRequiredTypeFnsPresent = false
    } else {
      oneRequiredTypeFnsPresent = true
    }
  }

  if (oneRequiredTypeFnsPresent) {
    if (!allRequiredTypeFnsPresent) {
      throw new Error(`You must supply all required custom type functions: ${requiredTypeFns.join(', ')}`)
    }

    // Check type functions required for _choosePrefix
    if (options.prefix !== 'never') {
      const prefixRequiredTypeFns = ['lt', 'gt', 'le', 'ge', 'abs']
      let allPrefixRequiredTypeFnsPresent = true
      for (const fn of prefixRequiredTypeFns) {
        if (options.type[fn]._IS_UNITMATH_DEFAULT_FUNCTION) {
          allPrefixRequiredTypeFnsPresent = false
        }
      }
      if (!allPrefixRequiredTypeFnsPresent) {
        throw new Error(`The following custom type functions are required when prefix is '${options.prefix}': ${prefixRequiredTypeFns.join(', ')}`)
      }
    }
  }

  Object.freeze(options)

  /**
   * Factory function unitmath returns a new Unit (so that user does not have to use "new" keyword, but we still benefit from using prototypes)
   * @param {Number|String|*} value The `number` to assign to the unit, or a `string` representing the value and the unit string together.
   * @param {String} unitString The unit string, unless already included in the `value` parameter.
   * @returns {Unit} The Unit given by the value and unit string.
   */
  function unitmath (value, unitString) {
    let unit = new Unit(value, unitString)
    Object.freeze(unit)
    return unit
  }

  /**
   * The actual constructor for Unit. Creates a new Unit with the specified value and unit string.
   * @param {Number|String|*} value The `number` to assign to the unit, or a `string` representing the value and the unit string together.
   * @param {String} unitString The unit string, unless already included in the `value` parameter.
   * @returns {Unit} The Unit given by the value and unit string.
   */
  class Unit {
    constructor (value, unitString) {
      let parseResult

      if (typeof value === 'undefined' && typeof unitString === 'undefined') {
        // No arguments
        parseResult = unitStore.parser('')
        parseResult.value = null
      } else if (typeof value === 'string' && typeof unitString === 'undefined') {
        // single string
        parseResult = unitStore.parser(value)
      } else if (typeof unitString === 'string') {
        // number|string|custom, string
        parseResult = unitStore.parser(unitString)
        parseResult.value = options.type.conv(value)
      } else if (typeof unitString === 'undefined') {
        // number|custom
        parseResult = unitStore.parser('')
        parseResult.value = options.type.conv(value)
      } else {
        throw new TypeError('To construct a unit, you must supply a single string, two strings, a number and a string, or a custom type and a string.')
      }
      this.type = 'Unit'
      this.dimension = parseResult.dimension
      this.units = _combineDuplicateUnits(parseResult.units)
      this.value = (parseResult.value === undefined || parseResult.value === null) ? null : denormalize(this.units, normalize(parseResult.units, parseResult.value, options.type), options.type)
    }

    // These are public methods available to each instance of a Unit. They each should return a frozen Unit.

    /**
     * create a copy of this unit
     * @memberof Unit
     * @return {Unit} Returns a cloned version of the unit
     */
    clone () {
      let unit = _clone(this)
      Object.freeze(unit)
      return unit
    }

    /**
     * Adds two units. Both units' dimensions must be equal.
     * @param {Unit|string} other The unit to add to this one. If a string is supplied, it will be converted to a unit.
     * @returns {Unit} The result of adding this and the other unit.
     */
    add (other) {
      other = _convertParamToUnit(other)
      let unit = _add(this, other)
      Object.freeze(unit)
      return unit
    }

    /**
     * Subtracts two units. Both units' dimensions must be equal.
     * @param {Unit|string} other The unit to subtract from this one. If a string is supplied, it will be converted to a unit.
     * @returns {Unit} The result of subtract this and the other unit.
     */
    sub (other) {
      other = _convertParamToUnit(other)
      let unit = _sub(this, other)
      Object.freeze(unit)
      return unit
    }

    /**
     * Multiplies two units.
     * @param {Unit|string} other The unit to multiply to this one.
     * @returns {Unit} The result of multiplying this and the other unit.
     */
    mul (other) {
      other = _convertParamToUnit(other)
      let unit = _mul(this, other)
      Object.freeze(unit)
      return unit
    }

    /**
     * Divides two units.
     * @param {Unit|string} other The unit to divide this unit by.
     * @returns {Unit} The result of dividing this by the other unit.
     */
    div (other) {
      other = _convertParamToUnit(other)
      let unit = _div(this, other)
      Object.freeze(unit)
      return unit
    }

    /**
     * Calculate the power of a unit
     * @memberof Unit
     * @param {number|custom} p
     * @returns {Unit}      The result: this^p
     */
    pow (p) {
      let unit = _pow(this, p)
      Object.freeze(unit)
      return unit
    }

    /**
     * Takes the square root of a unit.
     * @memberof Unit
     * @returns {Unit} The square root of this unit.
     */
    sqrt () {
      let unit = _sqrt(this)
      Object.freeze(unit)
      return unit
    }

    /**
     * Returns the absolute value of this unit.
     * @memberOf Unit
     * @returns {Unit} The absolute value of this unit.
     */
    abs () {
      let unit = _abs(this)
      Object.freeze(unit)
      return unit
    }

    /**
     * Returns an array of units whose sum is equal to this unit, where each unit in the array is taken from the supplied string array.
     * @param {string[]} units A string array of units to split this unit into.
     * @returns {Unit[]} An array of units
     */
    split (units) {
      let us = _split(this, units)
      for (let i = 0; i < us.length; i++) {
        Object.freeze(us[i])
      }
      return us
    }

    /**
     * Convert the unit to a specific unit.
     * @memberof Unit
     * @param {string | Unit} valuelessUnit   A unit without value. Can have prefix, like "cm". If omitted, a new unit is returned which is fixed (will not be auto-simplified)
     * @returns {Unit} Returns a clone of the unit with a fixed prefix and unit.
     */
    to (valuelessUnit) {
      let unit
      if (typeof valuelessUnit === 'undefined') {
        // Special case. Just clone the unit and set as fixed.
        unit = _clone(this)
        unit.fixed = true
        Object.freeze(unit)
        return unit
      } else {
        if (!(valuelessUnit.type === 'Unit') && typeof valuelessUnit !== 'string') {
          throw new TypeError('Parameter must be a Unit or a string.')
        }
        valuelessUnit = _convertParamToUnit(valuelessUnit)
        unit = _to(this, valuelessUnit)
        Object.freeze(unit)
        return unit
      }
    }

    /**
     * Convert the unit to SI units.
     * @memberof Unit
     * @returns {Unit} Returns a clone of the unit with a fixed prefix and unit.
     */
    toSI () {
      let unit = _toSI(this)
      Object.freeze(unit)
      return unit
    }

    /**
     * Returns a new unit with the given value.
     * @param {number | string | custom} value
     * @returns A new unit with the given value.
     */
    setValue (value) {
      let unit = _setValue(this, value)
      Object.freeze(unit)
      return unit
    }

    /**
     * Returns this unit's value.
     * @returns The value of this unit.
     */
    getValue () {
      return this.value
    }

    /**
     * Returns this unit's normalized value, which is the value it would have if it were to be converted to SI base units (or whatever base units are defined)
     * @returns The notmalized value of the unit.
     */
    getNormalizedValue () {
      return normalize(this.units, this.value, options.type)
    }

    /**
     * Returns a new unit with the given normalized value.
     * @param {number | string | custom} normalizedValue
     * @returns A new unit with the given normalized value.
     */
    setNormalizedValue (normalizedValue) {
      let unit = _setValue(this, denormalize(this.units, normalizedValue, options.type))
      Object.freeze(unit)
      return unit
    }

    /**
     * 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.
     * @returns {Unit} A simplified unit if possible, or the original unit if it could not be simplified.
     */
    simplify () {
      // console.log(this)
      const result = _clone(this)

      let systemStr = options.system
      if (systemStr === 'auto') {
        // If unit system is 'auto', then examine the existing units to infer which system is preferred. Favor 'si', or the first available system, in the event of a tie.

        // TODO: Object key order might not be consistent across platforms
        let firstAvailableSystem = Object.keys(unitStore.defs.unitSystems)[0]
        let identifiedSystems = { [firstAvailableSystem]: 0.1 }
        for (let i = 0; i < this.units.length; i++) {
          this.units[i].unit.systems.forEach(sys => {
            identifiedSystems[sys] = (identifiedSystems[sys] || 0) + 1
          })
        }
        let ids = Object.keys(identifiedSystems)
        ids.sort((a, b) => identifiedSystems[a] < identifiedSystems[b] ? 1 : -1)
        // console.log(`Identified the following systems when examining unit ${result.to().format()}`, ids.map(id => `${id}=${identifiedSystems[id]}`))
        systemStr = ids[0]
      }

      let system = unitStore.defs.unitSystems[systemStr]

      const proposedUnitList = []

      // Search for a matching dimension in the given unit system
      let matchingDim
      for (const key in system) {
        if (result.hasQuantity(key)) {
          // console.log(`Found a matching dimension in system ${systemStr}: ${result.to().format()} has a dimension of ${key}, unit is ${system[key].unit.name}`)
          matchingDim = key
          break
        }
      }

      let ok = true
      if (matchingDim) {
        // console.log(`Pushing onto proposed unit list: ${system[matchingDim].prefix}${system[matchingDim].unit.name}`)
        proposedUnitList.push({
          unit: system[matchingDim].unit,
          prefix: system[matchingDim].prefix,
          power: 1.0
        })
      } else {
        // Multiple units or units with powers are formatted like this:
        // 5 kg m^2 / s^3 mol
        // Build a representation from the base units of the current unit system
        for (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
          const baseDim = unitStore.defs.baseQuantities[i]
          if (Math.abs(result.dimension[i] || 0) > 1e-12) {
            if (system.hasOwnProperty(baseDim)) {
              proposedUnitList.push({
                unit: system[baseDim].unit,
                prefix: system[baseDim].prefix,
                power: result.dimension[i]
              })
            } else {
              ok = false
            }
          }
        }
      }

      // TODO: Decide when to simplify in case that the system is different, as in, unit.config({ system: 'us' })('10 N')).toString()

      if (ok) {
        // Replace this unit list with the proposed list
        result.units = proposedUnitList
        if (this.value !== null) {
          result.value = denormalize(result.units, normalize(this.units, this.value, options.type), options.type)
        } else {
          result.value = null
        }
      }

      Object.freeze(result)
      return result
    }

    /**
     * Returns this unit without a value.
     * @memberof Unit
     * @returns {Unit} A new unit formed by removing the value from this unit.
     */
    getUnits () {
      let result = _clone(this)
      result.value = null
      Object.freeze(result)
      return result
    }

    /**
     * Returns whether the unit is compound (like m/s, cm^2) or not (kg, N, hogshead)
     * @memberof Unit
     * @returns True if the unit is compound
     */
    isCompound () {
      return _isCompound(this.units)
    }

    /**
     * check if this unit matches the given quantity
     * @memberof Unit
     * @param {QUANTITY | string | undefined} quantity
     */
    hasQuantity (quantity) {
      if (typeof (quantity) === 'string') {
        quantity = unitStore.defs.quantities[quantity]
      }
      if (!quantity) {
        return false
      }
      // Dimensions must be the same
      for (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
        if (Math.abs((this.dimension[i] || 0) - (quantity[i] || 0)) > 1e-12) {
          return false
        }
      }
      return true
    }

    /**
     * Check if this unit has a dimension equal to another unit
     * @param {Unit} other
     * @return {boolean} true if equal dimensions
     */
    equalQuantity (other) {
      other = _convertParamToUnit(other)
      // All dimensions must be the same
      for (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
        if (Math.abs(this.dimension[i] - other.dimension[i]) > 1e-12) {
          return false
        }
      }
      return true
    }

    /**
     * Returns a string array of all the quantities that match this unit.
     * @return {string[]} The matching quantities, or an empty array if there are no matching quantities.
     */
    getQuantities () {
      const result = []
      for (let d in unitStore.defs.quantities) {
        if (this.hasQuantity(d)) {
          result.push(d)
        }
      }
      return result
    }

    /**
     * Check if this unit equals another unit
     * @memberof Unit
     * @param {Unit} other
     * @return {boolean} true if both units are equal
     */
    equals (other) {
      if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && options.type.eq._IS_UNITMATH_DEFAULT_FUNCTION) {
        throw new Error(`When using custom types, equals requires a type.eq function`)
      }
      other = _convertParamToUnit(other)
      if ((this.value === null) !== (other.value === null)) {
        // One has a value and the other does not, so they cannot be equal
        return false
      }
      let { value1, value2 } = _comparePrepare(this, other, false)
      return this.equalQuantity(other) && options.type.eq(value1, value2)
    }

    /**
     * Compare this unit to another and return a value indicating whether this unit is less than, greater than, or equal to the other.
     * @param {Unit} other
     * @return {number} -1 if this unit is less than, 1 if this unit is greater than, and 0 if this unit is equal to the other unit.
     */
    compare (other) {
      if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && (options.type.gt._IS_UNITMATH_DEFAULT_FUNCTION || options.type.lt._IS_UNITMATH_DEFAULT_FUNCTION)) {
        throw new Error(`When using custom types, compare requires a type.gt and a type.lt function`)
      }
      other = _convertParamToUnit(other)
      let { value1, value2 } = _comparePrepare(this, other, true)

      if (isNaN(value1)) {
        return 1
      } else if (isNaN(value2)) {
        return -1
      } else if (options.type.lt(value1, value2)) {
        return -1
      } else if (options.type.gt(value1, value2)) {
        return 1
      } else {
        return 0
      }
    }

    /**
     * Compare this unit to another and return whether this unit is less than the other.
     * @param {Unit} other
     * @return {boolean} true if this unit is less than the other.
     */
    lessThan (other) {
      if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && options.type.lt._IS_UNITMATH_DEFAULT_FUNCTION) {
        throw new Error(`When using custom types, lessThan requires a type.lt function`)
      }
      other = _convertParamToUnit(other)
      let { value1, value2 } = _comparePrepare(this, other, true)
      return options.type.lt(value1, value2)
    }

    /**
     * Compare this unit to another and return whether this unit is less than or equal to the other.
     * @param {Unit} other
     * @return {boolean} true if this unit is less than or equal the other.
     */
    lessThanOrEqual (other) {
      if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && options.type.le._IS_UNITMATH_DEFAULT_FUNCTION) {
        throw new Error(`When using custom types, lessThanOrEqual requires a type.le function`)
      }
      other = _convertParamToUnit(other)
      let { value1, value2 } = _comparePrepare(this, other, true)
      return options.type.le(value1, value2)
    }

    /**
     * Compare this unit to another and return whether this unit is greater than the other.
     * @param {Unit} other
     * @return {boolean} true if this unit is greater than the other.
     */
    greaterThan (other) {
      if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && options.type.gt._IS_UNITMATH_DEFAULT_FUNCTION) {
        throw new Error(`When using custom types, greaterThan requires a type.gt function`)
      }
      other = _convertParamToUnit(other)
      let { value1, value2 } = _comparePrepare(this, other, true)
      return options.type.gt(value1, value2)
    }

    /**
     * Compare this unit to another and return whether this unit is greater than or equal to the other.
     * @param {Unit} other
     * @return {boolean} true if this unit is greater than or equal the other.
     */
    greaterThanOrEqual (other) {
      if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && options.type.ge._IS_UNITMATH_DEFAULT_FUNCTION) {
        throw new Error(`When using custom types, greaterThanOrEqual requires a type.ge function`)
      }
      other = _convertParamToUnit(other)
      let { value1, value2 } = _comparePrepare(this, other, true)
      return options.type.ge(value1, value2)
    }

    /**
     * Get a string representation of the Unit, with optional formatting options. Alias of `format`.
     * @memberof Unit
     * @param {Object} [opts]  Formatting options.
     * @return {string}
     */
    toString (...opts) {
      return this.format(...opts)
    }

    /**
     * Returns a raw string representation of this Unit, without simplifying or rounding. Could be useful for debugging.
     */
    valueOf () {
      return this.format({
        precision: 0, // 0 means do not round
        simplify: 'never',
        prefix: 'never',
        parentheses: false
      })
    }

    /**
     * Get a string representation of the Unit, with optional formatting options.
     * @memberof Unit
     * @param {Object} [userOpts]  Formatting options.
     * @return {string}
     */
    format (...userOpts) {
      let simp = this.clone()

      // A bit of clarification:
      // options is the original options
      // userOpts is a user-supplied argument
      // _opts is the original options, extended with opts if opts is an object

      let _opts = Object.assign({}, options)
      if (typeof userOpts[0] === 'object') {
        _opts = Object.assign(_opts, userOpts[0])
      }

      if (_opts.simplify === 'always') {
        simp = simp.simplify()
      } else if (_opts.simplify === 'auto' && !this.fixed && this.value !== null) {
        let simp2 = simp.simplify()

        // Determine if the simplified unit is simpler

        let calcComplexity = (unitList) => {
          // Number of total units, each adds one symbol
          let comp = unitList.length

          // Number of units in denominator and numerator
          let nDen = unitList.filter(a => a.power < 1e-14).length
          let nNum = unitList.length - nDen

          // If there are no units in the numerator, then any units in the denominator will need a ^-1

          // Number of units in the numerator containing powers !== 1, i.e. kg^2, adds two symbols
          comp += unitList.filter(a => Math.abs(a.power - 1) > 1e-14).length * 2

          // If there is at least one unit in the numerator and denominator, we will invert the denominator units' powers
          let denPowerInvert = nDen > 0 && nNum > 0 ? -1 : 1

          // Number of units in the denominator containing inverted? powers !== 1
          comp += unitList.filter(a => a.power < 0 && Math.abs(a.power * denPowerInvert - 1) > 1e-14).length * 2

          // At least one unit in numerator and denominator, adds one symbol: '/'
          if (nNum > 0 && nDen > 0) {
            comp += 1
          }

          return comp
        }

        // TODO: Decide when to simplify in case that the system is different, as in, unit.config({ system: 'us' })('10 N')).toString()

        // Is the proposed unit list "simpler" than the existing one?
        if (calcComplexity(simp2.units) <= calcComplexity(simp.units) - _opts.simplifyThreshold) {
          simp = simp2
        }
      }

      if (_opts.prefix === 'always' || (_opts.prefix === 'auto' && !this.fixed)) {
        simp = _choosePrefix(simp, _opts)
      }

      let str = ''
      if (typeof simp.value === 'number' && _opts.type.format._IS_UNITMATH_DEFAULT_FUNCTION && _opts.precision > 0) {
        // Use default formatter
        str += +simp.value.toPrecision(_opts.precision) // The extra + at the beginning removes trailing zeroes
      } else if (simp.value !== null) {
        // Use custom type's format method (which defaults to the toString(opts) method)
        str += _opts.type.format(simp.value, ...userOpts)
      }
      const unitStr = _formatUnits(simp, _opts)
      if (unitStr.length > 0 && str.length > 0) {
        str += ' '
      }
      str += unitStr
      return str
    }
  }
  // END OF UNIT CLASS

  // These private functions do not freeze the units before returning, so that we can do mutations on the units before returning the final, frozen unit to the user.

  // TODO: Possible source of unhelpful error message and user confusion, if user supplies a type that is not a unit, not a string, and not a number, to a public API method that uses this function to convert input to a unit. Since there is no way to know whether a user might be using a custom type, or just sent the wrong type.
  /**
   * Converts the supplied parameter to a frozen unit, or, if a unit was supplied, returns it unchanged.
   * @param {any} param
   * @returns {Unit} The frozen unit that was converted from the input parameter, or the original unit.
   */
  function _convertParamToUnit (param) {
    if (param.type === 'Unit') {
      return param
    }
    return unitmath(param)
  }

  /**
   * Private function _clone
   * @param {Unit} unit
   */
  function _clone (unit) {
    const result = new Unit()
    result.value = unit.value === null ? null : options.type.clone(unit.value)
    result.dimension = unit.dimension.slice(0)
    if (unit.fixed) {
      result.fixed = unit.fixed
    }
    result.units = []
    for (let i = 0; i < unit.units.length; i++) {
      result.units[i] = {}
      for (const p in unit.units[i]) {
        if (unit.units[i].hasOwnProperty(p)) {
          result.units[i][p] = unit.units[i][p]
        }
      }
    }

    return result
  }

  /**
   * Private function _combineDuplicateUnits returns a new array of unit pieces where the duplicate units have been combined together. Units with zero power are also removed.
   * @param {unit[]} units Array of unit pieces
   * @returns {unit[]} A new array of unit pieces where the duplicates have been combined together and units with zero power have been removed.
   */
  function _combineDuplicateUnits (units) {
    // Two-level deep copy of units
    let result = units.map(u => Object.assign({}, u))

    if (result.length >= 2) {
      // Combine duplicate units
      let foundUnits = {}
      for (let i = 0; i < result.length; i++) {
        if (foundUnits.hasOwnProperty(result[i].unit.name)) {
          // Combine this unit with the other
          let firstUnit = foundUnits[result[i].unit.name]
          // console.log(`Found duplicate unit: ${result[i].unit.name}`)
          // console.log(firstUnit.power)
          // console.log(result[i].power)
          firstUnit.power += result[i].power
          result.splice(i, 1)
          i--
        } else {
          foundUnits[result[i].unit.name] = result[i]
        }
      }

      // Remove units that have zero power
      for (let i = 0; i < result.length; i++) {
        if (Math.abs(result[i].power) < 1e-15) {
          result.splice(i, 1)
          i--
        }
      }
    }

    return result
  }

  function _comparePrepare (unit1, unit2, requireMatchingDimensions) {
    if (requireMatchingDimensions && !unit1.equalQuantity(unit2)) {
      throw new Error(`Cannot compare units ${unit1} and ${unit2}; dimensions do not match`)
    }
    let value1, value2
    if (unit1.value === null && unit2.value === null) {
      // If both units are valueless, get the normalized value of 1 to compare only the unit lists
      value1 = normalize(unit1.units, options.type.conv(1, unit1.value), options.type)
      value2 = normalize(unit2.units, options.type.conv(1, unit2.value), options.type)
    } else if (unit1.value !== null && unit2.value !== null) {
      // Both units have values
      value1 = normalize(unit1.units, unit1.value, options.type)
      value2 = normalize(unit2.units, unit2.value, options.type)
    } else {
      // One has a value and one does not. Not allowed.
      throw new Error(`Cannot compare units ${unit1} and ${unit2}; one has a value and the other does not`)
    }
    return { value1, value2 }
  }

  /**
   * Private function _add
   * @param {Unit} unit1 The first unit
   * @param {Unit} unit2 The second unit
   * @returns {Unit} The sum of the two units
   */
  function _add (unit1, unit2) {
    if (unit1.value === null || unit1.value === undefined || unit2.value === null || unit2.value === undefined) {
      throw new Error(`Cannot add ${unit1.toString()} and ${unit2.toString()}: both units must have values`)
    }
    if (!unit1.equalQuantity(unit2)) {
      throw new Error(`Cannot add ${unit1.toString()} and ${unit2.toString()}: dimensions do not match`)
    }
    const result = _clone(unit1)
    result.value = denormalize(unit1.units, options.type.add(normalize(unit1.units, unit1.value, options.type), normalize(unit2.units, unit2.value, options.type)), options.type)
    return result
  }

  /**
   * Private function _sub
   * @param {Unit} unit1 The first unit
   * @param {Unit} unit2 The second unit
   * @returns {Unit} The difference of the two units
   */
  function _sub (unit1, unit2) {
    if (unit1.value === null || unit1.value === undefined || unit2.value === null || unit2.value === undefined) {
      throw new Error(`Cannot subtract ${unit1.toString()} and ${unit2.toString()}: both units must have values`)
    }
    if (!unit1.equalQuantity(unit2)) {
      throw new Error(`Cannot subtract ${unit1.toString()} and ${unit2.toString()}: dimensions do not match`)
    }
    const result = _clone(unit1)
    result.value = denormalize(unit1.units, options.type.sub(normalize(unit1.units, unit1.value, options.type), normalize(unit2.units, unit2.value, options.type)), options.type)
    return result
  }

  /**
   * Private function _mul
   * @param {Unit} unit1 The first unit
   * @param {Unit} unit2 The second unit
   * @returns {Unit} The product of the two units
   */
  function _mul (unit1, unit2) {
    const result = _clone(unit1)

    for (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
      // dimension arrays may be of different lengths. Default to 0.
      result.dimension[i] = unit1.dimension[i] + unit2.dimension[i]
    }

    // Append other's units list onto result
    for (let i = 0; i < unit2.units.length; i++) {
      // Make a deep copy
      const inverted = {}
      for (const key in unit2.units[i]) {
        inverted[key] = unit2.units[i][key]
      }
      result.units.push(inverted)
    }

    result.units = _combineDuplicateUnits(result.units)

    // If at least one operand has a value, then the result should also have a value
    if (unit1.value !== null || unit2.value !== null) {
      const val1 = unit1.value === null ? normalize(unit1.units, 1, options.type) : normalize(unit1.units, unit1.value, options.type)
      const val2 = unit2.value === null ? normalize(unit2.units, 1, options.type) : normalize(unit2.units, unit2.value, options.type)
      result.value = denormalize(result.units, options.type.mul(val1, val2), options.type)
    } else {
      result.value = null
    }

    return result
  }

  /**
   * Private function _div
   * @param {Unit} unit1 The first unit
   * @param {Unit} unit2 The second unit
   */
  function _div (unit1, unit2) {
    const result = _clone(unit1)

    for (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
      result.dimension[i] = unit1.dimension[i] - unit2.dimension[i]
    }

    // Invert and append other's units list onto result
    for (let i = 0; i < unit2.units.length; i++) {
      // Make a deep copy
      const inverted = {}
      for (const key in unit2.units[i]) {
        inverted[key] = unit2.units[i][key]
      }
      inverted.power = -inverted.power
      result.units.push(inverted)
    }

    result.units = _combineDuplicateUnits(result.units)

    // If at least one operand has a value, the result should have a value
    if (unit1.value !== null || unit2.value !== null) {
      const val1 = unit1.value === null ? normalize(unit1.units, 1, options.type) : normalize(unit1.units, unit1.value, options.type)
      const val2 = unit2.value === null ? normalize(unit2.units, 1, options.type) : normalize(unit2.units, unit2.value, options.type)
      result.value = denormalize(result.units, options.type.div(val1, val2), options.type)
    } else {
      result.value = null
    }

    return result
  }

  /**
   * Private function _pow
   * @param {Unit} unit The unit
   * @param {number|custom} p The exponent
   */
  function _pow (unit, p) {
    // TODO: combineDuplicateUnits
    const result = _clone(unit)
    for (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
      result.dimension[i] = unit.dimension[i] * p
    }

    // Adjust the power of each unit in the list
    for (let i = 0; i < result.units.length; i++) {
      result.units[i].power = result.units[i].power * p
    }

    if (result.value !== null) {
      result.value = options.type.pow(result.value, options.type.conv(p, unit.value))
    } else {
      result.value = null
    }

    return result
  }

  function _sqrt (unit) {
    return _pow(unit, options.type.conv(0.5, unit.value))
  }

  /**
     * Returns an array of units whose sum is equal to this unit, where each unit in the array is taken from the supplied string array.
     * @param {string[]} units A string array of units to split this unit into.
     * @returns {Unit[]} An array of units
     */
  function _split (unit, units) {
    if (!options.type.conv._IS_UNITMATH_DEFAULT_FUNCTION && (options.type.round._IS_UNITMATH_DEFAULT_FUNCTION || options.type.trunc._IS_UNITMATH_DEFAULT_FUNCTION)) {
      throw new Error(`When using custom types, split requires a type.round and a type.trunc function`)
    }
    let x = _clone(unit)
    const result = []
    for (let i = 0; i < units.length; i++) {
      // Convert x to the requested unit

      x = _to(x, _convertParamToUnit(units[i]))
      if (i === units.length - 1) break

      // Check to see if x.value is nearly equal to an integer,
      // since trunc can incorrectly round down if there is round-off error
      const xRounded = options.type.round(x.value)
      let xFixed
      const isNearlyEqual = options.type.eq(xRounded, x.value)
      if (isNearlyEqual) {
        xFixed = xRounded
      } else {
        xFixed = options.type.trunc(x.value)
      }

      const y = new Unit(xFixed, units[i].toString())
      result.push(y)
      x = _sub(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.
    let testSum = options.type.conv(0, unit.value)
    for (let i = 0; i < result.length; i++) {
      testSum = options.type.add(testSum, normalize(result[i].units, result[i].value, options.type))
    }
    if (options.type.eq(testSum, normalize(unit.units, unit.value, options.type))) {
      x.value = options.type.conv(0, unit.value)
    }

    result.push(x)

    return result
  }

  function _abs (unit) {
    const result = _clone(unit)
    result.value = denormalize(result.units, options.type.abs(normalize(result.units, result.value, options.type)), options.type)
    return result
  }

  /**
   * Private function _to
   * @param {Unit} unit The unit to convert.
   * @param {Unit} valuelessUnit The valueless unit to convert it to.
   */
  function _to (unit, valuelessUnit) {
    let result
    const value = unit.value === null ? 1 : unit.value
    if (!unit.equalQuantity(valuelessUnit)) {
      throw new TypeError(`Cannot convert ${unit.toString()} to ${valuelessUnit}: dimensions do not match`)
    }
    if (valuelessUnit.value !== null) {
      throw new Error(`Cannot convert ${unit.toString()}: target unit must be valueless`)
    }
    result = _clone(valuelessUnit)
    result.value = denormalize(result.units, normalize(unit.units, value, options.type), options.type)
    result.fixed = true // Don't auto simplify
    return result
  }

  /**
   * Private function _choosePrefix
   * @param {Unit} unit The unit to choose the best prefix for.
   * @returns {Unit} A new unit that contains the "best" prefix, or, if no better prefix was found, returns the same unit unchanged.
   */
  function _choosePrefix (unit, opts) {
    let result = _clone(unit)
    let piece = result.units[0] // TODO: Someday this might choose the most "dominant" unit, or something, to prefix, rather than the first unit

    if (unit.units.length !== 1) {
      // TODO: Support for compound units
      return unit
    }
    if (unit.value === null) {
      // Unit does not have a value
      return unit
    }
    if (Math.abs(piece.power - Math.round(piece.power)) >= 1e-14) {
      // TODO: Support for non-integer powers
      return unit
    }
    if (Math.abs(piece.power) < 1e-14) {
      // Unit has power of 0, so prefix will have no effect
      return unit
    }
    if (opts.type.lt(opts.type.abs(unit.value), opts.type.conv(1e-50, unit.value))) {
      // Unit is too small for the prefix to matter
      return unit
    }
    if (opts.type.le(opts.type.abs(unit.value), opts.prefixMax) && opts.type.ge(opts.type.abs(unit.value), opts.prefixMin)) {
      // Unit's value is already acceptable
      return unit
    }

    function calcValue (prefix) {
      return opts.type.div(
        unit.value,
        opts.type.pow(
          options.type.div(
            options.type.conv(piece.unit.prefixes[prefix], unit.value),
            options.type.conv(piece.unit.prefixes[piece.prefix], unit.value)
          ),
          options.type.conv(piece.power, unit.value)
        )
      )
    }

    // TODO: Test this for negative numbers. Are we doing type.abs everywhere we need to be?
    let unitValue = opts.type.abs(unit.value)
    // console.log(`unitValue = ${unitValue}`)
    function calcScore (prefix) {
      let thisValue = opts.type.abs(calcValue(prefix))
      // console.log(`Calculating score for ${prefix}; thisValue = ${thisValue}`)
      if (opts.type.lt(thisValue, opts.prefixMin)) {
        // prefix makes the value too small
        // console.log(`Prefix makes thisValue too small`)
        return opts.type.div(options.type.conv(opts.prefixMin, unitValue), thisValue)
      }
      if (opts.type.gt(thisValue, opts.prefixMax)) {
        // prefix makes the value too large
        // console.log(`Prefix makes thisValue too large`)
        return opts.type.div(thisValue, options.type.conv(opts.prefixMax, unitValue))
      }

      // The prefix is in range, but return a score that says how close it is to the original value.
      if (opts.type.le(thisValue, unitValue)) {
        // console.log(`thisValue <= unitValue, score = ${-thisValue / unitValue} (${1-thisValue/unitValue})`)

        // return opts.type.mul(opts.type.div(thisValue, unitValue), opts.type.conv(-1, unitValue))
        return opts.type.sub(opts.type.conv(1, unitValue), opts.type.div(thisValue, unitValue))
      } else {
        // console.log(`thisValue > unitValue, score = ${-unitValue / thisValue} (${1-unitValue/thisValue})`)
        // return opts.type.mul(opts.type.div(unitValue, thisValue), opts.type.conv(-1, unitValue))
        return opts.type.sub(opts.type.conv(1, unitValue), opts.type.div(unitValue, thisValue))
      }
    }

    // We should be able to do this in one pass. Start on one end of the array, as determined by searchDirection, and search until 1) the prefix results in a value within the acceptable range, 2) or the values start getting worse.
    // Find the index to begin searching. This might be tricky because the unit could have a prefix that is *not* common.
    let bestPrefix = piece.prefix
    let bestScore = calcScore(bestPrefix)
    // console.log(`The score was ${bestScore}`)

    let prefixes
    if (opts.prefixesToChooseFrom === 'all') {
      prefixes = Object.keys(piece.unit.prefixes)
    } else if (opts.prefixesToChooseFrom === 'common') {
      prefixes = piece.unit.commonPrefixes
    }

    if (!prefixes) {
      // Unit does not have any prefixes for formatting
      return unit
    }

    for (let i = 0; i < prefixes.length; i++) {
      // What would the value of the unit be if this prefix were applied?
      let thisPrefix = prefixes[i]
      let thisScore = calcScore(thisPrefix)
      // console.log(`The score was ${thisScore}`)

      if (opts.type.lt(thisScore, bestScore)) {
        bestScore = thisScore
        bestPrefix = thisPrefix
      }
    }

    piece.prefix = bestPrefix
    result.value = denormalize(result.units, normalize(unit.units, unit.value, opts.type), opts.type)

    Object.freeze(result)
    return result
  }

  /**
   * Private function _toSI
   * @param {Unit} unit The unit to convert to SI.
   */
  function _toSI (unit) {
    const result = _clone(unit)

    const 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 (let i = 0; i < unitStore.defs.baseQuantities.length; i++) {
      const baseDim = unitStore.defs.baseQuantities[i]
      if (Math.abs(result.dimension[i] || 0) > 1e-12) {
        if (unitStore.defs.unitSystems['si'].hasOwnProperty(baseDim)) {
          proposedUnitList.push({
            unit: unitStore.defs.unitSystems['si'][baseDim].unit,
            prefix: unitStore.defs.unitSystems['si'][baseDim].prefix,
            power: result.dimension[i]
          })
        } else {
          throw new Error(`Cannot express unit '${unit.format()}' in SI units. System 'si' does not contain a unit for base quantity '${baseDim}'`)
        }
      }
    }

    // Replace this unit list with the proposed list
    result.units = proposedUnitList
    if (unit.value !== null) { result.value = denormalize(result.units, normalize(unit.units, unit.value, options.type), options.type) }
    result.fixed = true // Don't auto simplify
    return result
  }

  /** Private function _setValue
   * @param {Unit} unit The unit to set the value of
   * @param {string | number | custom} value The value to set
   * @returns {Unit} A new unit with the given value
   */
  function _setValue (unit, value) {
    let result = _clone(unit)
    if (typeof value === 'undefined' || value === null) {
      result.value = null
    } else {
      result.value = options.type.conv(value)
    }
    return result
  }

  /**
   * Get a string representation of the units of this Unit, without the value.
   * @return {string}
   */
  function _formatUnits (unit, opts) {
    let strNum = ''
    let strDen = ''
    let nNum = 0
    let nDen = 0

    for (let i = 0; i < unit.units.length; i++) {
      if (unit.units[i].power > 0) {
        nNum++
        strNum += ' ' + unit.units[i].prefix + unit.units[i].unit.name
        if (Math.abs(unit.units[i].power - 1.0) > 1e-15) {
          strNum += '^' + unit.units[i].power
        }
      } else if (unit.units[i].power < 0) {
        nDen++
      }
    }

    if (nDen > 0) {
      for (let i = 0; i < unit.units.length; i++) {
        if (unit.units[i].power < 0) {
          if (nNum > 0) {
            strDen += ' ' + unit.units[i].prefix + unit.units[i].unit.name
            if (Math.abs(unit.units[i].power + 1.0) > 1e-15) {
              strDen += '^' + (-unit.units[i].power)
            }
          } else {
            strDen += ' ' + unit.units[i].prefix + unit.units[i].unit.name
            strDen += '^' + (unit.units[i].power)
          }
        }
      }
    }

    // Remove leading " "
    strNum = strNum.substr(1)
    strDen = strDen.substr(1)

    if (opts.parentheses) {
      // 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 + ')'
      }
    }

    let str = strNum
    if (nNum > 0 && nDen > 0) {
      str += ' / '
    }
    str += strDen

    return str
  }

  let unitStore = createUnitStore(options)

  // Public functions available on the unitmath namespace

  /**
   * Create a clone of the this unit factory function, but with the specified options.
   * @param {Object} options Configuration options, in addition to those existing, to set on the new instance.
   * @returns {Function} A new instance of the unit factory function with the specified options; or, if no arguments are given, the current options.
   */
  unitmath.config = function config (newOptions) {
    if (typeof (newOptions) === 'undefined') {
      return options
    }

    // Shallow copy existing config
    let retOptions = Object.assign({}, options)

    // Shallow copy new options (except unit and type)
    for (let key in newOptions) {
      if (key !== 'unit' && key !== 'type') {
        retOptions[key] = newOptions[key]
      }
    }

    // Shallow copy unit and type
    retOptions.definitions = Object.assign({}, options.definitions, newOptions.definitions)
    retOptions.type = Object.assign({}, options.type, newOptions.type)
    return _config(retOptions)
  }

  unitmath.definitions = function definitions () {
    return unitStore.originalDefinitions
  }

  /* Alternate API syntax */

  /**
   * Adds two units. Both units' dimensions must be equal.
   * @param {Unit|string|number} a The first unit to add. If a string or number is supplied, it will be converted to a unit.
   * @param {Unit|string|number} b The second unit to add. If a string or number is supplied, it will be converted to a unit.
   * @returns {Unit} The result of the addition a + b.
   */
  unitmath.add = function add (a, b) {
    return _convertParamToUnit(a).add(b)
  }

  /**
   * Subtracts two units. Both units' dimensions must be equal.
   * @param {Unit|string|number} a The first unit. If a string or number is supplied, it will be converted to a unit.
   * @param {Unit|string|number} b The second unit. If a string or number is supplied, it will be converted to a unit.
   * @returns {Unit} The result of the subtract a - b.
   */
  unitmath.sub = function sub (a, b) {
    return _convertParamToUnit(a).sub(b)
  }

  /**
   * Multiplies two units. Both units' dimensions must be equal.
   * @param {Unit|string|number} a The first unit. If a string or number is supplied, it will be converted to a unit.
   * @param {Unit|string|number} b The second unit. If a string or number is supplied, it will be converted to a unit.
   * @returns {Unit} The result a * b.
   */
  unitmath.mul = function mul (a, b) {
    return _convertParamToUnit(a).mul(b)
  }

  /**
   * Divides two units. Both units' dimensions must be equal.
   * @param {Unit|string|number} a The first unit. If a string or number is supplied, it will be converted to a unit.
   * @param {Unit|string|number} b The second unit. If a string or number is supplied, it will be converted to a unit.
   * @returns {Unit} The result a / b.
   */
  unitmath.div = function div (a, b) {
    return _convertParamToUnit(a).div(b)
  }
  /**
   * Raises a unit to a power.
   * @param {Unit|string|number} a The unit.
   * @param {number} b The power.
   * @returns {Unit} The result of raising the unit a to the power b.
   */
  unitmath.pow = function pow (a, b) {
    return _convertParamToUnit(a).pow(b)
  }

  /**
  * Takes the square root of a unit.
  * @param {Unit|string|number} a The unit.
  * @returns {Unit} The square root of the unit a.
  */
  unitmath.sqrt = function sqrt (a) {
    return _convertParamToUnit(a).sqrt()
  }

  /**
   * Returns the absolute value of a unit.
   * @param {Unit|string|number} a The unit.
   * @returns {Unit} The absolute value of the unit a.
   */
  unitmath.abs = function abs (a) {
    return _convertParamToUnit(a).abs()
  }

  /**
  * Convert a unit.
  * @param {Unit|string|number} unit The unit to convert.
  * @param {Unit|string} valuelessUnit The valueless unit to convert the first unit to.
  * @returns {Unit} The result of converting the unit.
  */
  unitmath.to = function to (unit, valuelessUnit) {
    return _convertParamToUnit(unit).to(valuelessUnit)
  }

  /**
  * Convert a unit to SI.
  * @param {Unit|string|number} unit The unit to convert.
  * @returns {Unit} The result of converting the unit to SI.
  */
  unitmath.toSI = function toSI (unit) {
    return _convertParamToUnit(unit).toSI()
  }

  unitmath.exists = unitStore.exists

  // TODO: This is used only for testing, could there be another way rather than exposing it on the public namespace?
  unitmath._unitStore = unitStore

  Object.freeze(unitmath)

  return unitmath
}

// Define default arithmetic functions
let defaults = {}
defaults.add = (a, b) => a + b
defaults.sub = (a, b) => a - b
defaults.mul = (a, b) => a * b
defaults.div = (a, b) => a / b
defaults.pow = (a, b) => Math.pow(a, b)
defaults.abs = (a) => Math.abs(a)
defaults.eq = (a, b) => (a === b) || Math.abs(a - b) / Math.abs(a + b) < 1e-15
defaults.lt = (a, b) => a < b
defaults.gt = (a, b) => a > b
defaults.le = (a, b) => a <= b
defaults.ge = (a, b) => a >= b
defaults.round = (a) => Math.round(a)
defaults.trunc = (a) => Math.trunc(a)
defaults.conv = (a) => typeof a === 'string' ? parseFloat(a) : a
defaults.clone = (a) => a
defaults.format = (a) => a.toString()

// These are mostly to help warn the user if they forgot to override one or more of the default functions
for (const key in defaults) {
  defaults[key]._IS_UNITMATH_DEFAULT_FUNCTION = true
}

// TODO: setting to say whether to format using only the common prefixes or all prefixes

let defaultOptions = {
  parentheses: false,
  precision: 15,
  prefix: 'auto',
  prefixMin: 0.1,
  prefixMax: 1000,
  prefixesToChooseFrom: 'common',
  simplify: 'auto',
  simplifyThreshold: 2,
  system: 'auto',
  subsystem: 'auto',
  definitions: {
    skipBuiltIns: false,
    units: {},
    prefixes: {},
    baseQuantities: [],
    quantities: {},
    unitSystems: {}
  },
  type: defaults
}

let firstUnit = _config(defaultOptions, {})

export default firstUnit


<|start_filename|>index.js<|end_filename|>

const esmRequire = require('esm')(module)
module.exports = esmRequire('./src/Unit.js').default


<|start_filename|>rollup.config.js<|end_filename|>
import babel from 'rollup-plugin-babel'
import resolve from 'rollup-plugin-node-resolve'
import commonjs from 'rollup-plugin-commonjs'
import { terser } from 'rollup-plugin-terser'

const teserOptions = {
  compress: {
    pure_getters: true,
    unsafe: true,
    unsafe_comps: true
  }
}

const babelOptions = {
  presets: [
    ['@babel/preset-env', {
      useBuiltIns: 'usage',
      debug: true,
      corejs: 3
    }]
  ],
  ignore: [
    'node_modules'
  ]
}

const name = 'UnitMath'
const input = 'src/Unit.js'

export default [
  // UMD build
  {
    input,
    output: {
      name,
      file: 'dist/UnitMath.js',
      format: 'umd'
    },
    plugins: [babel(babelOptions),
      resolve(),
      commonjs()]
  },
  // minified UMD build
  {
    input,
    output: {
      file: 'dist/UnitMath.min.js',
      format: 'umd',
      indent: false,
      name
    },
    plugins: [
      babel(babelOptions),
      resolve(),
      commonjs(),
      terser(teserOptions)
    ]
  },
  // minified UMD build
  {
    input,
    output: {
      file: 'dist/UnitMath.min2.js',
      format: 'umd',
      indent: false,
      name
    },
    plugins: [
      terser(teserOptions)
    ]
  },
  // es build
  {
    input,
    output: {
      file: 'es/UnitMath.js',
      format: 'es'
    }
  },
  // minified es build
  {
    input,
    output: {
      file: 'es/UnitMath.min.js',
      format: 'es',
      indent: false
    },
    plugins: [ terser(teserOptions) ]
  }
]


<|start_filename|>src/UnitStore.js<|end_filename|>
import createParser from './Parser.js'
import { normalize } from './utils.js'
import * as builtIns from './BuiltIns.js'

/**
 * Creates a new unit store.
 * @param {Object} options
 */
export default function createUnitStore (options) {
  /* Units are defined by these objects:
   * defs.prefixes
   * defs.baseQuantities
   * defs.quantities
   * defs.units
   * defs.unitSystems
   */

  // Steps:
  // Import the BUILT_INS
  // Object.assign BUILT_INS, options.extras

  // TODO: Should we deep freeze the built-ins to prevent modification of the built-in units?

  const skip = options.definitions.skipBuiltIns

  // There are five objects that make of the definition
  const definitionKeys = ['units', 'prefixes', 'unitSystems', 'baseQuantities', 'quantities']

  // These will contain the built-in units extended with the user's definitions
  const originalDefinitions = {}

  // These will contain copies we can mutate without affecting the originals
  const defs = {}

  // TODO: Don't iterate over definitionKeys any more, it's just too complicated and each key has its own special rules
  definitionKeys.forEach(key => {
    if (Array.isArray(builtIns[key])) {
      originalDefinitions[key] = builtIns[key].concat(options.definitions[key])
      defs[key] = originalDefinitions[key].slice()
    } else {
      if (key === 'unitSystems') {
        // We would like to extend unitSystem two levels deep
        originalDefinitions[key] = Object.assign({}, skip ? {} : builtIns[key])
        for (let subKey in options.definitions[key]) {
          originalDefinitions[key][subKey] = Object.assign({}, originalDefinitions[key][subKey] || {}, options.definitions[key][subKey])
        }
      } else {
        originalDefinitions[key] = Object.assign({}, skip ? {} : builtIns[key], options.definitions[key])
      }

      // Omit units because there is additional processing we need to do before we add them to defs
      if (key !== 'units') {
        defs[key] = Object.assign({}, originalDefinitions[key])
      }
    }
  })

  /**
   * A unit system is a set of dimensionally independent base units plus a set of derived units, formed by multiplication and division of the base units, that are by convention used with the unit system.
   */

  // Add additional unit systems here.

  /* All of the prefixes, base quantities, quantities, units, and unit systems have now been defined.
   *
   * We will perform the following processing steps to prepare the UnitStore for use:
   *
   * - For each QUANTITY, parse its value and replace it with a dimension array, where each index of the array corresponds to the base quantity's index in defs.baseQuantities, and the value of each element is the power (exponent) of that base in the dimension.
   *
   * - Initialize the parser with an empty set of units.
   *
   * - Loop through the units. If the unit has a `quantity` property, initialize that unit with the quantity's dimension, and the given value property. If the unit does not, then parse the unit's value property (which is either a string or an two-element array) using the parser, and create the dimension and value from the resulting Unit. Create the unit with the name, dimension, value, offset, prefixes, and commonPrefixes properties. Convert the prefixes from a string to the associated object from the defs.prefixes object.
   *
   * - Some units will fail to be parsed if the defs.units object keys are not enumerated in the optimal order. Repeat the loop until all units have been converted.
   *
   * - Verify that each unit's commonPrefixes are contained in prefixes.
   *
   * - Loop through the defs.unitSystems and converts the strings into unit/prefix pairs.
   *
   * - Add the unit system names to each unit (as an array) for reverse lookup.
   *
   * - Clone units that have aliases. Shallow copies are acceptable since the resulting defs.units object will be deep-immutable.
   *
*/

  // Sort base quantities and check for duplicates
  let sortedBases = defs.baseQuantities.slice()
  sortedBases.sort()
  for (let i = 0; i < sortedBases.length; i++) {
    if (sortedBases[i] === sortedBases[i + 1]) {
      throw new Error(`Duplicate base quantity: ${sortedBases[i]}`)
    }
  }

  // For each key in defs.quantities, replace the string value with a dimension array, where each index of the array corresponds to the base quantity's index in defs.baseQuantities, and the value of each element is the power (exponent) of that base in the dimension.
  // The value must be a string, which is a space-delimited list of base quantities, each optionally raised to a power.
  // Example:
  // Before: VELOCITY: 'LENGTH TIME^-1'
  // After: VELOCITY: [1, 0, -1, 0, 0, 0, 0, 0, 0, 0]

  for (let quant in defs.quantities) {
    const dimArr = defs.baseQuantities.map(() => 0)
    let bases = defs.quantities[quant].split(' ')
    bases.forEach(base => {
      if (base === '') return
      let parts = base.split('^')
      let power = 1
      let idx = defs.baseQuantities.indexOf(parts[0])
      if (idx < 0) {
        throw new Error(`Error processing quantity ${quant}: base quantity ${parts[0]} not found`)
      }
      if (parts[1]) {
        power = parseFloat(parts[1])
        if (isNaN(power)) {
          throw new Error(`Error processing quantity ${quant}: could not determine value of the exponent in string ${base}`)
        }
      }
      dimArr[idx] = power
    })
    defs.quantities[quant] = dimArr
  }

  // Also add the base quantities into defs.quantities, for completeness
  defs.baseQuantities.forEach((base, idx) => {
    const dimArr = defs.baseQuantities.map(() => 0)
    dimArr[idx] = 1
    defs.quantities[base] = dimArr
  })

  // Prevent modification of the quantities
  for (const quant in defs.quantities) { Object.freeze(defs.quantities[quant]) }

  // Empty out the set of units.
  defs.units = {}

  // Create a parser configured for these options, and also supply it with the findUnit function and the number of base quantities.
  const parser = createParser(options, findUnit, defs.baseQuantities.length)

  // Loop through the units. If the unit has a `quantity` property, initialize that unit with the quantity's dimension, and the given value property. If the unit does not, then parse the unit's value property (which is either a string or an two-element array) using the parser, and create the dimension and value from the resulting Unit. Create the unit with the name, dimension, value, offset, prefixes, and commonPrefixes properties. Convert the prefixes from a string to the associated object from the defs.prefixes object.

  while (true) {
    let unitsAdded = 0
    let unitsSkipped = []
    let reasonsSkipped = []
    for (const unitDefKey in originalDefinitions.units) {
      if (defs.units.hasOwnProperty(unitDefKey)) continue

      const unitDef = originalDefinitions.units[unitDefKey]

      if (!unitDef) continue

      const containsUnknownPrefix = unitDef.prefixes && !defs.prefixes.hasOwnProperty(unitDef.prefixes)
      if (containsUnknownPrefix) {
        throw new Error(`Unknown prefixes '${unitDef.prefixes}' for unit '${unitDefKey}'`)
      }

      let unitValue; let unitDimension; let skipThisUnit = false

      if (unitDef.quantity) {
        // Defining the unit based on a quantity.
        if (!defs.quantities.hasOwnProperty(unitDef.quantity)) {
          throw new Error(`Unknown quantity specified for unit ${unitDefKey}: ${unitDef.quantity}`)
        }
        unitValue = unitDef.value
        unitDimension = defs.quantities[unitDef.quantity]
      } else {
        // Defining the unit based on other units.
        let parsed
        try {
          if (unitDef.hasOwnProperty('value')) {
            if (typeof unitDef.value === 'string') {
              parsed = parser(unitDef.value)
            } else if (Array.isArray(unitDef.value) && unitDef.value.length === 2) {
              parsed = parser(unitDef.value[1])
              parsed.value = options.type.conv(unitDef.value[0])
            } else {
              throw new TypeError(`Unit definition for '${unitDefKey}' must be a string, or it must be an object with a value property where the value is a string or a two-element array.`)
            }
          } else if (typeof unitDef === 'string') {
            parsed = parser(unitDef)
          } else {
            throw new TypeError(`Unit definition for '${unitDefKey}' must be a string, or it must be an object with a value property where the value is a string or a two-element array.`)
          }
          unitValue = normalize(parsed.units, parsed.value, options.type)
          unitDimension = Object.freeze(parsed.dimension)
        } catch (ex) {
          if (/Unit.*not found/.test(ex.toString())) {
            unitsSkipped.push(unitDefKey)
            reasonsSkipped.push(ex.toString())
            skipThisUnit = true
          } else {
            throw ex
          }
        }
      }

      if (!skipThisUnit) {
        // Add this units and its aliases (they are all the same except for the name)
        let unitAndAliases = [unitDefKey].concat(unitDef.aliases || [])
        unitAndAliases.forEach(newUnitName => {
          if (defs.units.hasOwnProperty(newUnitName)) {
            throw new Error(`Alias '${newUnitName}' would override an existing unit`)
          }
          if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(newUnitName) && newUnitName !== '') {
            throw new SyntaxError(`Unit name contains non-alphanumeric characters or begins with a number: '${newUnitName}'`)
          }
          const newUnit = {
            name: newUnitName,
            value: unitValue,
            offset: unitDef.offset || 0,
            dimension: unitDimension,
            prefixes: defs.prefixes[unitDef.prefixes] || { '': 1 },
            commonPrefixes: unitDef.commonPrefixes, // Default should be undefined
            systems: []
          }
          Object.freeze(newUnit)
          defs.units[newUnitName] = newUnit
          unitsAdded++
        })
      }
    }

    // console.log(`Added ${unitsAdded} units and skipped: ${unitsSkipped.join(', ')}`)
    if (unitsSkipped.length === 0) break
    else if (unitsAdded === 0) {
      throw new Error(`Could not create the following units: ${unitsSkipped.join(', ')}. Reasons follow: ${reasonsSkipped.join(' ')}`)
    }
  }

  // Check to make sure config options has selected a unit system that exists.
  if (options.system !== 'auto') {
    if (!defs.unitSystems.hasOwnProperty(options.system)) {
      throw new Error(`Unknown unit system ${options.system}. Available systems are: auto, ${Object.keys(defs.unitSystems).join(', ')} `)
    }
  }

  // Convert unit systems from strings to unit/prefix pairs
  for (let sysKey in defs.unitSystems) {
    // Clone the individual system before mutating its properties
    defs.unitSystems[sysKey] = Object.assign({}, defs.unitSystems[sysKey])
    let sys = defs.unitSystems[sysKey]
    for (let quantKey in sys) {
      if (!defs.quantities.hasOwnProperty(quantKey)) {
        throw new Error(`Unit system ${sysKey} mentions quantity ${quantKey}, which does not exist`)
      }
      let unitPrefixPair = findUnit(sys[quantKey])
      if (unitPrefixPair) {
        // Check to make this unit is consistent with this quantity in the unit system
        for (let i = 0; i < defs.quantities[quantKey].length; i++) {
          if (defs.quantities[quantKey][i] !== unitPrefixPair.unit.dimension[i]) {
            throw new Error(`In system '${sysKey}', quantity '${quantKey}' is inconsistent with unit '${sys[quantKey]}'`)
          }
        }

        sys[quantKey] = unitPrefixPair

        // Add the system's name to the unit (for reverse lookup) so we can infer unit systems just by inspecting the individual units
        unitPrefixPair.unit.systems.push(sysKey)
      } else {
        throw new Error(`Unknown unit '${sys[quantKey]}' for quantity '${quantKey}' in unit system '${sysKey}'`)
      }
    }
  }

  // Final setup for units
  for (let key in defs.units) {
    const unit = defs.units[key]
    // Check that each commonPrefix is in prefixes
    if (unit.commonPrefixes) {
      for (let i = 0; i < unit.commonPrefixes.length; i++) {
        let s = unit.commonPrefixes[i]
        if (!unit.prefixes.hasOwnProperty(s)) {
          throw new Error(`In unit ${unit.name}, common prefix ${s} was not found among the allowable prefixes`)
        }
      }
    }
  }

  /**
   * Tests whether the given string exists as a known unit. The unit may have a prefix.
   * @param {string} singleUnitString The name of the unit, with optional prefix.
   */
  function exists (singleUnitString) {
    return findUnit(singleUnitString) !== null
  }

  /**
   * Find a unit from a string
   * @param {string} unitString              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
   */
  function findUnit (unitString) {
    if (typeof unitString !== 'string') {
      throw new TypeError(`parameter must be a string (${unitString} given)`)
    }
    // 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 (defs.units.hasOwnProperty(unitString)) {
      const unit = defs.units[unitString]
      return {
        unit,
        prefix: ''
      }
    }

    for (const name in defs.units) {
      if (defs.units.hasOwnProperty(name)) {
        if (unitString.substring(unitString.length - name.length, unitString.length) === name) {
          const unit = defs.units[name]
          const prefixLen = (unitString.length - name.length)
          const prefix = unitString.substring(0, prefixLen)
          if (unit.prefixes.hasOwnProperty(prefix)) {
            // store unit, prefix, and value
            // console.log(`findUnit(${unitString}): { unit.name: ${unit.name}, prefix: ${prefix} }`)
            return {
              unit,
              prefix
            }
          }
        }
      }
    }

    return null
  }

  Object.freeze(defs.prefixes)
  Object.freeze(defs.baseQuantities)
  Object.freeze(defs.quantities)
  Object.freeze(defs.unitSystems)
  Object.freeze(defs.units)

  return { originalDefinitions, defs, exists, findUnit, parser }
}
