Full Code of gentooboontoo/js-quantities for AI

master 7116174b9995 cached
42 files
571.5 KB
169.3k tokens
275 symbols
1 requests
Download .txt
Showing preview only (596K chars total). Download the full file or copy to clipboard to get everything.
Repository: gentooboontoo/js-quantities
Branch: master
Commit: 7116174b9995
Files: 42
Total size: 571.5 KB

Directory structure:
gitextract_9tyqrl1_/

├── .eslintrc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── RELEASE
├── SpecRunner.html
├── bench/
│   ├── index.html
│   └── main.js
├── bin/
│   ├── bench.rb
│   ├── prepare-release.sh
│   └── release.sh
├── build/
│   ├── quantities.js
│   └── quantities.mjs
├── lib/
│   ├── JSLitmus.js
│   ├── jasmine-3.4.0/
│   │   ├── boot.js
│   │   ├── jasmine-html.js
│   │   ├── jasmine.css
│   │   └── jasmine.js
│   └── require.js
├── package.json
├── spec/
│   ├── blns.json
│   ├── quantitiesSpec.js
│   └── support/
│       └── jasmine.json
└── src/
    ├── quantities/
    │   ├── comparators.js
    │   ├── constructor.js
    │   ├── conversion.js
    │   ├── definitions.js
    │   ├── error.js
    │   ├── format.js
    │   ├── global-api.js
    │   ├── kind.js
    │   ├── nested-map.js
    │   ├── operators.js
    │   ├── parse.js
    │   ├── predicates.js
    │   ├── signature.js
    │   ├── temperature.js
    │   └── utils.js
    └── quantities.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .eslintrc
================================================
{
  "env": {
    "browser": true,
    "es6": true
  },
  "globals": {
    "define": true,
    "module": true
  },
  parserOptions: {
    "sourceType": "module"
  },
  "rules": {
    "brace-style": [
      "error",
      "stroustrup",
      {
        "allowSingleLine": true
      }
    ],
    "camelcase": [
      "error",
      {
        "properties": "never"
      }
    ],
    "curly": [
      "error",
      "all"
    ],
    "eqeqeq": "error",
    "eol-last": "error",
    "guard-for-in": "error",
    "indent": [
      "error",
      2,
      {
        "SwitchCase": 1
      }
    ],
    "keyword-spacing": [
      "error",
      {}
    ],
    "new-cap": 0,
    "no-implicit-coercion": [
      "error",
      {
        "boolean": false,
        "string": true,
        "number": false
      }
    ],
    "no-bitwise": "error",
    "no-mixed-spaces-and-tabs": "error",
    "no-multiple-empty-lines": "error",
    "no-trailing-spaces": "error",
    "no-undef": "error",
    "no-unused-vars": "error",
    "no-with": "error",
    "quotes": [
      "error",
      "double"
    ],
    "semi": [
      "error",
      "always"
    ],
    "space-before-function-paren": [
      "error",
      "never"
    ],
    "space-infix-ops": "error",
    "space-unary-ops": [
      "error",
      {
        "nonwords": false,
        "words": true
      }
    ],
    "strict": [
      "error",
      "function"
    ],
    "valid-jsdoc": "error",
    "wrap-iife": "error"
  }
}


================================================
FILE: .gitignore
================================================
.envrc
shell.nix
src/.committed-quantities.js
*.swp
node_modules/
tags


================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
  - "node"
  - "lts/*"
script: make test


================================================
FILE: CHANGELOG.md
================================================
Unreleased
----------

1.8.0 / 2023-08-21
------------------

### Added

* Add "Joules" alias
* Add some parts-per notation units
* Add metric ton symbol t
* Add electronvolt
* Add arcminute and arcsecond

### Fixed

* Fix wrong quantity name for molar_concentration units

1.7.6 / 2020-12-06
------------------

### Added

* Unit of acceleration `Gal`

### Fixed

* Missing aliases for barrel-related units (#101)

1.7.5 / 2020-04-05
------------------

* Update development dependencies
* Use ESLint as linter

1.7.4 / 2019-06-26
------------------

* Add Imperial Gallons, and Barrels (US Beer, Imperial Beer, Oil)
* Add support for imperial version of fluid ounces and pints

1.7.3 / 2018-12-15
------------------

* Fix some inconsistent resulting units when multiplying or dividing
quantities (#94)

1.7.2 / 2018-03-31
------------------

* Remove `module` from package definition (Fix #91)

1.7.1 / 2018-03-25
------------------

* Add missing unit aliases

1.7.0 / 2017-08-23
------------------

* Fix JS allocation failure for unrealistically large exponents
* Add volt-ampere, volt-ampere-reactive and data mile definitions
* Add redshift alias

1.6.6 / 2017-02-08
------------------

* Report incompatible units in error message

1.6.5 / 2017-01-20
------------------

* Fix main entrypoint in bower.json

1.6.4 / 2017-01-02
------------------

* Fix infinite regex check

1.6.3 / 2016-09-22
------------------

* Add missing kinds
* Fix conversion from percentage to unitless quantity
* Fix capacitance definition
* Exclude `farad` from base units
* Rename `mass concentration` to `density`

1.6.2 / 2016-04-13
------------------

* Accept blank string as initialization value

1.6.1 / 2016-03-27
------------------

* Fix definition of square foot
* Add tablespoon `tbsp` alias
* Add `Qty.version` property

1.6.0 / 2015-12-26
------------------

* Add `Qty.getUnits` to return available units of a well-known kind
* Add `Qty.getAliases` to return every alias of a specific unit
* Allow to initialize a quantity with scalar and units as separate arguments
* Rename `memory` kind to `information`
* Add `information_rate` kind
* Accept Wh and Ah as units
* Fix hang when using water height pressure units
* Add plural for fluid ounce
* Fix `amu` and `dalton` definitions
* Add `tb` as tablespoon alias
* Add plural for `information` units
* Minor fixes or improvements

1.5.0 / 2014-12-08
------------------

* Add `Qty.getKinds` returning known kinds of units
* Add µ symbol as micro prefix alias
* Add Ω symbol as ohm unit alias
* Minor internal improvements and fixes

1.4.2 / 2014-09-09
------------------

* Fix plural for radian and add missing ones for time units
* Add "gon" international standard symbol as gradian alias
* Fix units of force
* Allow whitespaces between sign and scalar and do not accept sign
  without scalar

1.4.1 / 2014-05-14
------------------

* Use a little more robust to test string type and factorize it

1.4.0 / 2014-04-10
------------------

* Directly convert array of values when using swiftConverter
* Add support for bushel units

1.3.0 / 2014-03-05
------------------

* Add Qty#format and accept custom formatters
* Allow to call Qty() without new to create Qty instances (Qty could be used
  both as a constructor or as a factory)
* Qty#toString only supports to be passed output units as single parameter.
  Former parameters are now deprecated but still supported to not introduce
  a breaking change
* Add mc as alternate definition for prefix "micro"
* Throw error with mmm as unit
* Add rounding optimization

1.2.0 / 2013-12-17
------------------

* Throw QtyError instead of plain string
* Cache conversion results from Qty#to instead of Qty#toString
* Fix point and pica unit definitions
* Fix error when initializing a quantity with an empty string

1.1.2 / 2013-11-04
------------------

* Fix rounding issue when converting 1 cm3 to mm3
* Do some code cleaning (it should not break public API)

1.1.1 / 2013-10-01
------------------

* Fix Qty#toPrec() returning wrong result with some precision

1.1.0 / 2013-09-20
------------------

* Add array converting method
* Major speedup by means of some optimizations and refactoring

1.0.0 / 2013-07-30
------------------

* First stable version


================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright © 2006-2007 Kevin C. Olbrich
Copyright © 2010-2016 LIM SAS (http://lim.eu) - Julien Sanchez

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
SPEC_DIR := spec
SPECFILES := $(shell find $(SPEC_DIR) -iname '*.js')
SOURCE_DIR := src
SOURCES := $(shell find $(SOURCE_DIR) -iname '*.js')
LINTEDFILES := $(SOURCES) $(SPECFILES)
BUILD_DIR := build
ESM_FILE := $(BUILD_DIR)/quantities.mjs
UMD_FILE := $(BUILD_DIR)/quantities.js
DIST_FILES := $(ESM_FILE) $(UMD_FILE)
BANNER := $(shell cat LICENSE)

ROLLUP := npx rollup
JASMINE := npx jasmine
ESLINT := npx eslint

build: $(DIST_FILES)

$(UMD_FILE): $(SOURCES)
	$(ROLLUP) --file=$(UMD_FILE) \
            --format=umd \
            --name=Qty \
            --banner="`echo '/*'; cat LICENSE; echo '*/'`" \
            src/quantities.js

$(ESM_FILE): $(SOURCES)
	$(ROLLUP) --file=$(ESM_FILE) \
            --format=es \
            --banner="`echo '/*'; cat LICENSE; echo '*/'`" \
            src/quantities.js

test: lint build
	$(JASMINE)

lint: $(LINTEDFILES)
	$(ESLINT) $(LINTEDFILES)

bench:
	bin/bench.rb

clean:
	rm -f $(DIST_FILES)

.PHONY: bench build clean lint test


================================================
FILE: README.md
================================================
# JS-quantities

[![Build Status](https://travis-ci.org/gentooboontoo/js-quantities.png)](https://travis-ci.org/gentooboontoo/js-quantities)

JS-quantities is originally a JavaScript port of Kevin Olbrich's library Ruby
Units (http://github.com/olbrich/ruby-units).

The library aims to simplify the handling of units for scientific calculations
involving quantities.

JS-quantities is built as UMD and ES modules and can be used with Node.js
and browsers. It has **no dependencies**.

## Installation

Install with `npm install js-quantities` or download latest release v1.8.0 as:

* [UMD module](https://raw.github.com/gentooboontoo/js-quantities/v1.8.0/build/quantities.js)
* [ES module](https://raw.github.com/gentooboontoo/js-quantities/v1.8.0/build/quantities.mjs)

## Usage

### Node.js

```javascript
// As CommonJS module
const Qty = require('js-quantities');

// As ES module
import Qty from 'js-quantities';
```

### Browsers

* UMD module could be included as is:

```html
<script src='quantities.js'></script>
```

In this case, it will define a global variable `Qty`.

* With an AMD loader like [Require.JS](http://requirejs.org/):

```javascript
define(['quantities'], function(Qty) {
  ...
});
```

* As ES module:

```html
<script type="module">
  import Qty from "./path/to/quantities.mjs";
  // ...
</script>
```

## Synopsis

### Creation

Instances of quantities are made by means of `Qty()` method. `Qty` can both be
used as a constructor (with new) or as a factory (without new):

```javascript
qty = new Qty('23 ft'); // constructor
qty = Qty('23 ft'); // factory
```

`Qty` constructor accepts strings, numbers and `Qty` instances as
initializing values.

If scalars and their respective units are available programmatically, the
two argument signature may be useful:

```javascript
qty = new Qty(124, 'cm'); // => 1.24 meter
qty = Qty(124, 'cm'); // => 1.24 meter
```

For the sake of simplicity, one will use the factory way below but using
`new Qty()` is equivalent.

```javascript
qty = Qty('1m'); // => 1 meter
qty = Qty('m'); // =>  1 meter (scalar defaults to 1)

qty = Qty('1 N*m');
qty = Qty('1 N m'); // * is optional

qty = Qty('1 m/s');

qty = Qty('1 m^2/s^2');
qty = Qty('1 m^2 s^-2'); // negative powers
qty = Qty('1 m2 s-2'); // ^ is optional

qty = Qty('1 m^2 kg^2 J^2/s^2 A');

qty = Qty('1.5'); // unitless quantity
qty = Qty(1.5); // number as initializing value

qty = Qty('1 attoparsec/microfortnight');

qtyCopy = Qty(qty); // quantity could be copied when used as
                    // initializing value
```

`Qty.parse` utility method is also provided to parse and create
quantities from strings. Unlike the constructor, it will return `null`
instead of throwing an error when parsing an invalid quantity.

```javascript
Qty.parse('1 m'); // => 1 meter
Qty.parse('foo') // => null
```

### Available well-known kinds

```javascript
Qty.getKinds(); // => Array of names of every well-known kind of units
```

### Available units of a particular kind

```javascript
Qty.getUnits('currency'); // => [ 'dollar', 'cents' ]
// Or all alphabetically sorted
Qty.getUnits(); // => [ 'acre','Ah','ampere','AMU','angstrom']
```

### Alternative names of a unit

```javascript
Qty.getAliases('m'); // => [ 'm', 'meter', 'meters', 'metre', 'metres' ]
```

### Quantity compatibility, kind and various queries

```javascript
qty1.isCompatible(qty2); // => true or false

qty.kind(); // => 'length', 'area', etc...

qty.isUnitless(); // => true or false
qty.isBase(); // => true if quantity is represented with base units
```

### Conversion

```javascript
qty.toBase(); // converts to SI units (10 cm => 0.1 m) (new instance)

qty.toFloat(); // returns scalar of unitless quantity
               // (otherwise throws error)

qty.to('m'); // converts quantity to meter if compatible
             // or throws an error (new instance)
qty1.to(qty2); // converts quantity to same unit of qty2 if compatible
               // or throws an error (new instance)

qty.inverse(); // converts quantity to its inverse
               // ('100 m/s' => '.01 s/m')
// Inverses can be used, but there is no special checking to
// rename the units
Qty('10ohm').inverse() // '.1/ohm'
                       // (not '.1S', although they are equivalent)
// however, the 'to' command will convert between inverses also
Qty('10ohm').to('S') // '.1S'
```

`Qty.swiftConverter()` is a fast way to efficiently convert large array of
Number values. It configures a function accepting a value or an array of Number
values to convert.

```javascript
var convert = Qty.swiftConverter('m/h', 'ft/s'); // Configures converter

// Converting single value
var converted = convert(2500); // => 2.278..

// Converting large array of values
var convertedSerie = convert([2500, 5000, ...]); // => [2.278.., 4.556.., ...]
```

The main drawback of this conversion method is that it does not take care of
rounding issues.

### Comparison

```javascript
qty1.eq(qty2); // => true if both quantities are equal (1m == 100cm => true)
qty1.same(qty2); // => true if both quantities are same (1m == 100cm => false)
qty1.lt(qty2); // => true if qty1 is stricty less than qty2
qty1.lte(qty2); // => true if qty1 is less than or equal to qty2
qty1.gt(qty2); // => true if qty1 is stricty greater than qty2
qty1.gte(qty2); // => true if qty1 is greater than or equal to qty2

qty1.compareTo(qty2); // => -1 if qty1 < qty2,
                      // => 0 if qty1 == qty2,
                      // => 1 if qty1 > qty2
```

### Operators

* add(other): Add. other can be string or quantity. other should be unit compatible.
* sub(other): Substract. other can be string or quantity. other should be unit compatible.
* mul(other): Multiply. other can be string, number or quantity.
* div(other): Divide. other can be string, number or quantity.

### Rounding

`Qty#toPrec(precision)` : returns the nearest multiple of quantity passed as
precision.

```javascript
var qty = Qty('5.17 ft');
qty.toPrec('ft'); // => 5 ft
qty.toPrec('0.5 ft'); // => 5 ft
qty.toPrec('0.25 ft'); // => 5.25 ft
qty.toPrec('0.1 ft'); // => 5.2 ft
qty.toPrec('0.05 ft'); // => 5.15 ft
qty.toPrec('0.01 ft'); // => 5.17 ft
qty.toPrec('0.00001 ft'); // => 5.17 ft
qty.toPrec('2 ft'); // => 6 ft
qty.toPrec('2'); // => 6 ft

var qty = Qty('6.3782 m');
qty.toPrec('dm'); // => 6.4 m
qty.toPrec('cm'); // => 6.38 m
qty.toPrec('mm'); // => 6.378 m
qty.toPrec('5 cm'); // => 6.4 m
qty.toPrec('10 m'); // => 10 m
qty.toPrec(0.1); // => 6.3 m

var qty = Qty('1.146 MPa');
qty.toPrec('0.1 bar'); // => 1.15 MPa
```

### Formatting quantities

`Qty#toString` returns a string using the canonical form of the quantity (that
is it could be seamlessly reparsed by `Qty`).

```javascript
var qty = Qty('1.146 MPa');
qty.toString(); // => '1.146 MPa'
```

As a shorthand, units could be passed to `Qty#toString` and is equivalent to
successively call `Qty#to` then `Qty#toString`.

```javascript
var qty = Qty('1.146 MPa');
qty.toString('bar'); // => '11.46 bar'
qty.to('bar').toString(); // => '11.46 bar'
```

`Qty#toString` could also be used with any method from `Qty` to make some sort
of formatting. For instance, one could use `Qty#toPrec` to fix the maximum
number of decimals:

```javascript
var qty = Qty('1.146 MPa');
qty.toPrec(0.1).toString(); // => '1.1 MPa'
qty.to('bar').toPrec(0.1).toString(); // => '11.5 bar'
```

For advanced formatting needs as localization, specific rounding or any other
custom customization, quantities can be transformed into strings through
`Qty#format` according to optional target units and formatter. If target units
are specified, the quantity is converted into them before formatting.

Such a string is not intended to be reparsed to construct a new instance of
`Qty` (unlike output of `Qty#toString`).

If no formatter is specified, quantities are formatted according to default
js-quantities' formatter and is equivalent to `Qty#toString`.

```javascript
var qty = Qty('1.1234 m');
qty.format(); // same units, default formatter => '1.234 m'
qty.format('cm'); // converted to 'cm', default formatter => '123.45 cm'
```

`Qty#format` could delegates formatting to a custom formatter if required. A
formatter is a callback function accepting scalar and units as parameters and
returning a formatted string representing the quantity.

```javascript
var configurableRoundingFormatter = function(maxDecimals) {
  return function(scalar, units) {
    var pow = Math.pow(10, maxDecimals);
    var rounded = Math.round(scalar * pow) / pow;

    return rounded + ' ' + units;
  };
};

var qty = Qty('1.1234 m');

// same units, custom formatter => '1.12 m'
qty.format(configurableRoundingFormatter(2));

// convert to 'cm', custom formatter => '123.4 cm'
qty.format('cm', configurableRoundingFormatter(1));
```

Custom formatter can be configured globally by setting `Qty.formatter`.

```javascript
Qty.formatter = configurableRoundingFormatter(2);
var qty = Qty('1.1234 m');
qty.format(); // same units, current default formatter => '1.12 m'
```

### Temperatures

Like ruby-units, JS-quantities makes a distinction between a temperature (which
technically is a property) and degrees of temperature (which temperatures are
measured in).

Temperature units (i.e., 'tempK') can be converted back and forth, and will take
into account the differences in the zero points of the various scales.
Differential temperature (e.g., '100 degC') units behave like most other units.

```javascript
Qty('37 tempC').to('tempF') // => 98.6 tempF
```

JS-quantities will throw an error if you attempt to create a temperature unit
that would fall below absolute zero.

Unit math on temperatures is fairly limited.

```javascript
Qty('100 tempC').add('10 degC')  // 110 tempC
Qty('100 tempC').sub('10 degC')  // 90 tempC
Qty('100 tempC').add('50 tempC') // throws error
Qty('100 tempC').sub('50 tempC') // 50 degC
Qty('50 tempC').sub('100 tempC') // -50 degC
Qty('100 tempC').mul(scalar)     // 100*scalar tempC
Qty('100 tempC').div(scalar)     // 100/scalar tempC
Qty('100 tempC').mul(qty)        // throws error
Qty('100 tempC').div(qty)        // throws error
Qty('100 tempC*unit')            // throws error
Qty('100 tempC/unit')            // throws error
Qty('100 unit/tempC')            // throws error
Qty('100 tempC').inverse()       // throws error
```

```javascript
Qty('100 tempC').to('degC') // => 100 degC
```

This conversion references the 0 point on the scale of the temperature unit

```javascript
Qty('100 degC').to('tempC') // => -173.15 tempC
```

These conversions are always interpreted as being relative to absolute zero.
Conversions are probably better done like this...

```javascript
Qty('0 tempC').add('100 degC') // => 100 tempC
```

### Errors

Every error thrown by JS-quantities is an instance of `Qty.Error`.

```javascript
try {
  // code triggering an error inside JS-quantities
}
catch(e) {
  if(e instanceof Qty.Error) {
    // ...
  }
  else {
    // ...
  }
}
```

## Tests

Tests are implemented with Jasmine (https://github.com/pivotal/jasmine).
You could use both HTML and jasmine-node runners.

To execute specs through HTML runner, just open `SpecRunner.html` file in a
browser to execute them.

To execute specs through `jasmine-node`, launch:

    make test

### Performance regression test

There is a small benchmarking HTML page to spot performance regression between
currently checked-out quantities.js and any committed version.
Just execute:

    make bench

then open http://0.0.0.0:3000/bench

Checked-out version is benchmarked against HEAD by default but it could be changed by passing
any commit SHA on the command line. Port (default 3000) is also configurable.

    make bench COMMIT=e0c7fc468 PORT=5000

## TypeScript type declarations

A TypeScript declaration file is published on
[DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/js-quantities).

It could be installed with `npm install @types/js-quantities`.

## Contribute

Feedback and contributions are welcomed.

Pull requests must pass tests and linting. Please make sure that `make test`
and `make lint` return no errors before submitting.


================================================
FILE: RELEASE
================================================
1.8.0


================================================
FILE: SpecRunner.html
================================================
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Jasmine Spec Runner</title>

  <link rel="shortcut icon" type="image/png" href="lib/jasmine-3.4.0/jasmine_favicon.png">
  <link rel="stylesheet" href="lib/jasmine-3.4.0/jasmine.css">

  <script src="lib/jasmine-3.4.0/jasmine.js"></script>
  <script src="lib/jasmine-3.4.0/jasmine-html.js"></script>
  <script src="lib/jasmine-3.4.0/boot.js"></script>

  <script type="module">
    import Qty from "./src/quantities.js";
    window.Qty = Qty;
  </script>

  <script type="text/javascript" src="spec/quantitiesSpec.js"></script>
</head>

<body>
</body>
</html>


================================================
FILE: bench/index.html
================================================
<!DOCTYPE html>
<html>
<head>
  <title>JS-quantities Benchmarks</title>
  <script src="../lib/require.js"></script>
  <!-- JSLitmus should not be asynchronously loaded
  since it uses document.write() -->
  <script src="../lib/JSLitmus.js"></script>
  <script src="main.js"></script>
</head>
<body>
</body>
</html>



================================================
FILE: bench/main.js
================================================
/* global requirejs, JSLitmus */
requirejs.config({
  baseUrl: "../lib",
  paths: {
    "src": "../src"
  }
});

require([
  "src/.committed-quantities",
  "src/quantities"
], function(previousQty, currentQty) {
  "use strict";

  function bench(name, func) {
    JSLitmus.test(name, func);
  }

  function benchCurrent(name, func) {
    bench(name + " (current)", func(currentQty));
  }

  function benchPrevious(name, func) {
    bench(name + " (GIT)", func(previousQty));
  }

  function compare(name, func) {
    benchPrevious(name, func);
    benchCurrent(name, func);
  }

  compare("Unit conversion", function(Qty) {
    return function() {
      new Qty("2500 m/h").to("ft/s");
    };
  });

  compare("Array conversion", function(Qty) {
    var convert = Qty.swiftConverter("m/h", "ft/s");
    var values = [];
    for(var i = 0; i < 1000; i++) {
      values.push(i);
    }

    return function() {
      return convert(values);
    };
  });
});



================================================
FILE: bin/bench.rb
================================================
#!/usr/bin/env ruby

Dir.chdir(File.join(File.dirname(__FILE__), '..'))

port = ENV['PORT'] || '3000'
commit = ENV['COMMIT'] || 'HEAD'

pid = fork do
  exec "adsf -p #{port}"
end

`git show #{commit}:src/quantities.js > src/.committed-quantities.js`

puts "Bench server launched: please open http://0.0.0.0:#{port}/bench"

begin
  Process.wait(pid)
rescue Interrupt => e
  puts "Killing web server..."
  Process.kill(9, pid)
end

File.unlink 'src/.committed-quantities.js'


================================================
FILE: bin/prepare-release.sh
================================================
#!/bin/bash

if [[ $# != 1 ]]; then
  echo "Usage: $0 VERSION"
  exit 1
fi

VERSION="$1"
LOG_ENTRY="${VERSION} / $(date +%Y-%m-%d)\n\
------------------\n\
\n\
* ...\n"

sed -i "1i $LOG_ENTRY" CHANGELOG.md
sed -i "s/\(\"version\"\):.*$/\1: \"${VERSION}\",/" package.json
sed -i "s/\(v[[:digit:]]\+\.[[:digit:]]\+\.[[:digit:]]\+\)/v${VERSION}/g" README.md
sed -i "s/Qty.version =.*$/Qty.version = \"${VERSION}\";/" src/quantities.js
echo "${VERSION}" > RELEASE
make


================================================
FILE: bin/release.sh
================================================
#!/bin/bash

set -e

VERSION=$(cat RELEASE)
TAG=v${VERSION}

git add CHANGELOG.md README.md package.json \
        RELEASE src/quantities.js build/quantities.js build/quantities.mjs
git commit -m "Release ${TAG}"
git tag "${TAG}"
git push github master "${TAG}"

git checkout gh-pages
git show master:README.md > index.md
git add index.md
git commit -m "Update index.md"
git push github gh-pages

git checkout master
npm publish


================================================
FILE: build/quantities.js
================================================
/*
The MIT License (MIT)
Copyright © 2006-2007 Kevin C. Olbrich
Copyright © 2010-2016 LIM SAS (http://lim.eu) - Julien Sanchez

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Qty = factory());
})(this, (function () { 'use strict';

  /**
   * Tests if a value is a string
   *
   * @param {*} value - Value to test
   *
   * @returns {boolean} true if value is a string, false otherwise
   */
  function isString(value) {
    return typeof value === "string" || value instanceof String;
  }

  /*
   * Prefer stricter Number.isFinite if currently supported.
   * To be dropped when ES6 is finalized. Obsolete browsers will
   * have to use ES6 polyfills.
   */
  var isFiniteImpl = Number.isFinite || window.isFinite;
  /**
   * Tests if a value is a number
   *
   * @param {*} value - Value to test
   *
   * @returns {boolean} true if value is a number, false otherwise
   */
  function isNumber(value) {
    // Number.isFinite allows not to consider NaN or '1' as numbers
    return isFiniteImpl(value);
  }

  /*
   * Identity function
   */
  function identity(value) {
    return value;
  }

  /**
   * Returns unique strings from list
   *
   * @param {string[]} strings - array of strings
   *
   *
   * @returns {string[]} a new array of strings without duplicates
   */
  function uniq(strings) {
    var seen = {};
    return strings.filter(function(item) {
      return seen.hasOwnProperty(item) ? false : (seen[item] = true);
    });
  }

  function compareArray(array1, array2) {
    if (array2.length !== array1.length) {
      return false;
    }
    for (var i = 0; i < array1.length; i++) {
      if (array2[i].compareArray) {
        if (!array2[i].compareArray(array1[i])) {
          return false;
        }
      }
      if (array2[i] !== array1[i]) {
        return false;
      }
    }
    return true;
  }

  function assign(target, properties) {
    Object.keys(properties).forEach(function(key) {
      target[key] = properties[key];
    });
  }

  /**
   * Safely multiplies numbers while avoiding floating errors
   * like 0.1 * 0.1 => 0.010000000000000002
   *
   * @param {...number} numbers - numbers to multiply
   *
   * @returns {number} result
   */
  function mulSafe() {
    var result = 1, decimals = 0;
    for (var i = 0; i < arguments.length; i++) {
      var arg = arguments[i];
      decimals = decimals + getFractional(arg);
      result *= arg;
    }

    return decimals !== 0 ? round(result, decimals) : result;
  }

  /**
   * Safely divides two numbers while avoiding floating errors
   * like 0.3 / 0.05 => 5.999999999999999
   *
   * @returns {number} result
   * @param {number} num Numerator
   * @param {number} den Denominator
   */
  function divSafe(num, den) {
    if (den === 0) {
      throw new Error("Divide by zero");
    }

    var factor = Math.pow(10, getFractional(den));
    var invDen = factor / (factor * den);

    return mulSafe(num, invDen);
  }

  /**
   * Rounds value at the specified number of decimals
   *
   * @param {number} val - value to round
   * @param {number} decimals - number of decimals
   *
   * @returns {number} rounded number
   */
  function round(val, decimals) {
    return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals);
  }

  function getFractional(num) {
    // Check for NaNs or Infinities
    if (!isFinite(num)) {
      return 0;
    }

    // Faster than parsing strings
    // http://jsperf.com/count-decimals/2
    var count = 0;
    while (num % 1 !== 0) {
      num *= 10;
      count++;
    }
    return count;
  }

  /**
   * Custom error type definition
   * @constructor
   */
  function QtyError() {
    var err;
    if (!this) { // Allows to instantiate QtyError without new()
      err = Object.create(QtyError.prototype);
      QtyError.apply(err, arguments);
      return err;
    }
    err = Error.apply(this, arguments);
    this.name = "QtyError";
    this.message = err.message;
    this.stack = err.stack;
  }
  QtyError.prototype = Object.create(Error.prototype, {constructor: { value: QtyError }});

  /*
   * Throws incompatible units error
   * @param {string} left - units
   * @param {string} right - units incompatible with first argument
   * @throws "Incompatible units" error
   */
  function throwIncompatibleUnits(left, right) {
    throw new QtyError("Incompatible units: " + left + " and " + right);
  }

  var UNITS = {
    /* prefixes */
    "<googol>" : [["googol"], 1e100, "prefix"],
    "<kibi>"  :  [["Ki","Kibi","kibi"], Math.pow(2,10), "prefix"],
    "<mebi>"  :  [["Mi","Mebi","mebi"], Math.pow(2,20), "prefix"],
    "<gibi>"  :  [["Gi","Gibi","gibi"], Math.pow(2,30), "prefix"],
    "<tebi>"  :  [["Ti","Tebi","tebi"], Math.pow(2,40), "prefix"],
    "<pebi>"  :  [["Pi","Pebi","pebi"], Math.pow(2,50), "prefix"],
    "<exi>"   :  [["Ei","Exi","exi"], Math.pow(2,60), "prefix"],
    "<zebi>"  :  [["Zi","Zebi","zebi"], Math.pow(2,70), "prefix"],
    "<yebi>"  :  [["Yi","Yebi","yebi"], Math.pow(2,80), "prefix"],
    "<yotta>" :  [["Y","Yotta","yotta"], 1e24, "prefix"],
    "<zetta>" :  [["Z","Zetta","zetta"], 1e21, "prefix"],
    "<exa>"   :  [["E","Exa","exa"], 1e18, "prefix"],
    "<peta>"  :  [["P","Peta","peta"], 1e15, "prefix"],
    "<tera>"  :  [["T","Tera","tera"], 1e12, "prefix"],
    "<giga>"  :  [["G","Giga","giga"], 1e9, "prefix"],
    "<mega>"  :  [["M","Mega","mega"], 1e6, "prefix"],
    "<kilo>"  :  [["k","kilo"], 1e3, "prefix"],
    "<hecto>" :  [["h","Hecto","hecto"], 1e2, "prefix"],
    "<deca>"  :  [["da","Deca","deca","deka"], 1e1, "prefix"],
    "<deci>"  :  [["d","Deci","deci"], 1e-1, "prefix"],
    "<centi>"  : [["c","Centi","centi"], 1e-2, "prefix"],
    "<milli>" :  [["m","Milli","milli"], 1e-3, "prefix"],
    "<micro>"  : [
      ["u","\u03BC"/*µ as greek letter*/,"\u00B5"/*µ as micro sign*/,"Micro","mc","micro"],
      1e-6,
      "prefix"
    ],
    "<nano>"  :  [["n","Nano","nano"], 1e-9, "prefix"],
    "<pico>"  :  [["p","Pico","pico"], 1e-12, "prefix"],
    "<femto>" :  [["f","Femto","femto"], 1e-15, "prefix"],
    "<atto>"  :  [["a","Atto","atto"], 1e-18, "prefix"],
    "<zepto>" :  [["z","Zepto","zepto"], 1e-21, "prefix"],
    "<yocto>" :  [["y","Yocto","yocto"], 1e-24, "prefix"],

    "<1>"     :  [["1", "<1>"], 1, ""],
    /* length units */
    "<meter>" :  [["m","meter","meters","metre","metres"], 1.0, "length", ["<meter>"] ],
    "<inch>"  :  [["in","inch","inches","\""], 0.0254, "length", ["<meter>"]],
    "<foot>"  :  [["ft","foot","feet","'"], 0.3048, "length", ["<meter>"]],
    "<yard>"  :  [["yd","yard","yards"], 0.9144, "length", ["<meter>"]],
    "<mile>"  :  [["mi","mile","miles"], 1609.344, "length", ["<meter>"]],
    "<naut-mile>" : [["nmi","naut-mile"], 1852, "length", ["<meter>"]],
    "<league>":  [["league","leagues"], 4828, "length", ["<meter>"]],
    "<furlong>": [["furlong","furlongs"], 201.2, "length", ["<meter>"]],
    "<rod>"   :  [["rd","rod","rods"], 5.029, "length", ["<meter>"]],
    "<mil>"   :  [["mil","mils"], 0.0000254, "length", ["<meter>"]],
    "<angstrom>"  :[["ang","angstrom","angstroms"], 1e-10, "length", ["<meter>"]],
    "<fathom>" : [["fathom","fathoms"], 1.829, "length", ["<meter>"]],
    "<pica>"  : [["pica","picas"], 0.00423333333, "length", ["<meter>"]],
    "<point>" : [["pt","point","points"], 0.000352777778, "length", ["<meter>"]],
    "<redshift>" : [["z","red-shift", "redshift"], 1.302773e26, "length", ["<meter>"]],
    "<AU>"    : [["AU","astronomical-unit"], 149597900000, "length", ["<meter>"]],
    "<light-second>":[["ls","light-second"], 299792500, "length", ["<meter>"]],
    "<light-minute>":[["lmin","light-minute"], 17987550000, "length", ["<meter>"]],
    "<light-year>" : [["ly","light-year"], 9460528000000000, "length", ["<meter>"]],
    "<parsec>"  : [["pc","parsec","parsecs"], 30856780000000000, "length", ["<meter>"]],
    "<datamile>"  :  [["DM","datamile"], 1828.8, "length", ["<meter>"]],

    /* mass */
    "<kilogram>" : [["kg","kilogram","kilograms"], 1.0, "mass", ["<kilogram>"]],
    "<AMU>" : [["u","AMU","amu"], 1.660538921e-27, "mass", ["<kilogram>"]],
    "<dalton>" : [["Da","Dalton","Daltons","dalton","daltons"], 1.660538921e-27, "mass", ["<kilogram>"]],
    "<slug>" : [["slug","slugs"], 14.5939029, "mass", ["<kilogram>"]],
    "<short-ton>" : [["tn","ton","short-ton"], 907.18474, "mass", ["<kilogram>"]],
    "<metric-ton>":[["t","tonne","metric-ton"], 1000, "mass", ["<kilogram>"]],
    "<carat>" : [["ct","carat","carats"], 0.0002, "mass", ["<kilogram>"]],
    "<pound>" : [["lbs","lb","pound","pounds","#"], 0.45359237, "mass", ["<kilogram>"]],
    "<ounce>" : [["oz","ounce","ounces"], 0.0283495231, "mass", ["<kilogram>"]],
    "<gram>"    :  [["g","gram","grams","gramme","grammes"], 1e-3, "mass", ["<kilogram>"]],
    "<grain>" : [["grain","grains","gr"], 6.479891e-5, "mass", ["<kilogram>"]],
    "<dram>"  : [["dram","drams","dr"], 0.0017718452, "mass",["<kilogram>"]],
    "<stone>" : [["stone","stones","st"],6.35029318, "mass",["<kilogram>"]],

    /* area */
    "<hectare>":[["hectare"], 10000, "area", ["<meter>","<meter>"]],
    "<acre>":[["acre","acres"], 4046.85642, "area", ["<meter>","<meter>"]],
    "<sqft>":[["sqft"], 0.09290304, "area", ["<foot>","<foot>"]],

    /* volume */
    "<liter>" : [["l","L","liter","liters","litre","litres"], 0.001, "volume", ["<meter>","<meter>","<meter>"]],
    "<gallon>":  [["gal","gallon","gallons"], 0.0037854118, "volume", ["<meter>","<meter>","<meter>"]],
    "<gallon-imp>":  [["galimp","gallon-imp","gallons-imp"], 0.0045460900, "volume", ["<meter>","<meter>","<meter>"]],
    "<quart>":  [["qt","quart","quarts"], 0.00094635295, "volume", ["<meter>","<meter>","<meter>"]],
    "<pint>":  [["pt","pint","pints"], 0.000473176475, "volume", ["<meter>","<meter>","<meter>"]],
    "<pint-imp>":  [["ptimp","pint-imp","pints-imp"], 5.6826125e-4, "volume", ["<meter>","<meter>","<meter>"]],
    "<cup>":  [["cu","cup","cups"], 0.000236588238, "volume", ["<meter>","<meter>","<meter>"]],
    "<fluid-ounce>":  [["floz","fluid-ounce","fluid-ounces"], 2.95735297e-5, "volume", ["<meter>","<meter>","<meter>"]],
    "<fluid-ounce-imp>":  [["flozimp", "floz-imp","fluid-ounce-imp","fluid-ounces-imp"], 2.84130625e-5, "volume", ["<meter>","<meter>","<meter>"]],
    "<tablespoon>":  [["tb","tbsp","tbs","tablespoon","tablespoons"], 1.47867648e-5, "volume", ["<meter>","<meter>","<meter>"]],
    "<teaspoon>":  [["tsp","teaspoon","teaspoons"], 4.92892161e-6, "volume", ["<meter>","<meter>","<meter>"]],
    "<bushel>":  [["bu","bsh","bushel","bushels"], 0.035239072, "volume", ["<meter>","<meter>","<meter>"]],
    "<oilbarrel>":  [["bbl","oilbarrel", "oilbarrels", "oil-barrel","oil-barrels"], 0.158987294928, "volume", ["<meter>","<meter>","<meter>"]],
    "<beerbarrel>":  [["bl","bl-us","beerbarrel", "beerbarrels", "beer-barrel","beer-barrels"], 0.1173477658, "volume", ["<meter>","<meter>","<meter>"]],
    "<beerbarrel-imp>":  [["blimp","bl-imp","beerbarrel-imp", "beerbarrels-imp", "beer-barrel-imp","beer-barrels-imp"], 0.16365924, "volume", ["<meter>","<meter>","<meter>"]],

    /* speed */
    "<kph>" : [["kph"], 0.277777778, "speed", ["<meter>"], ["<second>"]],
    "<mph>" : [["mph"], 0.44704, "speed", ["<meter>"], ["<second>"]],
    "<knot>" : [["kt","kn","kts","knot","knots"], 0.514444444, "speed", ["<meter>"], ["<second>"]],
    "<fps>"  : [["fps"], 0.3048, "speed", ["<meter>"], ["<second>"]],

    /* acceleration */
    "<gee>" : [["gee"], 9.80665, "acceleration", ["<meter>"], ["<second>","<second>"]],
    "<Gal>" : [["Gal"], 1e-2, "acceleration", ["<meter>"], ["<second>","<second>"]],

    /* temperature_difference */
    "<kelvin>" : [["degK","kelvin"], 1.0, "temperature", ["<kelvin>"]],
    "<celsius>" : [["degC","celsius","celsius","centigrade"], 1.0, "temperature", ["<kelvin>"]],
    "<fahrenheit>" : [["degF","fahrenheit"], 5 / 9, "temperature", ["<kelvin>"]],
    "<rankine>" : [["degR","rankine"], 5 / 9, "temperature", ["<kelvin>"]],
    "<temp-K>"  : [["tempK","temp-K"], 1.0, "temperature", ["<temp-K>"]],
    "<temp-C>"  : [["tempC","temp-C"], 1.0, "temperature", ["<temp-K>"]],
    "<temp-F>"  : [["tempF","temp-F"], 5 / 9, "temperature", ["<temp-K>"]],
    "<temp-R>"  : [["tempR","temp-R"], 5 / 9, "temperature", ["<temp-K>"]],

    /* time */
    "<second>":  [["s","sec","secs","second","seconds"], 1.0, "time", ["<second>"]],
    "<minute>":  [["min","mins","minute","minutes"], 60.0, "time", ["<second>"]],
    "<hour>":  [["h","hr","hrs","hour","hours"], 3600.0, "time", ["<second>"]],
    "<day>":  [["d","day","days"], 3600 * 24, "time", ["<second>"]],
    "<week>":  [["wk","week","weeks"], 7 * 3600 * 24, "time", ["<second>"]],
    "<fortnight>": [["fortnight","fortnights"], 1209600, "time", ["<second>"]],
    "<year>":  [["y","yr","year","years","annum"], 31556926, "time", ["<second>"]],
    "<decade>":[["decade","decades"], 315569260, "time", ["<second>"]],
    "<century>":[["century","centuries"], 3155692600, "time", ["<second>"]],

    /* pressure */
    "<pascal>" : [["Pa","pascal","Pascal"], 1.0, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<bar>" : [["bar","bars"], 100000, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<mmHg>" : [["mmHg"], 133.322368, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<inHg>" : [["inHg"], 3386.3881472, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<torr>" : [["torr"], 133.322368, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<atm>" : [["atm","ATM","atmosphere","atmospheres"], 101325, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<psi>" : [["psi"], 6894.76, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<cmh2o>" : [["cmH2O","cmh2o"], 98.0638, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
    "<inh2o>" : [["inH2O","inh2o"], 249.082052, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],

    /* viscosity */
    "<poise>"  : [["P","poise"], 0.1, "viscosity", ["<kilogram>"],["<meter>","<second>"] ],
    "<stokes>" : [["St","stokes"], 1e-4, "viscosity", ["<meter>","<meter>"], ["<second>"]],

    /* substance */
    "<mole>"  :  [["mol","mole"], 1.0, "substance", ["<mole>"]],

    /* molar_concentration */
    "<molar>" : [["M","molar"], 1000, "molar_concentration", ["<mole>"], ["<meter>","<meter>","<meter>"]],
    "<wtpercent>"  : [["wt%","wtpercent"], 10, "molar_concentration", ["<kilogram>"], ["<meter>","<meter>","<meter>"]],

    /* activity */
    "<katal>" :  [["kat","katal","Katal"], 1.0, "activity", ["<mole>"], ["<second>"]],
    "<unit>"  :  [["U","enzUnit","unit"], 16.667e-16, "activity", ["<mole>"], ["<second>"]],

    /* capacitance */
    "<farad>" :  [["F","farad","Farad"], 1.0, "capacitance", ["<second>","<second>","<second>","<second>","<ampere>","<ampere>"], ["<meter>", "<meter>", "<kilogram>"]],

    /* charge */
    "<coulomb>" :  [["C","coulomb","Coulomb"], 1.0, "charge", ["<ampere>","<second>"]],
    "<Ah>" :  [["Ah"], 3600, "charge", ["<ampere>","<second>"]],
    "<elementary-charge>" :  [["e"], 1.602176634e-19, "charge", ["<ampere>","<second>"]],

    /* current */
    "<ampere>"  :  [["A","Ampere","ampere","amp","amps"], 1.0, "current", ["<ampere>"]],

    /* conductance */
    "<siemens>" : [["S","Siemens","siemens"], 1.0, "conductance", ["<second>","<second>","<second>","<ampere>","<ampere>"], ["<kilogram>","<meter>","<meter>"]],

    /* inductance */
    "<henry>" :  [["H","Henry","henry"], 1.0, "inductance", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<ampere>","<ampere>"]],

    /* potential */
    "<volt>"  :  [["V","Volt","volt","volts"], 1.0, "potential", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<second>","<ampere>"]],

    /* resistance */
    "<ohm>" :  [
      ["Ohm","ohm","\u03A9"/*Ω as greek letter*/,"\u2126"/*Ω as ohm sign*/],
      1.0,
      "resistance",
      ["<meter>","<meter>","<kilogram>"],["<second>","<second>","<second>","<ampere>","<ampere>"]
    ],
    /* magnetism */
    "<weber>" : [["Wb","weber","webers"], 1.0, "magnetism", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<ampere>"]],
    "<tesla>"  : [["T","tesla","teslas"], 1.0, "magnetism", ["<kilogram>"], ["<second>","<second>","<ampere>"]],
    "<gauss>" : [["G","gauss"], 1e-4, "magnetism",  ["<kilogram>"], ["<second>","<second>","<ampere>"]],
    "<maxwell>" : [["Mx","maxwell","maxwells"], 1e-8, "magnetism", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<ampere>"]],
    "<oersted>"  : [["Oe","oersted","oersteds"], 250.0 / Math.PI, "magnetism", ["<ampere>"], ["<meter>"]],

    /* energy */
    "<joule>" :  [["J","joule","Joule","joules","Joules"], 1.0, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<erg>"   :  [["erg","ergs"], 1e-7, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<btu>"   :  [["BTU","btu","BTUs"], 1055.056, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<calorie>" :  [["cal","calorie","calories"], 4.18400, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<Calorie>" :  [["Cal","Calorie","Calories"], 4184.00, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<therm-US>" : [["th","therm","therms","Therm","therm-US"], 105480400, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<Wh>" : [["Wh"], 3600, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
    "<electronvolt>" : [["eV", "electronvolt", "electronvolts"], 1.602176634E-19, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],

    /* force */
    "<newton>"  : [["N","Newton","newton"], 1.0, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
    "<dyne>"  : [["dyn","dyne"], 1e-5, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
    "<pound-force>"  : [["lbf","pound-force"], 4.448222, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
    "<kilogram-force>"  : [["kgf","kilogram-force", "kilopond", "kp"], 9.80665, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
    "<gram-force>"  : [["gf","gram-force"], 9.80665E-3, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],

    /* frequency */
    "<hertz>" : [["Hz","hertz","Hertz"], 1.0, "frequency", ["<1>"], ["<second>"]],

    /* angle */
    "<radian>" :[["rad","radian","radians"], 1.0, "angle", ["<radian>"]],
    "<degree>" :[["deg","degree","degrees"], Math.PI / 180.0, "angle", ["<radian>"]],
    "<arcminute>" :[["arcmin","arcminute","arcminutes"], Math.PI / 10800.0, "angle", ["<radian>"]],
    "<arcsecond>" :[["arcsec","arcsecond","arcseconds"], Math.PI / 648000.0, "angle", ["<radian>"]],
    "<gradian>"   :[["gon","grad","gradian","grads"], Math.PI / 200.0, "angle", ["<radian>"]],
    "<steradian>"  : [["sr","steradian","steradians"], 1.0, "solid_angle", ["<steradian>"]],

    /* rotation */
    "<rotation>" : [["rotation"], 2.0 * Math.PI, "angle", ["<radian>"]],
    "<rpm>"   :[["rpm"], 2.0 * Math.PI / 60.0, "angular_velocity", ["<radian>"], ["<second>"]],

    /* information */
    "<byte>"  :[["B","byte","bytes"], 1.0, "information", ["<byte>"]],
    "<bit>"  :[["b","bit","bits"], 0.125, "information", ["<byte>"]],

    /* information rate */
    "<Bps>" : [["Bps"], 1.0, "information_rate", ["<byte>"], ["<second>"]],
    "<bps>" : [["bps"], 0.125, "information_rate", ["<byte>"], ["<second>"]],

    /* currency */
    "<dollar>":[["USD","dollar"], 1.0, "currency", ["<dollar>"]],
    "<cents>" :[["cents"], 0.01, "currency", ["<dollar>"]],

    /* luminosity */
    "<candela>" : [["cd","candela"], 1.0, "luminosity", ["<candela>"]],
    "<lumen>" : [["lm","lumen"], 1.0, "luminous_power", ["<candela>","<steradian>"]],
    "<lux>" :[["lux"], 1.0, "illuminance", ["<candela>","<steradian>"], ["<meter>","<meter>"]],

    /* power */
    "<watt>"  : [["W","watt","watts"], 1.0, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],
    "<volt-ampere>"  : [["VA","volt-ampere"], 1.0, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],
    "<volt-ampere-reactive>"  : [["var","Var","VAr","VAR","volt-ampere-reactive"], 1.0, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],
    "<horsepower>"  :  [["hp","horsepower"], 745.699872, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],

    /* radiation */
    "<gray>" : [["Gy","gray","grays"], 1.0, "radiation", ["<meter>","<meter>"], ["<second>","<second>"]],
    "<roentgen>" : [["R","roentgen"], 0.009330, "radiation", ["<meter>","<meter>"], ["<second>","<second>"]],
    "<sievert>" : [["Sv","sievert","sieverts"], 1.0, "radiation", ["<meter>","<meter>"], ["<second>","<second>"]],
    "<becquerel>" : [["Bq","becquerel","becquerels"], 1.0, "radiation", ["<1>"],["<second>"]],
    "<curie>" : [["Ci","curie","curies"], 3.7e10, "radiation", ["<1>"],["<second>"]],

    /* rate */
    "<cpm>" : [["cpm"], 1.0 / 60.0, "rate", ["<count>"],["<second>"]],
    "<dpm>" : [["dpm"], 1.0 / 60.0, "rate", ["<count>"],["<second>"]],
    "<bpm>" : [["bpm"], 1.0 / 60.0, "rate", ["<count>"],["<second>"]],

    /* resolution / typography */
    "<dot>" : [["dot","dots"], 1, "resolution", ["<each>"]],
    "<pixel>" : [["pixel","px"], 1, "resolution", ["<each>"]],
    "<ppi>" : [["ppi"], 1, "resolution", ["<pixel>"], ["<inch>"]],
    "<dpi>" : [["dpi"], 1, "typography", ["<dot>"], ["<inch>"]],

    /* other */
    "<cell>" : [["cells","cell"], 1, "counting", ["<each>"]],
    "<each>" : [["each"], 1.0, "counting", ["<each>"]],
    "<count>" : [["count"], 1.0, "counting", ["<each>"]],
    "<base-pair>"  : [["bp","base-pair"], 1.0, "counting", ["<each>"]],
    "<nucleotide>" : [["nt","nucleotide"], 1.0, "counting", ["<each>"]],
    "<molecule>" : [["molecule","molecules"], 1.0, "counting", ["<1>"]],
    "<dozen>" :  [["doz","dz","dozen"],12.0,"prefix_only", ["<each>"]],
    "<percent>": [["%","percent"], 0.01, "prefix_only", ["<1>"]],
    "<ppm>" :  [["ppm"],1e-6, "prefix_only", ["<1>"]],
    "<ppb>" :  [["ppb"],1e-9, "prefix_only", ["<1>"]],
    "<ppt>" :  [["ppt"],1e-12, "prefix_only", ["<1>"]],
    "<ppq>" :  [["ppq"],1e-15, "prefix_only", ["<1>"]],
    "<gross>" :  [["gr","gross"],144.0, "prefix_only", ["<dozen>","<dozen>"]],
    "<decibel>"  : [["dB","decibel","decibels"], 1.0, "logarithmic", ["<decibel>"]]
  };

  var BASE_UNITS = ["<meter>","<kilogram>","<second>","<mole>", "<ampere>","<radian>","<kelvin>","<temp-K>","<byte>","<dollar>","<candela>","<each>","<steradian>","<decibel>"];

  var UNITY = "<1>";
  var UNITY_ARRAY = [UNITY];

  // Setup

  /**
   * Asserts unit definition is valid
   *
   * @param {string} unitDef - Name of unit to test
   * @param {Object} definition - Definition of unit to test
   *
   * @returns {void}
   * @throws {QtyError} if unit definition is not valid
   */
  function validateUnitDefinition(unitDef, definition) {
    var scalar = definition[1];
    var numerator = definition[3] || [];
    var denominator = definition[4] || [];
    if (!isNumber(scalar)) {
      throw new QtyError(unitDef + ": Invalid unit definition. " +
                         "'scalar' must be a number");
    }

    numerator.forEach(function(unit) {
      if (UNITS[unit] === undefined) {
        throw new QtyError(unitDef + ": Invalid unit definition. " +
                           "Unit " + unit + " in 'numerator' is not recognized");
      }
    });

    denominator.forEach(function(unit) {
      if (UNITS[unit] === undefined) {
        throw new QtyError(unitDef + ": Invalid unit definition. " +
                           "Unit " + unit + " in 'denominator' is not recognized");
      }
    });
  }

  var PREFIX_VALUES = {};
  var PREFIX_MAP = {};
  var UNIT_VALUES = {};
  var UNIT_MAP = {};
  var OUTPUT_MAP = {};
  for (var unitDef in UNITS) {
    if (UNITS.hasOwnProperty(unitDef)) {
      var definition = UNITS[unitDef];
      if (definition[2] === "prefix") {
        PREFIX_VALUES[unitDef] = definition[1];
        for (var i = 0; i < definition[0].length; i++) {
          PREFIX_MAP[definition[0][i]] = unitDef;
        }
      }
      else {
        validateUnitDefinition(unitDef, definition);
        UNIT_VALUES[unitDef] = {
          scalar: definition[1],
          numerator: definition[3],
          denominator: definition[4]
        };
        for (var j = 0; j < definition[0].length; j++) {
          UNIT_MAP[definition[0][j]] = unitDef;
        }
      }
      OUTPUT_MAP[unitDef] = definition[0][0];
    }
  }

  /**
   * Returns a list of available units of kind
   *
   * @param {string} [kind] - kind of units
   * @returns {array} names of units
   * @throws {QtyError} if kind is unknown
   */
  function getUnits(kind) {
    var i;
    var units = [];
    var unitKeys = Object.keys(UNITS);
    if (typeof kind === "undefined") {
      for (i = 0; i < unitKeys.length; i++) {
        if (["", "prefix"].indexOf(UNITS[unitKeys[i]][2]) === -1) {
          units.push(unitKeys[i].substr(1, unitKeys[i].length - 2));
        }
      }
    }
    else if (this.getKinds().indexOf(kind) === -1) {
      throw new QtyError("Kind not recognized");
    }
    else {
      for (i = 0; i < unitKeys.length; i++) {
        if (UNITS[unitKeys[i]][2] === kind) {
          units.push(unitKeys[i].substr(1, unitKeys[i].length - 2));
        }
      }
    }

    return units.sort(function(a, b) {
      if (a.toLowerCase() < b.toLowerCase()) {
        return -1;
      }
      if (a.toLowerCase() > b.toLowerCase()) {
        return 1;
      }
      return 0;
    });
  }

  /**
   * Returns a list of alternative names for a unit
   *
   * @param {string} unitName - name of unit
   * @returns {string[]} aliases for unit
   * @throws {QtyError} if unit is unknown
   */
  function getAliases(unitName) {
    if (!UNIT_MAP[unitName]) {
      throw new QtyError("Unit not recognized");
    }
    return UNITS[UNIT_MAP[unitName]][0];
  }

  var SIGNATURE_VECTOR = ["length", "time", "temperature", "mass", "current", "substance", "luminosity", "currency", "information", "angle"];

  /*
  calculates the unit signature id for use in comparing compatible units and simplification
  the signature is based on a simple classification of units and is based on the following publication

  Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
  21(8), Aug 1995, pp.651-661
  doi://10.1109/32.403789
  http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
  */
  function unitSignature() {
    if (this.signature) {
      return this.signature;
    }
    var vector = unitSignatureVector.call(this);
    for (var i = 0; i < vector.length; i++) {
      vector[i] *= Math.pow(20, i);
    }

    return vector.reduce(
      function(previous, current) {
        return previous + current;
      },
      0
    );
  }

  // calculates the unit signature vector used by unit_signature
  function unitSignatureVector() {
    if (!this.isBase()) {
      return unitSignatureVector.call(this.toBase());
    }

    var vector = new Array(SIGNATURE_VECTOR.length);
    for (var i = 0; i < vector.length; i++) {
      vector[i] = 0;
    }
    var r, n;
    for (var j = 0; j < this.numerator.length; j++) {
      if ((r = UNITS[this.numerator[j]])) {
        n = SIGNATURE_VECTOR.indexOf(r[2]);
        if (n >= 0) {
          vector[n] = vector[n] + 1;
        }
      }
    }

    for (var k = 0; k < this.denominator.length; k++) {
      if ((r = UNITS[this.denominator[k]])) {
        n = SIGNATURE_VECTOR.indexOf(r[2]);
        if (n >= 0) {
          vector[n] = vector[n] - 1;
        }
      }
    }
    return vector;
  }

  var SIGN = "[+-]";
  var INTEGER = "\\d+";
  var SIGNED_INTEGER = SIGN + "?" + INTEGER;
  var FRACTION = "\\." + INTEGER;
  var FLOAT = "(?:" + INTEGER + "(?:" + FRACTION + ")?" + ")" +
              "|" +
              "(?:" + FRACTION + ")";
  var EXPONENT = "[Ee]" + SIGNED_INTEGER;
  var SCI_NUMBER = "(?:" + FLOAT + ")(?:" + EXPONENT + ")?";
  var SIGNED_NUMBER = SIGN + "?\\s*" + SCI_NUMBER;
  var QTY_STRING = "(" + SIGNED_NUMBER + ")?" + "\\s*([^/]*)(?:\/(.+))?";
  var QTY_STRING_REGEX = new RegExp("^" + QTY_STRING + "$");

  var POWER_OP = "\\^|\\*{2}";
  // Allow unit powers representing scalar, length, area, volume; 4 is for some
  // special case representations in SI base units.
  var SAFE_POWER = "[01234]";
  var TOP_REGEX = new RegExp ("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(-?" + SAFE_POWER + "(?![a-zA-Z]))");
  var BOTTOM_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(" + SAFE_POWER + "(?![a-zA-Z]))");

  /* parse a string into a unit object.
   * Typical formats like :
   * "5.6 kg*m/s^2"
   * "5.6 kg*m*s^-2"
   * "5.6 kilogram*meter*second^-2"
   * "2.2 kPa"
   * "37 degC"
   * "1"  -- creates a unitless constant with value 1
   * "GPa"  -- creates a unit with scalar 1 with units 'GPa'
   * 6'4"  -- recognized as 6 feet + 4 inches
   * 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
   */
  function parse(val) {
    if (!isString(val)) {
      val = val.toString();
    }
    val = val.trim();

    var result = QTY_STRING_REGEX.exec(val);
    if (!result) {
      throw new QtyError(val + ": Quantity not recognized");
    }

    var scalarMatch = result[1];
    if (scalarMatch) {
      // Allow whitespaces between sign and scalar for loose parsing
      scalarMatch = scalarMatch.replace(/\s/g, "");
      this.scalar = parseFloat(scalarMatch);
    }
    else {
      this.scalar = 1;
    }
    var top = result[2];
    var bottom = result[3];

    var n, x, nx;
    // TODO DRY me
    while ((result = TOP_REGEX.exec(top))) {
      n = parseFloat(result[2]);
      if (isNaN(n)) {
        // Prevents infinite loops
        throw new QtyError("Unit exponent is not a number");
      }
      // Disallow unrecognized unit even if exponent is 0
      if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) {
        throw new QtyError("Unit not recognized");
      }
      x = result[1] + " ";
      nx = "";
      for (var i = 0; i < Math.abs(n) ; i++) {
        nx += x;
      }
      if (n >= 0) {
        top = top.replace(result[0], nx);
      }
      else {
        bottom = bottom ? bottom + nx : nx;
        top = top.replace(result[0], "");
      }
    }

    while ((result = BOTTOM_REGEX.exec(bottom))) {
      n = parseFloat(result[2]);
      if (isNaN(n)) {
        // Prevents infinite loops
        throw new QtyError("Unit exponent is not a number");
      }
      // Disallow unrecognized unit even if exponent is 0
      if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) {
        throw new QtyError("Unit not recognized");
      }
      x = result[1] + " ";
      nx = "";
      for (var j = 0; j < n ; j++) {
        nx += x;
      }

      bottom = bottom.replace(result[0], nx);
    }

    if (top) {
      this.numerator = parseUnits(top.trim());
    }
    if (bottom) {
      this.denominator = parseUnits(bottom.trim());
    }
  }

  var PREFIX_REGEX = Object.keys(PREFIX_MAP).sort(function(a, b) {
    return b.length - a.length;
  }).join("|");
  var UNIT_REGEX = Object.keys(UNIT_MAP).sort(function(a, b) {
    return b.length - a.length;
  }).join("|");
  /*
   * Minimal boundary regex to support units with Unicode characters
   * \b only works for ASCII
   */
  var BOUNDARY_REGEX = "\\b|$";
  var UNIT_MATCH = "(" + PREFIX_REGEX + ")??(" +
                   UNIT_REGEX +
                   ")(?:" + BOUNDARY_REGEX + ")";
  var UNIT_TEST_REGEX = new RegExp("^\\s*(" + UNIT_MATCH + "[\\s\\*]*)+$");
  var UNIT_MATCH_REGEX = new RegExp(UNIT_MATCH, "g"); // g flag for multiple occurences
  var parsedUnitsCache = {};
  /**
   * Parses and converts units string to normalized unit array.
   * Result is cached to speed up next calls.
   *
   * @param {string} units Units string
   * @returns {string[]} Array of normalized units
   *
   * @example
   * // Returns ["<second>", "<meter>", "<second>"]
   * parseUnits("s m s");
   *
   */
  function parseUnits(units) {
    var cached = parsedUnitsCache[units];
    if (cached) {
      return cached;
    }

    var unitMatch, normalizedUnits = [];

    // Scan
    if (!UNIT_TEST_REGEX.test(units)) {
      throw new QtyError("Unit not recognized");
    }

    while ((unitMatch = UNIT_MATCH_REGEX.exec(units))) {
      normalizedUnits.push(unitMatch.slice(1));
    }

    normalizedUnits = normalizedUnits.map(function(item) {
      return PREFIX_MAP[item[0]] ? [PREFIX_MAP[item[0]], UNIT_MAP[item[1]]] : [UNIT_MAP[item[1]]];
    });

    // Flatten and remove null elements
    normalizedUnits = normalizedUnits.reduce(function(a,b) {
      return a.concat(b);
    }, []);
    normalizedUnits = normalizedUnits.filter(function(item) {
      return item;
    });

    parsedUnitsCache[units] = normalizedUnits;

    return normalizedUnits;
  }

  /**
   * Parses a string as a quantity
   * @param {string} value - quantity as text
   * @throws if value is not a string
   * @returns {Qty|null} Parsed quantity or null if unrecognized
   */
  function globalParse(value) {
    if (!isString(value)) {
      throw new QtyError("Argument should be a string");
    }

    try {
      return this(value);
    }
    catch (e) {
      return null;
    }
  }

  /**
   * Tests if a value is a Qty instance
   *
   * @param {*} value - Value to test
   *
   * @returns {boolean} true if value is a Qty instance, false otherwise
   */
  function isQty(value) {
    return value instanceof Qty;
  }

  function Qty(initValue, initUnits) {
    assertValidConstructorArgs.apply(null, arguments);

    if (!(isQty(this))) {
      return new Qty(initValue, initUnits);
    }

    this.scalar = null;
    this.baseScalar = null;
    this.signature = null;
    this._conversionCache = {};
    this.numerator = UNITY_ARRAY;
    this.denominator = UNITY_ARRAY;

    if (isDefinitionObject(initValue)) {
      this.scalar = initValue.scalar;
      this.numerator = (initValue.numerator && initValue.numerator.length !== 0) ? initValue.numerator : UNITY_ARRAY;
      this.denominator = (initValue.denominator && initValue.denominator.length !== 0) ? initValue.denominator : UNITY_ARRAY;
    }
    else if (initUnits) {
      parse.call(this, initUnits);
      this.scalar = initValue;
    }
    else {
      parse.call(this, initValue);
    }

    // math with temperatures is very limited
    if (this.denominator.join("*").indexOf("temp") >= 0) {
      throw new QtyError("Cannot divide with temperatures");
    }
    if (this.numerator.join("*").indexOf("temp") >= 0) {
      if (this.numerator.length > 1) {
        throw new QtyError("Cannot multiply by temperatures");
      }
      if (!compareArray(this.denominator, UNITY_ARRAY)) {
        throw new QtyError("Cannot divide with temperatures");
      }
    }

    this.initValue = initValue;
    updateBaseScalar.call(this);

    if (this.isTemperature() && this.baseScalar < 0) {
      throw new QtyError("Temperatures must not be less than absolute zero");
    }
  }

  Qty.prototype = {
    // Properly set up constructor
    constructor: Qty,
  };

  /**
   * Asserts constructor arguments are valid
   *
   * @param {*} value - Value to test
   * @param {string} [units] - Optional units when value is passed as a number
   *
   * @returns {void}
   * @throws {QtyError} if constructor arguments are invalid
   */
  function assertValidConstructorArgs(value, units) {
    if (units) {
      if (!(isNumber(value) && isString(units))) {
        throw new QtyError("Only number accepted as initialization value " +
                           "when units are explicitly provided");
      }
    }
    else {
      if (!(isString(value) ||
            isNumber(value) ||
            isQty(value)    ||
            isDefinitionObject(value))) {
        throw new QtyError("Only string, number or quantity accepted as " +
                           "single initialization value");
      }
    }
  }

  /**
   * Tests if a value is a Qty definition object
   *
   * @param {*} value - Value to test
   *
   * @returns {boolean} true if value is a definition object, false otherwise
   */
  function isDefinitionObject(value) {
    return value && typeof value === "object" && value.hasOwnProperty("scalar");
  }

  function updateBaseScalar() {
    if (this.baseScalar) {
      return this.baseScalar;
    }
    if (this.isBase()) {
      this.baseScalar = this.scalar;
      this.signature = unitSignature.call(this);
    }
    else {
      var base = this.toBase();
      this.baseScalar = base.scalar;
      this.signature = base.signature;
    }
  }

  var KINDS = {
    "-312078": "elastance",
    "-312058": "resistance",
    "-312038": "inductance",
    "-152058": "potential",
    "-152040": "magnetism",
    "-152038": "magnetism",
    "-7997": "specific_volume",
    "-79": "snap",
    "-59": "jolt",
    "-39": "acceleration",
    "-38": "radiation",
    "-20": "frequency",
    "-19": "speed",
    "-18": "viscosity",
    "-17": "volumetric_flow",
    "-1": "wavenumber",
    "0": "unitless",
    "1": "length",
    "2": "area",
    "3": "volume",
    "20": "time",
    "400": "temperature",
    "7941": "yank",
    "7942": "power",
    "7959": "pressure",
    "7961": "force",
    "7962": "energy",
    "7979": "viscosity",
    "7981": "momentum",
    "7982": "angular_momentum",
    "7997": "density",
    "7998": "area_density",
    "8000": "mass",
    "152020": "radiation_exposure",
    "159999": "magnetism",
    "160000": "current",
    "160020": "charge",
    "312058": "conductance",
    "312078": "capacitance",
    "3199980": "activity",
    "3199997": "molar_concentration",
    "3200000": "substance",
    "63999998": "illuminance",
    "64000000": "luminous_power",
    "1280000000": "currency",
    "25599999980": "information_rate",
    "25600000000": "information",
    "511999999980": "angular_velocity",
    "512000000000": "angle"
  };

  /**
   * Returns the list of available well-known kinds of units, e.g.
   * "radiation" or "length".
   *
   * @returns {string[]} names of kinds of units
   */
  function getKinds() {
    return uniq(Object.keys(KINDS).map(function(knownSignature) {
      return KINDS[knownSignature];
    }));
  }

  Qty.prototype.kind = function() {
    return KINDS[this.signature.toString()];
  };

  assign(Qty.prototype, {
    isDegrees: function() {
      // signature may not have been calculated yet
      return (this.signature === null || this.signature === 400) &&
        this.numerator.length === 1 &&
        compareArray(this.denominator, UNITY_ARRAY) &&
        (this.numerator[0].match(/<temp-[CFRK]>/) || this.numerator[0].match(/<(kelvin|celsius|rankine|fahrenheit)>/));
    },

    isTemperature: function() {
      return this.isDegrees() && this.numerator[0].match(/<temp-[CFRK]>/);
    }
  });

  function subtractTemperatures(lhs,rhs) {
    var lhsUnits = lhs.units();
    var rhsConverted = rhs.to(lhsUnits);
    var dstDegrees = Qty(getDegreeUnits(lhsUnits));
    return Qty({"scalar": lhs.scalar - rhsConverted.scalar, "numerator": dstDegrees.numerator, "denominator": dstDegrees.denominator});
  }

  function subtractTempDegrees(temp,deg) {
    var tempDegrees = deg.to(getDegreeUnits(temp.units()));
    return Qty({"scalar": temp.scalar - tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator});
  }

  function addTempDegrees(temp,deg) {
    var tempDegrees = deg.to(getDegreeUnits(temp.units()));
    return Qty({"scalar": temp.scalar + tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator});
  }

  function getDegreeUnits(units) {
    if (units === "tempK") {
      return "degK";
    }
    else if (units === "tempC") {
      return "degC";
    }
    else if (units === "tempF") {
      return "degF";
    }
    else if (units === "tempR") {
      return "degR";
    }
    else {
      throw new QtyError("Unknown type for temp conversion from: " + units);
    }
  }

  function toDegrees(src,dst) {
    var srcDegK = toDegK(src);
    var dstUnits = dst.units();
    var dstScalar;

    if (dstUnits === "degK") {
      dstScalar = srcDegK.scalar;
    }
    else if (dstUnits === "degC") {
      dstScalar = srcDegK.scalar ;
    }
    else if (dstUnits === "degF") {
      dstScalar = srcDegK.scalar * 9 / 5;
    }
    else if (dstUnits === "degR") {
      dstScalar = srcDegK.scalar * 9 / 5;
    }
    else {
      throw new QtyError("Unknown type for degree conversion to: " + dstUnits);
    }

    return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator});
  }

  function toDegK(qty) {
    var units = qty.units();
    var q;
    if (units.match(/(deg)[CFRK]/)) {
      q = qty.baseScalar;
    }
    else if (units === "tempK") {
      q = qty.scalar;
    }
    else if (units === "tempC") {
      q = qty.scalar;
    }
    else if (units === "tempF") {
      q = qty.scalar * 5 / 9;
    }
    else if (units === "tempR") {
      q = qty.scalar * 5 / 9;
    }
    else {
      throw new QtyError("Unknown type for temp conversion from: " + units);
    }

    return Qty({"scalar": q, "numerator": ["<kelvin>"], "denominator": UNITY_ARRAY});
  }

  function toTemp(src,dst) {
    var dstUnits = dst.units();
    var dstScalar;

    if (dstUnits === "tempK") {
      dstScalar = src.baseScalar;
    }
    else if (dstUnits === "tempC") {
      dstScalar = src.baseScalar - 273.15;
    }
    else if (dstUnits === "tempF") {
      dstScalar = (src.baseScalar * 9 / 5) - 459.67;
    }
    else if (dstUnits === "tempR") {
      dstScalar = src.baseScalar * 9 / 5;
    }
    else {
      throw new QtyError("Unknown type for temp conversion to: " + dstUnits);
    }

    return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator});
  }

  function toTempK(qty) {
    var units = qty.units();
    var q;
    if (units.match(/(deg)[CFRK]/)) {
      q = qty.baseScalar;
    }
    else if (units === "tempK") {
      q = qty.scalar;
    }
    else if (units === "tempC") {
      q = qty.scalar + 273.15;
    }
    else if (units === "tempF") {
      q = (qty.scalar + 459.67) * 5 / 9;
    }
    else if (units === "tempR") {
      q = qty.scalar * 5 / 9;
    }
    else {
      throw new QtyError("Unknown type for temp conversion from: " + units);
    }

    return Qty({"scalar": q, "numerator": ["<temp-K>"], "denominator": UNITY_ARRAY});
  }

  assign(Qty.prototype, {
    /**
     * Converts to other compatible units.
     * Instance's converted quantities are cached for faster subsequent calls.
     *
     * @param {(string|Qty)} other - Target units as string or retrieved from
     *                               other Qty instance (scalar is ignored)
     *
     * @returns {Qty} New converted Qty instance with target units
     *
     * @throws {QtyError} if target units are incompatible
     *
     * @example
     * var weight = Qty("25 kg");
     * weight.to("lb"); // => Qty("55.11556554621939 lbs");
     * weight.to(Qty("3 g")); // => Qty("25000 g"); // scalar of passed Qty is ignored
     */
    to: function(other) {
      var cached, target;

      if (other === undefined || other === null) {
        return this;
      }

      if (!isString(other)) {
        return this.to(other.units());
      }

      cached = this._conversionCache[other];
      if (cached) {
        return cached;
      }

      // Instantiating target to normalize units
      target = Qty(other);
      if (target.units() === this.units()) {
        return this;
      }

      if (!this.isCompatible(target)) {
        if (this.isInverse(target)) {
          target = this.inverse().to(other);
        }
        else {
          throwIncompatibleUnits(this.units(), target.units());
        }
      }
      else {
        if (target.isTemperature()) {
          target = toTemp(this,target);
        }
        else if (target.isDegrees()) {
          target = toDegrees(this,target);
        }
        else {
          var q = divSafe(this.baseScalar, target.baseScalar);
          target = Qty({"scalar": q, "numerator": target.numerator, "denominator": target.denominator});
        }
      }

      this._conversionCache[other] = target;
      return target;
    },

    // convert to base SI units
    // results of the conversion are cached so subsequent calls to this will be fast
    toBase: function() {
      if (this.isBase()) {
        return this;
      }

      if (this.isTemperature()) {
        return toTempK(this);
      }

      var cached = baseUnitCache[this.units()];
      if (!cached) {
        cached = toBaseUnits(this.numerator,this.denominator);
        baseUnitCache[this.units()] = cached;
      }
      return cached.mul(this.scalar);
    },

    // Converts the unit back to a float if it is unitless.  Otherwise raises an exception
    toFloat: function() {
      if (this.isUnitless()) {
        return this.scalar;
      }
      throw new QtyError("Can't convert to Float unless unitless.  Use Unit#scalar");
    },

    /**
     * Returns the nearest multiple of quantity passed as
     * precision
     *
     * @param {(Qty|string|number)} precQuantity - Quantity, string formated
     *   quantity or number as expected precision
     *
     * @returns {Qty} Nearest multiple of precQuantity
     *
     * @example
     * Qty('5.5 ft').toPrec('2 ft'); // returns 6 ft
     * Qty('0.8 cu').toPrec('0.25 cu'); // returns 0.75 cu
     * Qty('6.3782 m').toPrec('cm'); // returns 6.38 m
     * Qty('1.146 MPa').toPrec('0.1 bar'); // returns 1.15 MPa
     *
     */
    toPrec: function(precQuantity) {
      if (isString(precQuantity)) {
        precQuantity = Qty(precQuantity);
      }
      if (isNumber(precQuantity)) {
        precQuantity = Qty(precQuantity + " " + this.units());
      }

      if (!this.isUnitless()) {
        precQuantity = precQuantity.to(this.units());
      }
      else if (!precQuantity.isUnitless()) {
        throwIncompatibleUnits(this.units(), precQuantity.units());
      }

      if (precQuantity.scalar === 0) {
        throw new QtyError("Divide by zero");
      }

      var precRoundedResult = mulSafe(
        Math.round(this.scalar / precQuantity.scalar),
        precQuantity.scalar
      );

      return Qty(precRoundedResult + this.units());
    }
  });

  /**
   * Configures and returns a fast function to convert
   * Number values from units to others.
   * Useful to efficiently convert large array of values
   * with same units into others with iterative methods.
   * Does not take care of rounding issues.
   *
   * @param {string} srcUnits Units of values to convert
   * @param {string} dstUnits Units to convert to
   *
   * @returns {Function} Converting function accepting Number value
   *   and returning converted value
   *
   * @throws "Incompatible units" if units are incompatible
   *
   * @example
   * // Converting large array of numbers with the same units
   * // into other units
   * var converter = Qty.swiftConverter("m/h", "ft/s");
   * var convertedSerie = largeSerie.map(converter);
   *
   */
  function swiftConverter(srcUnits, dstUnits) {
    var srcQty = Qty(srcUnits);
    var dstQty = Qty(dstUnits);

    if (srcQty.eq(dstQty)) {
      return identity;
    }

    var convert;
    if (!srcQty.isTemperature()) {
      convert = function(value) {
        return value * srcQty.baseScalar / dstQty.baseScalar;
      };
    }
    else {
      convert = function(value) {
        // TODO Not optimized
        return srcQty.mul(value).to(dstQty).scalar;
      };
    }

    return function converter(value) {
      var i, length, result;
      if (!Array.isArray(value)) {
        return convert(value);
      }
      else {
        length = value.length;
        result = [];
        for (i = 0; i < length; i++) {
          result.push(convert(value[i]));
        }
        return result;
      }
    };
  }

  var baseUnitCache = {};

  function toBaseUnits(numerator,denominator) {
    var num = [];
    var den = [];
    var q = 1;
    var unit;
    for (var i = 0; i < numerator.length; i++) {
      unit = numerator[i];
      if (PREFIX_VALUES[unit]) {
        // workaround to fix
        // 0.1 * 0.1 => 0.010000000000000002
        q = mulSafe(q, PREFIX_VALUES[unit]);
      }
      else {
        if (UNIT_VALUES[unit]) {
          q *= UNIT_VALUES[unit].scalar;

          if (UNIT_VALUES[unit].numerator) {
            num.push(UNIT_VALUES[unit].numerator);
          }
          if (UNIT_VALUES[unit].denominator) {
            den.push(UNIT_VALUES[unit].denominator);
          }
        }
      }
    }
    for (var j = 0; j < denominator.length; j++) {
      unit = denominator[j];
      if (PREFIX_VALUES[unit]) {
        q /= PREFIX_VALUES[unit];
      }
      else {
        if (UNIT_VALUES[unit]) {
          q /= UNIT_VALUES[unit].scalar;

          if (UNIT_VALUES[unit].numerator) {
            den.push(UNIT_VALUES[unit].numerator);
          }
          if (UNIT_VALUES[unit].denominator) {
            num.push(UNIT_VALUES[unit].denominator);
          }
        }
      }
    }

    // Flatten
    num = num.reduce(function(a,b) {
      return a.concat(b);
    }, []);
    den = den.reduce(function(a,b) {
      return a.concat(b);
    }, []);

    return Qty({"scalar": q, "numerator": num, "denominator": den});
  }

  Qty.parse = globalParse;

  Qty.getUnits = getUnits;
  Qty.getAliases = getAliases;

  Qty.mulSafe = mulSafe;
  Qty.divSafe = divSafe;

  Qty.getKinds = getKinds;

  Qty.swiftConverter = swiftConverter;

  Qty.Error = QtyError;

  assign(Qty.prototype, {
    // Returns new instance with units of this
    add: function(other) {
      if (isString(other)) {
        other = Qty(other);
      }

      if (!this.isCompatible(other)) {
        throwIncompatibleUnits(this.units(), other.units());
      }

      if (this.isTemperature() && other.isTemperature()) {
        throw new QtyError("Cannot add two temperatures");
      }
      else if (this.isTemperature()) {
        return addTempDegrees(this, other);
      }
      else if (other.isTemperature()) {
        return addTempDegrees(other, this);
      }

      return Qty({"scalar": this.scalar + other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator});
    },

    sub: function(other) {
      if (isString(other)) {
        other = Qty(other);
      }

      if (!this.isCompatible(other)) {
        throwIncompatibleUnits(this.units(), other.units());
      }

      if (this.isTemperature() && other.isTemperature()) {
        return subtractTemperatures(this,other);
      }
      else if (this.isTemperature()) {
        return subtractTempDegrees(this,other);
      }
      else if (other.isTemperature()) {
        throw new QtyError("Cannot subtract a temperature from a differential degree unit");
      }

      return Qty({"scalar": this.scalar - other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator});
    },

    mul: function(other) {
      if (isNumber(other)) {
        return Qty({"scalar": mulSafe(this.scalar, other), "numerator": this.numerator, "denominator": this.denominator});
      }
      else if (isString(other)) {
        other = Qty(other);
      }

      if ((this.isTemperature() || other.isTemperature()) && !(this.isUnitless() || other.isUnitless())) {
        throw new QtyError("Cannot multiply by temperatures");
      }

      // Quantities should be multiplied with same units if compatible, with base units else
      var op1 = this;
      var op2 = other;

      // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den
      // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC"
      if (op1.isCompatible(op2) && op1.signature !== 400) {
        op2 = op2.to(op1);
      }
      var numdenscale = cleanTerms(op1.numerator, op1.denominator, op2.numerator, op2.denominator);

      return Qty({"scalar": mulSafe(op1.scalar, op2.scalar, numdenscale[2]), "numerator": numdenscale[0], "denominator": numdenscale[1]});
    },

    div: function(other) {
      if (isNumber(other)) {
        if (other === 0) {
          throw new QtyError("Divide by zero");
        }
        return Qty({"scalar": this.scalar / other, "numerator": this.numerator, "denominator": this.denominator});
      }
      else if (isString(other)) {
        other = Qty(other);
      }

      if (other.scalar === 0) {
        throw new QtyError("Divide by zero");
      }

      if (other.isTemperature()) {
        throw new QtyError("Cannot divide with temperatures");
      }
      else if (this.isTemperature() && !other.isUnitless()) {
        throw new QtyError("Cannot divide with temperatures");
      }

      // Quantities should be multiplied with same units if compatible, with base units else
      var op1 = this;
      var op2 = other;

      // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den
      // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC"
      if (op1.isCompatible(op2) && op1.signature !== 400) {
        op2 = op2.to(op1);
      }
      var numdenscale = cleanTerms(op1.numerator, op1.denominator, op2.denominator, op2.numerator);

      return Qty({"scalar": mulSafe(op1.scalar, numdenscale[2]) / op2.scalar, "numerator": numdenscale[0], "denominator": numdenscale[1]});
    },

    // Returns a Qty that is the inverse of this Qty,
    inverse: function() {
      if (this.isTemperature()) {
        throw new QtyError("Cannot divide with temperatures");
      }
      if (this.scalar === 0) {
        throw new QtyError("Divide by zero");
      }
      return Qty({"scalar": 1 / this.scalar, "numerator": this.denominator, "denominator": this.numerator});
    }
  });

  function cleanTerms(num1, den1, num2, den2) {
    function notUnity(val) {
      return val !== UNITY;
    }

    num1 = num1.filter(notUnity);
    num2 = num2.filter(notUnity);
    den1 = den1.filter(notUnity);
    den2 = den2.filter(notUnity);

    var combined = {};

    function combineTerms(terms, direction) {
      var k;
      var prefix;
      var prefixValue;
      for (var i = 0; i < terms.length; i++) {
        if (PREFIX_VALUES[terms[i]]) {
          k = terms[i + 1];
          prefix = terms[i];
          prefixValue = PREFIX_VALUES[prefix];
          i++;
        }
        else {
          k = terms[i];
          prefix = null;
          prefixValue = 1;
        }
        if (k && k !== UNITY) {
          if (combined[k]) {
            combined[k][0] += direction;
            var combinedPrefixValue = combined[k][2] ? PREFIX_VALUES[combined[k][2]] : 1;
            combined[k][direction === 1 ? 3 : 4] *= divSafe(prefixValue, combinedPrefixValue);
          }
          else {
            combined[k] = [direction, k, prefix, 1, 1];
          }
        }
      }
    }

    combineTerms(num1, 1);
    combineTerms(den1, -1);
    combineTerms(num2, 1);
    combineTerms(den2, -1);

    var num = [];
    var den = [];
    var scale = 1;

    for (var prop in combined) {
      if (combined.hasOwnProperty(prop)) {
        var item = combined[prop];
        var n;
        if (item[0] > 0) {
          for (n = 0; n < item[0]; n++) {
            num.push(item[2] === null ? item[1] : [item[2], item[1]]);
          }
        }
        else if (item[0] < 0) {
          for (n = 0; n < -item[0]; n++) {
            den.push(item[2] === null ? item[1] : [item[2], item[1]]);
          }
        }
        scale *= divSafe(item[3], item[4]);
      }
    }

    if (num.length === 0) {
      num = UNITY_ARRAY;
    }
    if (den.length === 0) {
      den = UNITY_ARRAY;
    }

    // Flatten
    num = num.reduce(function(a,b) {
      return a.concat(b);
    }, []);
    den = den.reduce(function(a,b) {
      return a.concat(b);
    }, []);

    return [num, den, scale];
  }

  assign(Qty.prototype, {
    eq: function(other) {
      return this.compareTo(other) === 0;
    },

    lt: function(other) {
      return this.compareTo(other) === -1;
    },

    lte: function(other) {
      return this.eq(other) || this.lt(other);
    },

    gt: function(other) {
      return this.compareTo(other) === 1;
    },

    gte: function(other) {
      return this.eq(other) || this.gt(other);
    },

    // Compare two Qty objects. Throws an exception if they are not of compatible types.
    // Comparisons are done based on the value of the quantity in base SI units.
    //
    // NOTE: We cannot compare inverses as that breaks the general compareTo contract:
    //   if a.compareTo(b) < 0 then b.compareTo(a) > 0
    //   if a.compareTo(b) == 0 then b.compareTo(a) == 0
    //
    //   Since "10S" == ".1ohm" (10 > .1) and "10ohm" == ".1S" (10 > .1)
    //     Qty("10S").inverse().compareTo("10ohm") == -1
    //     Qty("10ohm").inverse().compareTo("10S") == -1
    //
    //   If including inverses in the sort is needed, I suggest writing: Qty.sort(qtyArray,units)
    compareTo: function(other) {
      if (isString(other)) {
        return this.compareTo(Qty(other));
      }
      if (!this.isCompatible(other)) {
        throwIncompatibleUnits(this.units(), other.units());
      }
      if (this.baseScalar < other.baseScalar) {
        return -1;
      }
      else if (this.baseScalar === other.baseScalar) {
        return 0;
      }
      else if (this.baseScalar > other.baseScalar) {
        return 1;
      }
    },

    // Return true if quantities and units match
    // Unit("100 cm").same(Unit("100 cm"))  # => true
    // Unit("100 cm").same(Unit("1 m"))     # => false
    same: function(other) {
      return (this.scalar === other.scalar) && (this.units() === other.units());
    }
  });

  assign(Qty.prototype, {
    // returns true if no associated units
    // false, even if the units are "unitless" like 'radians, each, etc'
    isUnitless: function() {
      return [this.numerator, this.denominator].every(function(item) {
        return compareArray(item, UNITY_ARRAY);
      });
    },

    /*
    check to see if units are compatible, but not the scalar part
    this check is done by comparing signatures for performance reasons
    if passed a string, it will create a unit object with the string and then do the comparison
    this permits a syntax like:
    unit =~ "mm"
    if you want to do a regexp on the unit string do this ...
    unit.units =~ /regexp/
    */
    isCompatible: function(other) {
      if (isString(other)) {
        return this.isCompatible(Qty(other));
      }

      if (!(isQty(other))) {
        return false;
      }

      if (other.signature !== undefined) {
        return this.signature === other.signature;
      }
      else {
        return false;
      }
    },

    /*
    check to see if units are inverse of each other, but not the scalar part
    this check is done by comparing signatures for performance reasons
    if passed a string, it will create a unit object with the string and then do the comparison
    this permits a syntax like:
    unit =~ "mm"
    if you want to do a regexp on the unit string do this ...
    unit.units =~ /regexp/
    */
    isInverse: function(other) {
      return this.inverse().isCompatible(other);
    },

    // Returns 'true' if the Unit is represented in base units
    isBase: function() {
      if (this._isBase !== undefined) {
        return this._isBase;
      }
      if (this.isDegrees() && this.numerator[0].match(/<(kelvin|temp-K)>/)) {
        this._isBase = true;
        return this._isBase;
      }

      this.numerator.concat(this.denominator).forEach(function(item) {
        if (item !== UNITY && BASE_UNITS.indexOf(item) === -1 ) {
          this._isBase = false;
        }
      }, this);
      if (this._isBase === false) {
        return this._isBase;
      }
      this._isBase = true;
      return this._isBase;
    }
  });

  function NestedMap() {}

  NestedMap.prototype.get = function(keys) {

    // Allows to pass key1, key2, ... instead of [key1, key2, ...]
    if (arguments.length > 1) {
      // Slower with Firefox but faster with Chrome than
      // Array.prototype.slice.call(arguments)
      // See http://jsperf.com/array-apply-versus-array-prototype-slice-call
      keys = Array.apply(null, arguments);
    }

    return keys.reduce(function(map, key, index) {
      if (map) {

        var childMap = map[key];

        if (index === keys.length - 1) {
          return childMap ? childMap.data : undefined;
        }
        else {
          return childMap;
        }
      }
    },
    this);
  };

  NestedMap.prototype.set = function(keys, value) {

    if (arguments.length > 2) {
      keys = Array.prototype.slice.call(arguments, 0, -1);
      value = arguments[arguments.length - 1];
    }

    return keys.reduce(function(map, key, index) {

      var childMap = map[key];
      if (childMap === undefined) {
        childMap = map[key] = {};
      }

      if (index === keys.length - 1) {
        childMap.data = value;
        return value;
      }
      else {
        return childMap;
      }
    }, this);
  };

  /**
   * Default formatter
   *
   * @param {number} scalar - scalar value
   * @param {string} units - units as string
   *
   * @returns {string} formatted result
   */
  function defaultFormatter(scalar, units) {
    return (scalar + " " + units).trim();
  }

  /**
   *
   * Configurable Qty default formatter
   *
   * @type {function}
   *
   * @param {number} scalar
   * @param {string} units
   *
   * @returns {string} formatted result
   */
  Qty.formatter = defaultFormatter;

  assign(Qty.prototype, {

    // returns the 'unit' part of the Unit object without the scalar
    units: function() {
      if (this._units !== undefined) {
        return this._units;
      }

      var numIsUnity = compareArray(this.numerator, UNITY_ARRAY);
      var denIsUnity = compareArray(this.denominator, UNITY_ARRAY);
      if (numIsUnity && denIsUnity) {
        this._units = "";
        return this._units;
      }

      var numUnits = stringifyUnits(this.numerator);
      var denUnits = stringifyUnits(this.denominator);
      this._units = numUnits + (denIsUnity ? "" : ("/" + denUnits));
      return this._units;
    },

    /**
     * Stringifies the quantity
     * Deprecation notice: only units parameter is supported.
     *
     * @param {(number|string|Qty)} targetUnitsOrMaxDecimalsOrPrec -
     *                              target units if string,
     *                              max number of decimals if number,
     *                              passed to #toPrec before converting if Qty
     *
     * @param {number=} maxDecimals - Maximum number of decimals of
     *                                formatted output
     *
     * @returns {string} reparseable quantity as string
     */
    toString: function(targetUnitsOrMaxDecimalsOrPrec, maxDecimals) {
      var targetUnits;
      if (isNumber(targetUnitsOrMaxDecimalsOrPrec)) {
        targetUnits = this.units();
        maxDecimals = targetUnitsOrMaxDecimalsOrPrec;
      }
      else if (isString(targetUnitsOrMaxDecimalsOrPrec)) {
        targetUnits = targetUnitsOrMaxDecimalsOrPrec;
      }
      else if (isQty(targetUnitsOrMaxDecimalsOrPrec)) {
        return this.toPrec(targetUnitsOrMaxDecimalsOrPrec).toString(maxDecimals);
      }

      var out = this.to(targetUnits);

      var outScalar = maxDecimals !== undefined ? round(out.scalar, maxDecimals) : out.scalar;
      out = (outScalar + " " + out.units()).trim();
      return out;
    },

    /**
     * Format the quantity according to optional passed target units
     * and formatter
     *
     * @param {string} [targetUnits=current units] -
     *                 optional units to convert to before formatting
     *
     * @param {function} [formatter=Qty.formatter] -
     *                   delegates formatting to formatter callback.
     *                   formatter is called back with two parameters (scalar, units)
     *                   and should return formatted result.
     *                   If unspecified, formatting is delegated to default formatter
     *                   set to Qty.formatter
     *
     * @example
     * var roundingAndLocalizingFormatter = function(scalar, units) {
     *   // localize or limit scalar to n max decimals for instance
     *   // return formatted result
     * };
     * var qty = Qty('1.1234 m');
     * qty.format(); // same units, default formatter => "1.234 m"
     * qty.format("cm"); // converted to "cm", default formatter => "123.45 cm"
     * qty.format(roundingAndLocalizingFormatter); // same units, custom formatter => "1,2 m"
     * qty.format("cm", roundingAndLocalizingFormatter); // convert to "cm", custom formatter => "123,4 cm"
     *
     * @returns {string} quantity as string
     */
    format: function(targetUnits, formatter) {
      if (arguments.length === 1) {
        if (typeof targetUnits === "function") {
          formatter = targetUnits;
          targetUnits = undefined;
        }
      }

      formatter = formatter || Qty.formatter;
      var targetQty = this.to(targetUnits);
      return formatter.call(this, targetQty.scalar, targetQty.units());
    }
  });

  var stringifiedUnitsCache = new NestedMap();
  /**
   * Returns a string representing a normalized unit array
   *
   * @param {string[]} units Normalized unit array
   * @returns {string} String representing passed normalized unit array and
   *   suitable for output
   *
   */
  function stringifyUnits(units) {

    var stringified = stringifiedUnitsCache.get(units);
    if (stringified) {
      return stringified;
    }

    var isUnity = compareArray(units, UNITY_ARRAY);
    if (isUnity) {
      stringified = "1";
    }
    else {
      stringified = simplify(getOutputNames(units)).join("*");
    }

    // Cache result
    stringifiedUnitsCache.set(units, stringified);

    return stringified;
  }

  function getOutputNames(units) {
    var unitNames = [], token, tokenNext;
    for (var i = 0; i < units.length; i++) {
      token = units[i];
      tokenNext = units[i + 1];
      if (PREFIX_VALUES[token]) {
        unitNames.push(OUTPUT_MAP[token] + OUTPUT_MAP[tokenNext]);
        i++;
      }
      else {
        unitNames.push(OUTPUT_MAP[token]);
      }
    }
    return unitNames;
  }

  function simplify(units) {
    // this turns ['s','m','s'] into ['s2','m']

    var unitCounts = units.reduce(function(acc, unit) {
      var unitCounter = acc[unit];
      if (!unitCounter) {
        acc.push(unitCounter = acc[unit] = [unit, 0]);
      }

      unitCounter[1]++;

      return acc;
    }, []);

    return unitCounts.map(function(unitCount) {
      return unitCount[0] + (unitCount[1] > 1 ? unitCount[1] : "");
    });
  }

  Qty.version = "1.8.0";

  return Qty;

}));


================================================
FILE: build/quantities.mjs
================================================
/*
The MIT License (MIT)
Copyright © 2006-2007 Kevin C. Olbrich
Copyright © 2010-2016 LIM SAS (http://lim.eu) - Julien Sanchez

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/**
 * Tests if a value is a string
 *
 * @param {*} value - Value to test
 *
 * @returns {boolean} true if value is a string, false otherwise
 */
function isString(value) {
  return typeof value === "string" || value instanceof String;
}

/*
 * Prefer stricter Number.isFinite if currently supported.
 * To be dropped when ES6 is finalized. Obsolete browsers will
 * have to use ES6 polyfills.
 */
var isFiniteImpl = Number.isFinite || window.isFinite;
/**
 * Tests if a value is a number
 *
 * @param {*} value - Value to test
 *
 * @returns {boolean} true if value is a number, false otherwise
 */
function isNumber(value) {
  // Number.isFinite allows not to consider NaN or '1' as numbers
  return isFiniteImpl(value);
}

/*
 * Identity function
 */
function identity(value) {
  return value;
}

/**
 * Returns unique strings from list
 *
 * @param {string[]} strings - array of strings
 *
 *
 * @returns {string[]} a new array of strings without duplicates
 */
function uniq(strings) {
  var seen = {};
  return strings.filter(function(item) {
    return seen.hasOwnProperty(item) ? false : (seen[item] = true);
  });
}

function compareArray(array1, array2) {
  if (array2.length !== array1.length) {
    return false;
  }
  for (var i = 0; i < array1.length; i++) {
    if (array2[i].compareArray) {
      if (!array2[i].compareArray(array1[i])) {
        return false;
      }
    }
    if (array2[i] !== array1[i]) {
      return false;
    }
  }
  return true;
}

function assign(target, properties) {
  Object.keys(properties).forEach(function(key) {
    target[key] = properties[key];
  });
}

/**
 * Safely multiplies numbers while avoiding floating errors
 * like 0.1 * 0.1 => 0.010000000000000002
 *
 * @param {...number} numbers - numbers to multiply
 *
 * @returns {number} result
 */
function mulSafe() {
  var result = 1, decimals = 0;
  for (var i = 0; i < arguments.length; i++) {
    var arg = arguments[i];
    decimals = decimals + getFractional(arg);
    result *= arg;
  }

  return decimals !== 0 ? round(result, decimals) : result;
}

/**
 * Safely divides two numbers while avoiding floating errors
 * like 0.3 / 0.05 => 5.999999999999999
 *
 * @returns {number} result
 * @param {number} num Numerator
 * @param {number} den Denominator
 */
function divSafe(num, den) {
  if (den === 0) {
    throw new Error("Divide by zero");
  }

  var factor = Math.pow(10, getFractional(den));
  var invDen = factor / (factor * den);

  return mulSafe(num, invDen);
}

/**
 * Rounds value at the specified number of decimals
 *
 * @param {number} val - value to round
 * @param {number} decimals - number of decimals
 *
 * @returns {number} rounded number
 */
function round(val, decimals) {
  return Math.round(val * Math.pow(10, decimals)) / Math.pow(10, decimals);
}

function getFractional(num) {
  // Check for NaNs or Infinities
  if (!isFinite(num)) {
    return 0;
  }

  // Faster than parsing strings
  // http://jsperf.com/count-decimals/2
  var count = 0;
  while (num % 1 !== 0) {
    num *= 10;
    count++;
  }
  return count;
}

/**
 * Custom error type definition
 * @constructor
 */
function QtyError() {
  var err;
  if (!this) { // Allows to instantiate QtyError without new()
    err = Object.create(QtyError.prototype);
    QtyError.apply(err, arguments);
    return err;
  }
  err = Error.apply(this, arguments);
  this.name = "QtyError";
  this.message = err.message;
  this.stack = err.stack;
}
QtyError.prototype = Object.create(Error.prototype, {constructor: { value: QtyError }});

/*
 * Throws incompatible units error
 * @param {string} left - units
 * @param {string} right - units incompatible with first argument
 * @throws "Incompatible units" error
 */
function throwIncompatibleUnits(left, right) {
  throw new QtyError("Incompatible units: " + left + " and " + right);
}

var UNITS = {
  /* prefixes */
  "<googol>" : [["googol"], 1e100, "prefix"],
  "<kibi>"  :  [["Ki","Kibi","kibi"], Math.pow(2,10), "prefix"],
  "<mebi>"  :  [["Mi","Mebi","mebi"], Math.pow(2,20), "prefix"],
  "<gibi>"  :  [["Gi","Gibi","gibi"], Math.pow(2,30), "prefix"],
  "<tebi>"  :  [["Ti","Tebi","tebi"], Math.pow(2,40), "prefix"],
  "<pebi>"  :  [["Pi","Pebi","pebi"], Math.pow(2,50), "prefix"],
  "<exi>"   :  [["Ei","Exi","exi"], Math.pow(2,60), "prefix"],
  "<zebi>"  :  [["Zi","Zebi","zebi"], Math.pow(2,70), "prefix"],
  "<yebi>"  :  [["Yi","Yebi","yebi"], Math.pow(2,80), "prefix"],
  "<yotta>" :  [["Y","Yotta","yotta"], 1e24, "prefix"],
  "<zetta>" :  [["Z","Zetta","zetta"], 1e21, "prefix"],
  "<exa>"   :  [["E","Exa","exa"], 1e18, "prefix"],
  "<peta>"  :  [["P","Peta","peta"], 1e15, "prefix"],
  "<tera>"  :  [["T","Tera","tera"], 1e12, "prefix"],
  "<giga>"  :  [["G","Giga","giga"], 1e9, "prefix"],
  "<mega>"  :  [["M","Mega","mega"], 1e6, "prefix"],
  "<kilo>"  :  [["k","kilo"], 1e3, "prefix"],
  "<hecto>" :  [["h","Hecto","hecto"], 1e2, "prefix"],
  "<deca>"  :  [["da","Deca","deca","deka"], 1e1, "prefix"],
  "<deci>"  :  [["d","Deci","deci"], 1e-1, "prefix"],
  "<centi>"  : [["c","Centi","centi"], 1e-2, "prefix"],
  "<milli>" :  [["m","Milli","milli"], 1e-3, "prefix"],
  "<micro>"  : [
    ["u","\u03BC"/*µ as greek letter*/,"\u00B5"/*µ as micro sign*/,"Micro","mc","micro"],
    1e-6,
    "prefix"
  ],
  "<nano>"  :  [["n","Nano","nano"], 1e-9, "prefix"],
  "<pico>"  :  [["p","Pico","pico"], 1e-12, "prefix"],
  "<femto>" :  [["f","Femto","femto"], 1e-15, "prefix"],
  "<atto>"  :  [["a","Atto","atto"], 1e-18, "prefix"],
  "<zepto>" :  [["z","Zepto","zepto"], 1e-21, "prefix"],
  "<yocto>" :  [["y","Yocto","yocto"], 1e-24, "prefix"],

  "<1>"     :  [["1", "<1>"], 1, ""],
  /* length units */
  "<meter>" :  [["m","meter","meters","metre","metres"], 1.0, "length", ["<meter>"] ],
  "<inch>"  :  [["in","inch","inches","\""], 0.0254, "length", ["<meter>"]],
  "<foot>"  :  [["ft","foot","feet","'"], 0.3048, "length", ["<meter>"]],
  "<yard>"  :  [["yd","yard","yards"], 0.9144, "length", ["<meter>"]],
  "<mile>"  :  [["mi","mile","miles"], 1609.344, "length", ["<meter>"]],
  "<naut-mile>" : [["nmi","naut-mile"], 1852, "length", ["<meter>"]],
  "<league>":  [["league","leagues"], 4828, "length", ["<meter>"]],
  "<furlong>": [["furlong","furlongs"], 201.2, "length", ["<meter>"]],
  "<rod>"   :  [["rd","rod","rods"], 5.029, "length", ["<meter>"]],
  "<mil>"   :  [["mil","mils"], 0.0000254, "length", ["<meter>"]],
  "<angstrom>"  :[["ang","angstrom","angstroms"], 1e-10, "length", ["<meter>"]],
  "<fathom>" : [["fathom","fathoms"], 1.829, "length", ["<meter>"]],
  "<pica>"  : [["pica","picas"], 0.00423333333, "length", ["<meter>"]],
  "<point>" : [["pt","point","points"], 0.000352777778, "length", ["<meter>"]],
  "<redshift>" : [["z","red-shift", "redshift"], 1.302773e26, "length", ["<meter>"]],
  "<AU>"    : [["AU","astronomical-unit"], 149597900000, "length", ["<meter>"]],
  "<light-second>":[["ls","light-second"], 299792500, "length", ["<meter>"]],
  "<light-minute>":[["lmin","light-minute"], 17987550000, "length", ["<meter>"]],
  "<light-year>" : [["ly","light-year"], 9460528000000000, "length", ["<meter>"]],
  "<parsec>"  : [["pc","parsec","parsecs"], 30856780000000000, "length", ["<meter>"]],
  "<datamile>"  :  [["DM","datamile"], 1828.8, "length", ["<meter>"]],

  /* mass */
  "<kilogram>" : [["kg","kilogram","kilograms"], 1.0, "mass", ["<kilogram>"]],
  "<AMU>" : [["u","AMU","amu"], 1.660538921e-27, "mass", ["<kilogram>"]],
  "<dalton>" : [["Da","Dalton","Daltons","dalton","daltons"], 1.660538921e-27, "mass", ["<kilogram>"]],
  "<slug>" : [["slug","slugs"], 14.5939029, "mass", ["<kilogram>"]],
  "<short-ton>" : [["tn","ton","short-ton"], 907.18474, "mass", ["<kilogram>"]],
  "<metric-ton>":[["t","tonne","metric-ton"], 1000, "mass", ["<kilogram>"]],
  "<carat>" : [["ct","carat","carats"], 0.0002, "mass", ["<kilogram>"]],
  "<pound>" : [["lbs","lb","pound","pounds","#"], 0.45359237, "mass", ["<kilogram>"]],
  "<ounce>" : [["oz","ounce","ounces"], 0.0283495231, "mass", ["<kilogram>"]],
  "<gram>"    :  [["g","gram","grams","gramme","grammes"], 1e-3, "mass", ["<kilogram>"]],
  "<grain>" : [["grain","grains","gr"], 6.479891e-5, "mass", ["<kilogram>"]],
  "<dram>"  : [["dram","drams","dr"], 0.0017718452, "mass",["<kilogram>"]],
  "<stone>" : [["stone","stones","st"],6.35029318, "mass",["<kilogram>"]],

  /* area */
  "<hectare>":[["hectare"], 10000, "area", ["<meter>","<meter>"]],
  "<acre>":[["acre","acres"], 4046.85642, "area", ["<meter>","<meter>"]],
  "<sqft>":[["sqft"], 0.09290304, "area", ["<foot>","<foot>"]],

  /* volume */
  "<liter>" : [["l","L","liter","liters","litre","litres"], 0.001, "volume", ["<meter>","<meter>","<meter>"]],
  "<gallon>":  [["gal","gallon","gallons"], 0.0037854118, "volume", ["<meter>","<meter>","<meter>"]],
  "<gallon-imp>":  [["galimp","gallon-imp","gallons-imp"], 0.0045460900, "volume", ["<meter>","<meter>","<meter>"]],
  "<quart>":  [["qt","quart","quarts"], 0.00094635295, "volume", ["<meter>","<meter>","<meter>"]],
  "<pint>":  [["pt","pint","pints"], 0.000473176475, "volume", ["<meter>","<meter>","<meter>"]],
  "<pint-imp>":  [["ptimp","pint-imp","pints-imp"], 5.6826125e-4, "volume", ["<meter>","<meter>","<meter>"]],
  "<cup>":  [["cu","cup","cups"], 0.000236588238, "volume", ["<meter>","<meter>","<meter>"]],
  "<fluid-ounce>":  [["floz","fluid-ounce","fluid-ounces"], 2.95735297e-5, "volume", ["<meter>","<meter>","<meter>"]],
  "<fluid-ounce-imp>":  [["flozimp", "floz-imp","fluid-ounce-imp","fluid-ounces-imp"], 2.84130625e-5, "volume", ["<meter>","<meter>","<meter>"]],
  "<tablespoon>":  [["tb","tbsp","tbs","tablespoon","tablespoons"], 1.47867648e-5, "volume", ["<meter>","<meter>","<meter>"]],
  "<teaspoon>":  [["tsp","teaspoon","teaspoons"], 4.92892161e-6, "volume", ["<meter>","<meter>","<meter>"]],
  "<bushel>":  [["bu","bsh","bushel","bushels"], 0.035239072, "volume", ["<meter>","<meter>","<meter>"]],
  "<oilbarrel>":  [["bbl","oilbarrel", "oilbarrels", "oil-barrel","oil-barrels"], 0.158987294928, "volume", ["<meter>","<meter>","<meter>"]],
  "<beerbarrel>":  [["bl","bl-us","beerbarrel", "beerbarrels", "beer-barrel","beer-barrels"], 0.1173477658, "volume", ["<meter>","<meter>","<meter>"]],
  "<beerbarrel-imp>":  [["blimp","bl-imp","beerbarrel-imp", "beerbarrels-imp", "beer-barrel-imp","beer-barrels-imp"], 0.16365924, "volume", ["<meter>","<meter>","<meter>"]],

  /* speed */
  "<kph>" : [["kph"], 0.277777778, "speed", ["<meter>"], ["<second>"]],
  "<mph>" : [["mph"], 0.44704, "speed", ["<meter>"], ["<second>"]],
  "<knot>" : [["kt","kn","kts","knot","knots"], 0.514444444, "speed", ["<meter>"], ["<second>"]],
  "<fps>"  : [["fps"], 0.3048, "speed", ["<meter>"], ["<second>"]],

  /* acceleration */
  "<gee>" : [["gee"], 9.80665, "acceleration", ["<meter>"], ["<second>","<second>"]],
  "<Gal>" : [["Gal"], 1e-2, "acceleration", ["<meter>"], ["<second>","<second>"]],

  /* temperature_difference */
  "<kelvin>" : [["degK","kelvin"], 1.0, "temperature", ["<kelvin>"]],
  "<celsius>" : [["degC","celsius","celsius","centigrade"], 1.0, "temperature", ["<kelvin>"]],
  "<fahrenheit>" : [["degF","fahrenheit"], 5 / 9, "temperature", ["<kelvin>"]],
  "<rankine>" : [["degR","rankine"], 5 / 9, "temperature", ["<kelvin>"]],
  "<temp-K>"  : [["tempK","temp-K"], 1.0, "temperature", ["<temp-K>"]],
  "<temp-C>"  : [["tempC","temp-C"], 1.0, "temperature", ["<temp-K>"]],
  "<temp-F>"  : [["tempF","temp-F"], 5 / 9, "temperature", ["<temp-K>"]],
  "<temp-R>"  : [["tempR","temp-R"], 5 / 9, "temperature", ["<temp-K>"]],

  /* time */
  "<second>":  [["s","sec","secs","second","seconds"], 1.0, "time", ["<second>"]],
  "<minute>":  [["min","mins","minute","minutes"], 60.0, "time", ["<second>"]],
  "<hour>":  [["h","hr","hrs","hour","hours"], 3600.0, "time", ["<second>"]],
  "<day>":  [["d","day","days"], 3600 * 24, "time", ["<second>"]],
  "<week>":  [["wk","week","weeks"], 7 * 3600 * 24, "time", ["<second>"]],
  "<fortnight>": [["fortnight","fortnights"], 1209600, "time", ["<second>"]],
  "<year>":  [["y","yr","year","years","annum"], 31556926, "time", ["<second>"]],
  "<decade>":[["decade","decades"], 315569260, "time", ["<second>"]],
  "<century>":[["century","centuries"], 3155692600, "time", ["<second>"]],

  /* pressure */
  "<pascal>" : [["Pa","pascal","Pascal"], 1.0, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<bar>" : [["bar","bars"], 100000, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<mmHg>" : [["mmHg"], 133.322368, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<inHg>" : [["inHg"], 3386.3881472, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<torr>" : [["torr"], 133.322368, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<atm>" : [["atm","ATM","atmosphere","atmospheres"], 101325, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<psi>" : [["psi"], 6894.76, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<cmh2o>" : [["cmH2O","cmh2o"], 98.0638, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],
  "<inh2o>" : [["inH2O","inh2o"], 249.082052, "pressure", ["<kilogram>"],["<meter>","<second>","<second>"]],

  /* viscosity */
  "<poise>"  : [["P","poise"], 0.1, "viscosity", ["<kilogram>"],["<meter>","<second>"] ],
  "<stokes>" : [["St","stokes"], 1e-4, "viscosity", ["<meter>","<meter>"], ["<second>"]],

  /* substance */
  "<mole>"  :  [["mol","mole"], 1.0, "substance", ["<mole>"]],

  /* molar_concentration */
  "<molar>" : [["M","molar"], 1000, "molar_concentration", ["<mole>"], ["<meter>","<meter>","<meter>"]],
  "<wtpercent>"  : [["wt%","wtpercent"], 10, "molar_concentration", ["<kilogram>"], ["<meter>","<meter>","<meter>"]],

  /* activity */
  "<katal>" :  [["kat","katal","Katal"], 1.0, "activity", ["<mole>"], ["<second>"]],
  "<unit>"  :  [["U","enzUnit","unit"], 16.667e-16, "activity", ["<mole>"], ["<second>"]],

  /* capacitance */
  "<farad>" :  [["F","farad","Farad"], 1.0, "capacitance", ["<second>","<second>","<second>","<second>","<ampere>","<ampere>"], ["<meter>", "<meter>", "<kilogram>"]],

  /* charge */
  "<coulomb>" :  [["C","coulomb","Coulomb"], 1.0, "charge", ["<ampere>","<second>"]],
  "<Ah>" :  [["Ah"], 3600, "charge", ["<ampere>","<second>"]],
  "<elementary-charge>" :  [["e"], 1.602176634e-19, "charge", ["<ampere>","<second>"]],

  /* current */
  "<ampere>"  :  [["A","Ampere","ampere","amp","amps"], 1.0, "current", ["<ampere>"]],

  /* conductance */
  "<siemens>" : [["S","Siemens","siemens"], 1.0, "conductance", ["<second>","<second>","<second>","<ampere>","<ampere>"], ["<kilogram>","<meter>","<meter>"]],

  /* inductance */
  "<henry>" :  [["H","Henry","henry"], 1.0, "inductance", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<ampere>","<ampere>"]],

  /* potential */
  "<volt>"  :  [["V","Volt","volt","volts"], 1.0, "potential", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<second>","<ampere>"]],

  /* resistance */
  "<ohm>" :  [
    ["Ohm","ohm","\u03A9"/*Ω as greek letter*/,"\u2126"/*Ω as ohm sign*/],
    1.0,
    "resistance",
    ["<meter>","<meter>","<kilogram>"],["<second>","<second>","<second>","<ampere>","<ampere>"]
  ],
  /* magnetism */
  "<weber>" : [["Wb","weber","webers"], 1.0, "magnetism", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<ampere>"]],
  "<tesla>"  : [["T","tesla","teslas"], 1.0, "magnetism", ["<kilogram>"], ["<second>","<second>","<ampere>"]],
  "<gauss>" : [["G","gauss"], 1e-4, "magnetism",  ["<kilogram>"], ["<second>","<second>","<ampere>"]],
  "<maxwell>" : [["Mx","maxwell","maxwells"], 1e-8, "magnetism", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>","<ampere>"]],
  "<oersted>"  : [["Oe","oersted","oersteds"], 250.0 / Math.PI, "magnetism", ["<ampere>"], ["<meter>"]],

  /* energy */
  "<joule>" :  [["J","joule","Joule","joules","Joules"], 1.0, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<erg>"   :  [["erg","ergs"], 1e-7, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<btu>"   :  [["BTU","btu","BTUs"], 1055.056, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<calorie>" :  [["cal","calorie","calories"], 4.18400, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<Calorie>" :  [["Cal","Calorie","Calories"], 4184.00, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<therm-US>" : [["th","therm","therms","Therm","therm-US"], 105480400, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<Wh>" : [["Wh"], 3600, "energy",["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],
  "<electronvolt>" : [["eV", "electronvolt", "electronvolts"], 1.602176634E-19, "energy", ["<meter>","<meter>","<kilogram>"], ["<second>","<second>"]],

  /* force */
  "<newton>"  : [["N","Newton","newton"], 1.0, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
  "<dyne>"  : [["dyn","dyne"], 1e-5, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
  "<pound-force>"  : [["lbf","pound-force"], 4.448222, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
  "<kilogram-force>"  : [["kgf","kilogram-force", "kilopond", "kp"], 9.80665, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],
  "<gram-force>"  : [["gf","gram-force"], 9.80665E-3, "force", ["<kilogram>","<meter>"], ["<second>","<second>"]],

  /* frequency */
  "<hertz>" : [["Hz","hertz","Hertz"], 1.0, "frequency", ["<1>"], ["<second>"]],

  /* angle */
  "<radian>" :[["rad","radian","radians"], 1.0, "angle", ["<radian>"]],
  "<degree>" :[["deg","degree","degrees"], Math.PI / 180.0, "angle", ["<radian>"]],
  "<arcminute>" :[["arcmin","arcminute","arcminutes"], Math.PI / 10800.0, "angle", ["<radian>"]],
  "<arcsecond>" :[["arcsec","arcsecond","arcseconds"], Math.PI / 648000.0, "angle", ["<radian>"]],
  "<gradian>"   :[["gon","grad","gradian","grads"], Math.PI / 200.0, "angle", ["<radian>"]],
  "<steradian>"  : [["sr","steradian","steradians"], 1.0, "solid_angle", ["<steradian>"]],

  /* rotation */
  "<rotation>" : [["rotation"], 2.0 * Math.PI, "angle", ["<radian>"]],
  "<rpm>"   :[["rpm"], 2.0 * Math.PI / 60.0, "angular_velocity", ["<radian>"], ["<second>"]],

  /* information */
  "<byte>"  :[["B","byte","bytes"], 1.0, "information", ["<byte>"]],
  "<bit>"  :[["b","bit","bits"], 0.125, "information", ["<byte>"]],

  /* information rate */
  "<Bps>" : [["Bps"], 1.0, "information_rate", ["<byte>"], ["<second>"]],
  "<bps>" : [["bps"], 0.125, "information_rate", ["<byte>"], ["<second>"]],

  /* currency */
  "<dollar>":[["USD","dollar"], 1.0, "currency", ["<dollar>"]],
  "<cents>" :[["cents"], 0.01, "currency", ["<dollar>"]],

  /* luminosity */
  "<candela>" : [["cd","candela"], 1.0, "luminosity", ["<candela>"]],
  "<lumen>" : [["lm","lumen"], 1.0, "luminous_power", ["<candela>","<steradian>"]],
  "<lux>" :[["lux"], 1.0, "illuminance", ["<candela>","<steradian>"], ["<meter>","<meter>"]],

  /* power */
  "<watt>"  : [["W","watt","watts"], 1.0, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],
  "<volt-ampere>"  : [["VA","volt-ampere"], 1.0, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],
  "<volt-ampere-reactive>"  : [["var","Var","VAr","VAR","volt-ampere-reactive"], 1.0, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],
  "<horsepower>"  :  [["hp","horsepower"], 745.699872, "power", ["<kilogram>","<meter>","<meter>"], ["<second>","<second>","<second>"]],

  /* radiation */
  "<gray>" : [["Gy","gray","grays"], 1.0, "radiation", ["<meter>","<meter>"], ["<second>","<second>"]],
  "<roentgen>" : [["R","roentgen"], 0.009330, "radiation", ["<meter>","<meter>"], ["<second>","<second>"]],
  "<sievert>" : [["Sv","sievert","sieverts"], 1.0, "radiation", ["<meter>","<meter>"], ["<second>","<second>"]],
  "<becquerel>" : [["Bq","becquerel","becquerels"], 1.0, "radiation", ["<1>"],["<second>"]],
  "<curie>" : [["Ci","curie","curies"], 3.7e10, "radiation", ["<1>"],["<second>"]],

  /* rate */
  "<cpm>" : [["cpm"], 1.0 / 60.0, "rate", ["<count>"],["<second>"]],
  "<dpm>" : [["dpm"], 1.0 / 60.0, "rate", ["<count>"],["<second>"]],
  "<bpm>" : [["bpm"], 1.0 / 60.0, "rate", ["<count>"],["<second>"]],

  /* resolution / typography */
  "<dot>" : [["dot","dots"], 1, "resolution", ["<each>"]],
  "<pixel>" : [["pixel","px"], 1, "resolution", ["<each>"]],
  "<ppi>" : [["ppi"], 1, "resolution", ["<pixel>"], ["<inch>"]],
  "<dpi>" : [["dpi"], 1, "typography", ["<dot>"], ["<inch>"]],

  /* other */
  "<cell>" : [["cells","cell"], 1, "counting", ["<each>"]],
  "<each>" : [["each"], 1.0, "counting", ["<each>"]],
  "<count>" : [["count"], 1.0, "counting", ["<each>"]],
  "<base-pair>"  : [["bp","base-pair"], 1.0, "counting", ["<each>"]],
  "<nucleotide>" : [["nt","nucleotide"], 1.0, "counting", ["<each>"]],
  "<molecule>" : [["molecule","molecules"], 1.0, "counting", ["<1>"]],
  "<dozen>" :  [["doz","dz","dozen"],12.0,"prefix_only", ["<each>"]],
  "<percent>": [["%","percent"], 0.01, "prefix_only", ["<1>"]],
  "<ppm>" :  [["ppm"],1e-6, "prefix_only", ["<1>"]],
  "<ppb>" :  [["ppb"],1e-9, "prefix_only", ["<1>"]],
  "<ppt>" :  [["ppt"],1e-12, "prefix_only", ["<1>"]],
  "<ppq>" :  [["ppq"],1e-15, "prefix_only", ["<1>"]],
  "<gross>" :  [["gr","gross"],144.0, "prefix_only", ["<dozen>","<dozen>"]],
  "<decibel>"  : [["dB","decibel","decibels"], 1.0, "logarithmic", ["<decibel>"]]
};

var BASE_UNITS = ["<meter>","<kilogram>","<second>","<mole>", "<ampere>","<radian>","<kelvin>","<temp-K>","<byte>","<dollar>","<candela>","<each>","<steradian>","<decibel>"];

var UNITY = "<1>";
var UNITY_ARRAY = [UNITY];

// Setup

/**
 * Asserts unit definition is valid
 *
 * @param {string} unitDef - Name of unit to test
 * @param {Object} definition - Definition of unit to test
 *
 * @returns {void}
 * @throws {QtyError} if unit definition is not valid
 */
function validateUnitDefinition(unitDef, definition) {
  var scalar = definition[1];
  var numerator = definition[3] || [];
  var denominator = definition[4] || [];
  if (!isNumber(scalar)) {
    throw new QtyError(unitDef + ": Invalid unit definition. " +
                       "'scalar' must be a number");
  }

  numerator.forEach(function(unit) {
    if (UNITS[unit] === undefined) {
      throw new QtyError(unitDef + ": Invalid unit definition. " +
                         "Unit " + unit + " in 'numerator' is not recognized");
    }
  });

  denominator.forEach(function(unit) {
    if (UNITS[unit] === undefined) {
      throw new QtyError(unitDef + ": Invalid unit definition. " +
                         "Unit " + unit + " in 'denominator' is not recognized");
    }
  });
}

var PREFIX_VALUES = {};
var PREFIX_MAP = {};
var UNIT_VALUES = {};
var UNIT_MAP = {};
var OUTPUT_MAP = {};
for (var unitDef in UNITS) {
  if (UNITS.hasOwnProperty(unitDef)) {
    var definition = UNITS[unitDef];
    if (definition[2] === "prefix") {
      PREFIX_VALUES[unitDef] = definition[1];
      for (var i = 0; i < definition[0].length; i++) {
        PREFIX_MAP[definition[0][i]] = unitDef;
      }
    }
    else {
      validateUnitDefinition(unitDef, definition);
      UNIT_VALUES[unitDef] = {
        scalar: definition[1],
        numerator: definition[3],
        denominator: definition[4]
      };
      for (var j = 0; j < definition[0].length; j++) {
        UNIT_MAP[definition[0][j]] = unitDef;
      }
    }
    OUTPUT_MAP[unitDef] = definition[0][0];
  }
}

/**
 * Returns a list of available units of kind
 *
 * @param {string} [kind] - kind of units
 * @returns {array} names of units
 * @throws {QtyError} if kind is unknown
 */
function getUnits(kind) {
  var i;
  var units = [];
  var unitKeys = Object.keys(UNITS);
  if (typeof kind === "undefined") {
    for (i = 0; i < unitKeys.length; i++) {
      if (["", "prefix"].indexOf(UNITS[unitKeys[i]][2]) === -1) {
        units.push(unitKeys[i].substr(1, unitKeys[i].length - 2));
      }
    }
  }
  else if (this.getKinds().indexOf(kind) === -1) {
    throw new QtyError("Kind not recognized");
  }
  else {
    for (i = 0; i < unitKeys.length; i++) {
      if (UNITS[unitKeys[i]][2] === kind) {
        units.push(unitKeys[i].substr(1, unitKeys[i].length - 2));
      }
    }
  }

  return units.sort(function(a, b) {
    if (a.toLowerCase() < b.toLowerCase()) {
      return -1;
    }
    if (a.toLowerCase() > b.toLowerCase()) {
      return 1;
    }
    return 0;
  });
}

/**
 * Returns a list of alternative names for a unit
 *
 * @param {string} unitName - name of unit
 * @returns {string[]} aliases for unit
 * @throws {QtyError} if unit is unknown
 */
function getAliases(unitName) {
  if (!UNIT_MAP[unitName]) {
    throw new QtyError("Unit not recognized");
  }
  return UNITS[UNIT_MAP[unitName]][0];
}

var SIGNATURE_VECTOR = ["length", "time", "temperature", "mass", "current", "substance", "luminosity", "currency", "information", "angle"];

/*
calculates the unit signature id for use in comparing compatible units and simplification
the signature is based on a simple classification of units and is based on the following publication

Novak, G.S., Jr. "Conversion of units of measurement", IEEE Transactions on Software Engineering,
21(8), Aug 1995, pp.651-661
doi://10.1109/32.403789
http://ieeexplore.ieee.org/Xplore/login.jsp?url=/iel1/32/9079/00403789.pdf?isnumber=9079&prod=JNL&arnumber=403789&arSt=651&ared=661&arAuthor=Novak%2C+G.S.%2C+Jr.
*/
function unitSignature() {
  if (this.signature) {
    return this.signature;
  }
  var vector = unitSignatureVector.call(this);
  for (var i = 0; i < vector.length; i++) {
    vector[i] *= Math.pow(20, i);
  }

  return vector.reduce(
    function(previous, current) {
      return previous + current;
    },
    0
  );
}

// calculates the unit signature vector used by unit_signature
function unitSignatureVector() {
  if (!this.isBase()) {
    return unitSignatureVector.call(this.toBase());
  }

  var vector = new Array(SIGNATURE_VECTOR.length);
  for (var i = 0; i < vector.length; i++) {
    vector[i] = 0;
  }
  var r, n;
  for (var j = 0; j < this.numerator.length; j++) {
    if ((r = UNITS[this.numerator[j]])) {
      n = SIGNATURE_VECTOR.indexOf(r[2]);
      if (n >= 0) {
        vector[n] = vector[n] + 1;
      }
    }
  }

  for (var k = 0; k < this.denominator.length; k++) {
    if ((r = UNITS[this.denominator[k]])) {
      n = SIGNATURE_VECTOR.indexOf(r[2]);
      if (n >= 0) {
        vector[n] = vector[n] - 1;
      }
    }
  }
  return vector;
}

var SIGN = "[+-]";
var INTEGER = "\\d+";
var SIGNED_INTEGER = SIGN + "?" + INTEGER;
var FRACTION = "\\." + INTEGER;
var FLOAT = "(?:" + INTEGER + "(?:" + FRACTION + ")?" + ")" +
            "|" +
            "(?:" + FRACTION + ")";
var EXPONENT = "[Ee]" + SIGNED_INTEGER;
var SCI_NUMBER = "(?:" + FLOAT + ")(?:" + EXPONENT + ")?";
var SIGNED_NUMBER = SIGN + "?\\s*" + SCI_NUMBER;
var QTY_STRING = "(" + SIGNED_NUMBER + ")?" + "\\s*([^/]*)(?:\/(.+))?";
var QTY_STRING_REGEX = new RegExp("^" + QTY_STRING + "$");

var POWER_OP = "\\^|\\*{2}";
// Allow unit powers representing scalar, length, area, volume; 4 is for some
// special case representations in SI base units.
var SAFE_POWER = "[01234]";
var TOP_REGEX = new RegExp ("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(-?" + SAFE_POWER + "(?![a-zA-Z]))");
var BOTTOM_REGEX = new RegExp("([^ \\*\\d]+?)(?:" + POWER_OP + ")?(" + SAFE_POWER + "(?![a-zA-Z]))");

/* parse a string into a unit object.
 * Typical formats like :
 * "5.6 kg*m/s^2"
 * "5.6 kg*m*s^-2"
 * "5.6 kilogram*meter*second^-2"
 * "2.2 kPa"
 * "37 degC"
 * "1"  -- creates a unitless constant with value 1
 * "GPa"  -- creates a unit with scalar 1 with units 'GPa'
 * 6'4"  -- recognized as 6 feet + 4 inches
 * 8 lbs 8 oz -- recognized as 8 lbs + 8 ounces
 */
function parse(val) {
  if (!isString(val)) {
    val = val.toString();
  }
  val = val.trim();

  var result = QTY_STRING_REGEX.exec(val);
  if (!result) {
    throw new QtyError(val + ": Quantity not recognized");
  }

  var scalarMatch = result[1];
  if (scalarMatch) {
    // Allow whitespaces between sign and scalar for loose parsing
    scalarMatch = scalarMatch.replace(/\s/g, "");
    this.scalar = parseFloat(scalarMatch);
  }
  else {
    this.scalar = 1;
  }
  var top = result[2];
  var bottom = result[3];

  var n, x, nx;
  // TODO DRY me
  while ((result = TOP_REGEX.exec(top))) {
    n = parseFloat(result[2]);
    if (isNaN(n)) {
      // Prevents infinite loops
      throw new QtyError("Unit exponent is not a number");
    }
    // Disallow unrecognized unit even if exponent is 0
    if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) {
      throw new QtyError("Unit not recognized");
    }
    x = result[1] + " ";
    nx = "";
    for (var i = 0; i < Math.abs(n) ; i++) {
      nx += x;
    }
    if (n >= 0) {
      top = top.replace(result[0], nx);
    }
    else {
      bottom = bottom ? bottom + nx : nx;
      top = top.replace(result[0], "");
    }
  }

  while ((result = BOTTOM_REGEX.exec(bottom))) {
    n = parseFloat(result[2]);
    if (isNaN(n)) {
      // Prevents infinite loops
      throw new QtyError("Unit exponent is not a number");
    }
    // Disallow unrecognized unit even if exponent is 0
    if (n === 0 && !UNIT_TEST_REGEX.test(result[1])) {
      throw new QtyError("Unit not recognized");
    }
    x = result[1] + " ";
    nx = "";
    for (var j = 0; j < n ; j++) {
      nx += x;
    }

    bottom = bottom.replace(result[0], nx);
  }

  if (top) {
    this.numerator = parseUnits(top.trim());
  }
  if (bottom) {
    this.denominator = parseUnits(bottom.trim());
  }
}

var PREFIX_REGEX = Object.keys(PREFIX_MAP).sort(function(a, b) {
  return b.length - a.length;
}).join("|");
var UNIT_REGEX = Object.keys(UNIT_MAP).sort(function(a, b) {
  return b.length - a.length;
}).join("|");
/*
 * Minimal boundary regex to support units with Unicode characters
 * \b only works for ASCII
 */
var BOUNDARY_REGEX = "\\b|$";
var UNIT_MATCH = "(" + PREFIX_REGEX + ")??(" +
                 UNIT_REGEX +
                 ")(?:" + BOUNDARY_REGEX + ")";
var UNIT_TEST_REGEX = new RegExp("^\\s*(" + UNIT_MATCH + "[\\s\\*]*)+$");
var UNIT_MATCH_REGEX = new RegExp(UNIT_MATCH, "g"); // g flag for multiple occurences
var parsedUnitsCache = {};
/**
 * Parses and converts units string to normalized unit array.
 * Result is cached to speed up next calls.
 *
 * @param {string} units Units string
 * @returns {string[]} Array of normalized units
 *
 * @example
 * // Returns ["<second>", "<meter>", "<second>"]
 * parseUnits("s m s");
 *
 */
function parseUnits(units) {
  var cached = parsedUnitsCache[units];
  if (cached) {
    return cached;
  }

  var unitMatch, normalizedUnits = [];

  // Scan
  if (!UNIT_TEST_REGEX.test(units)) {
    throw new QtyError("Unit not recognized");
  }

  while ((unitMatch = UNIT_MATCH_REGEX.exec(units))) {
    normalizedUnits.push(unitMatch.slice(1));
  }

  normalizedUnits = normalizedUnits.map(function(item) {
    return PREFIX_MAP[item[0]] ? [PREFIX_MAP[item[0]], UNIT_MAP[item[1]]] : [UNIT_MAP[item[1]]];
  });

  // Flatten and remove null elements
  normalizedUnits = normalizedUnits.reduce(function(a,b) {
    return a.concat(b);
  }, []);
  normalizedUnits = normalizedUnits.filter(function(item) {
    return item;
  });

  parsedUnitsCache[units] = normalizedUnits;

  return normalizedUnits;
}

/**
 * Parses a string as a quantity
 * @param {string} value - quantity as text
 * @throws if value is not a string
 * @returns {Qty|null} Parsed quantity or null if unrecognized
 */
function globalParse(value) {
  if (!isString(value)) {
    throw new QtyError("Argument should be a string");
  }

  try {
    return this(value);
  }
  catch (e) {
    return null;
  }
}

/**
 * Tests if a value is a Qty instance
 *
 * @param {*} value - Value to test
 *
 * @returns {boolean} true if value is a Qty instance, false otherwise
 */
function isQty(value) {
  return value instanceof Qty;
}

function Qty(initValue, initUnits) {
  assertValidConstructorArgs.apply(null, arguments);

  if (!(isQty(this))) {
    return new Qty(initValue, initUnits);
  }

  this.scalar = null;
  this.baseScalar = null;
  this.signature = null;
  this._conversionCache = {};
  this.numerator = UNITY_ARRAY;
  this.denominator = UNITY_ARRAY;

  if (isDefinitionObject(initValue)) {
    this.scalar = initValue.scalar;
    this.numerator = (initValue.numerator && initValue.numerator.length !== 0) ? initValue.numerator : UNITY_ARRAY;
    this.denominator = (initValue.denominator && initValue.denominator.length !== 0) ? initValue.denominator : UNITY_ARRAY;
  }
  else if (initUnits) {
    parse.call(this, initUnits);
    this.scalar = initValue;
  }
  else {
    parse.call(this, initValue);
  }

  // math with temperatures is very limited
  if (this.denominator.join("*").indexOf("temp") >= 0) {
    throw new QtyError("Cannot divide with temperatures");
  }
  if (this.numerator.join("*").indexOf("temp") >= 0) {
    if (this.numerator.length > 1) {
      throw new QtyError("Cannot multiply by temperatures");
    }
    if (!compareArray(this.denominator, UNITY_ARRAY)) {
      throw new QtyError("Cannot divide with temperatures");
    }
  }

  this.initValue = initValue;
  updateBaseScalar.call(this);

  if (this.isTemperature() && this.baseScalar < 0) {
    throw new QtyError("Temperatures must not be less than absolute zero");
  }
}

Qty.prototype = {
  // Properly set up constructor
  constructor: Qty,
};

/**
 * Asserts constructor arguments are valid
 *
 * @param {*} value - Value to test
 * @param {string} [units] - Optional units when value is passed as a number
 *
 * @returns {void}
 * @throws {QtyError} if constructor arguments are invalid
 */
function assertValidConstructorArgs(value, units) {
  if (units) {
    if (!(isNumber(value) && isString(units))) {
      throw new QtyError("Only number accepted as initialization value " +
                         "when units are explicitly provided");
    }
  }
  else {
    if (!(isString(value) ||
          isNumber(value) ||
          isQty(value)    ||
          isDefinitionObject(value))) {
      throw new QtyError("Only string, number or quantity accepted as " +
                         "single initialization value");
    }
  }
}

/**
 * Tests if a value is a Qty definition object
 *
 * @param {*} value - Value to test
 *
 * @returns {boolean} true if value is a definition object, false otherwise
 */
function isDefinitionObject(value) {
  return value && typeof value === "object" && value.hasOwnProperty("scalar");
}

function updateBaseScalar() {
  if (this.baseScalar) {
    return this.baseScalar;
  }
  if (this.isBase()) {
    this.baseScalar = this.scalar;
    this.signature = unitSignature.call(this);
  }
  else {
    var base = this.toBase();
    this.baseScalar = base.scalar;
    this.signature = base.signature;
  }
}

var KINDS = {
  "-312078": "elastance",
  "-312058": "resistance",
  "-312038": "inductance",
  "-152058": "potential",
  "-152040": "magnetism",
  "-152038": "magnetism",
  "-7997": "specific_volume",
  "-79": "snap",
  "-59": "jolt",
  "-39": "acceleration",
  "-38": "radiation",
  "-20": "frequency",
  "-19": "speed",
  "-18": "viscosity",
  "-17": "volumetric_flow",
  "-1": "wavenumber",
  "0": "unitless",
  "1": "length",
  "2": "area",
  "3": "volume",
  "20": "time",
  "400": "temperature",
  "7941": "yank",
  "7942": "power",
  "7959": "pressure",
  "7961": "force",
  "7962": "energy",
  "7979": "viscosity",
  "7981": "momentum",
  "7982": "angular_momentum",
  "7997": "density",
  "7998": "area_density",
  "8000": "mass",
  "152020": "radiation_exposure",
  "159999": "magnetism",
  "160000": "current",
  "160020": "charge",
  "312058": "conductance",
  "312078": "capacitance",
  "3199980": "activity",
  "3199997": "molar_concentration",
  "3200000": "substance",
  "63999998": "illuminance",
  "64000000": "luminous_power",
  "1280000000": "currency",
  "25599999980": "information_rate",
  "25600000000": "information",
  "511999999980": "angular_velocity",
  "512000000000": "angle"
};

/**
 * Returns the list of available well-known kinds of units, e.g.
 * "radiation" or "length".
 *
 * @returns {string[]} names of kinds of units
 */
function getKinds() {
  return uniq(Object.keys(KINDS).map(function(knownSignature) {
    return KINDS[knownSignature];
  }));
}

Qty.prototype.kind = function() {
  return KINDS[this.signature.toString()];
};

assign(Qty.prototype, {
  isDegrees: function() {
    // signature may not have been calculated yet
    return (this.signature === null || this.signature === 400) &&
      this.numerator.length === 1 &&
      compareArray(this.denominator, UNITY_ARRAY) &&
      (this.numerator[0].match(/<temp-[CFRK]>/) || this.numerator[0].match(/<(kelvin|celsius|rankine|fahrenheit)>/));
  },

  isTemperature: function() {
    return this.isDegrees() && this.numerator[0].match(/<temp-[CFRK]>/);
  }
});

function subtractTemperatures(lhs,rhs) {
  var lhsUnits = lhs.units();
  var rhsConverted = rhs.to(lhsUnits);
  var dstDegrees = Qty(getDegreeUnits(lhsUnits));
  return Qty({"scalar": lhs.scalar - rhsConverted.scalar, "numerator": dstDegrees.numerator, "denominator": dstDegrees.denominator});
}

function subtractTempDegrees(temp,deg) {
  var tempDegrees = deg.to(getDegreeUnits(temp.units()));
  return Qty({"scalar": temp.scalar - tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator});
}

function addTempDegrees(temp,deg) {
  var tempDegrees = deg.to(getDegreeUnits(temp.units()));
  return Qty({"scalar": temp.scalar + tempDegrees.scalar, "numerator": temp.numerator, "denominator": temp.denominator});
}

function getDegreeUnits(units) {
  if (units === "tempK") {
    return "degK";
  }
  else if (units === "tempC") {
    return "degC";
  }
  else if (units === "tempF") {
    return "degF";
  }
  else if (units === "tempR") {
    return "degR";
  }
  else {
    throw new QtyError("Unknown type for temp conversion from: " + units);
  }
}

function toDegrees(src,dst) {
  var srcDegK = toDegK(src);
  var dstUnits = dst.units();
  var dstScalar;

  if (dstUnits === "degK") {
    dstScalar = srcDegK.scalar;
  }
  else if (dstUnits === "degC") {
    dstScalar = srcDegK.scalar ;
  }
  else if (dstUnits === "degF") {
    dstScalar = srcDegK.scalar * 9 / 5;
  }
  else if (dstUnits === "degR") {
    dstScalar = srcDegK.scalar * 9 / 5;
  }
  else {
    throw new QtyError("Unknown type for degree conversion to: " + dstUnits);
  }

  return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator});
}

function toDegK(qty) {
  var units = qty.units();
  var q;
  if (units.match(/(deg)[CFRK]/)) {
    q = qty.baseScalar;
  }
  else if (units === "tempK") {
    q = qty.scalar;
  }
  else if (units === "tempC") {
    q = qty.scalar;
  }
  else if (units === "tempF") {
    q = qty.scalar * 5 / 9;
  }
  else if (units === "tempR") {
    q = qty.scalar * 5 / 9;
  }
  else {
    throw new QtyError("Unknown type for temp conversion from: " + units);
  }

  return Qty({"scalar": q, "numerator": ["<kelvin>"], "denominator": UNITY_ARRAY});
}

function toTemp(src,dst) {
  var dstUnits = dst.units();
  var dstScalar;

  if (dstUnits === "tempK") {
    dstScalar = src.baseScalar;
  }
  else if (dstUnits === "tempC") {
    dstScalar = src.baseScalar - 273.15;
  }
  else if (dstUnits === "tempF") {
    dstScalar = (src.baseScalar * 9 / 5) - 459.67;
  }
  else if (dstUnits === "tempR") {
    dstScalar = src.baseScalar * 9 / 5;
  }
  else {
    throw new QtyError("Unknown type for temp conversion to: " + dstUnits);
  }

  return Qty({"scalar": dstScalar, "numerator": dst.numerator, "denominator": dst.denominator});
}

function toTempK(qty) {
  var units = qty.units();
  var q;
  if (units.match(/(deg)[CFRK]/)) {
    q = qty.baseScalar;
  }
  else if (units === "tempK") {
    q = qty.scalar;
  }
  else if (units === "tempC") {
    q = qty.scalar + 273.15;
  }
  else if (units === "tempF") {
    q = (qty.scalar + 459.67) * 5 / 9;
  }
  else if (units === "tempR") {
    q = qty.scalar * 5 / 9;
  }
  else {
    throw new QtyError("Unknown type for temp conversion from: " + units);
  }

  return Qty({"scalar": q, "numerator": ["<temp-K>"], "denominator": UNITY_ARRAY});
}

assign(Qty.prototype, {
  /**
   * Converts to other compatible units.
   * Instance's converted quantities are cached for faster subsequent calls.
   *
   * @param {(string|Qty)} other - Target units as string or retrieved from
   *                               other Qty instance (scalar is ignored)
   *
   * @returns {Qty} New converted Qty instance with target units
   *
   * @throws {QtyError} if target units are incompatible
   *
   * @example
   * var weight = Qty("25 kg");
   * weight.to("lb"); // => Qty("55.11556554621939 lbs");
   * weight.to(Qty("3 g")); // => Qty("25000 g"); // scalar of passed Qty is ignored
   */
  to: function(other) {
    var cached, target;

    if (other === undefined || other === null) {
      return this;
    }

    if (!isString(other)) {
      return this.to(other.units());
    }

    cached = this._conversionCache[other];
    if (cached) {
      return cached;
    }

    // Instantiating target to normalize units
    target = Qty(other);
    if (target.units() === this.units()) {
      return this;
    }

    if (!this.isCompatible(target)) {
      if (this.isInverse(target)) {
        target = this.inverse().to(other);
      }
      else {
        throwIncompatibleUnits(this.units(), target.units());
      }
    }
    else {
      if (target.isTemperature()) {
        target = toTemp(this,target);
      }
      else if (target.isDegrees()) {
        target = toDegrees(this,target);
      }
      else {
        var q = divSafe(this.baseScalar, target.baseScalar);
        target = Qty({"scalar": q, "numerator": target.numerator, "denominator": target.denominator});
      }
    }

    this._conversionCache[other] = target;
    return target;
  },

  // convert to base SI units
  // results of the conversion are cached so subsequent calls to this will be fast
  toBase: function() {
    if (this.isBase()) {
      return this;
    }

    if (this.isTemperature()) {
      return toTempK(this);
    }

    var cached = baseUnitCache[this.units()];
    if (!cached) {
      cached = toBaseUnits(this.numerator,this.denominator);
      baseUnitCache[this.units()] = cached;
    }
    return cached.mul(this.scalar);
  },

  // Converts the unit back to a float if it is unitless.  Otherwise raises an exception
  toFloat: function() {
    if (this.isUnitless()) {
      return this.scalar;
    }
    throw new QtyError("Can't convert to Float unless unitless.  Use Unit#scalar");
  },

  /**
   * Returns the nearest multiple of quantity passed as
   * precision
   *
   * @param {(Qty|string|number)} precQuantity - Quantity, string formated
   *   quantity or number as expected precision
   *
   * @returns {Qty} Nearest multiple of precQuantity
   *
   * @example
   * Qty('5.5 ft').toPrec('2 ft'); // returns 6 ft
   * Qty('0.8 cu').toPrec('0.25 cu'); // returns 0.75 cu
   * Qty('6.3782 m').toPrec('cm'); // returns 6.38 m
   * Qty('1.146 MPa').toPrec('0.1 bar'); // returns 1.15 MPa
   *
   */
  toPrec: function(precQuantity) {
    if (isString(precQuantity)) {
      precQuantity = Qty(precQuantity);
    }
    if (isNumber(precQuantity)) {
      precQuantity = Qty(precQuantity + " " + this.units());
    }

    if (!this.isUnitless()) {
      precQuantity = precQuantity.to(this.units());
    }
    else if (!precQuantity.isUnitless()) {
      throwIncompatibleUnits(this.units(), precQuantity.units());
    }

    if (precQuantity.scalar === 0) {
      throw new QtyError("Divide by zero");
    }

    var precRoundedResult = mulSafe(
      Math.round(this.scalar / precQuantity.scalar),
      precQuantity.scalar
    );

    return Qty(precRoundedResult + this.units());
  }
});

/**
 * Configures and returns a fast function to convert
 * Number values from units to others.
 * Useful to efficiently convert large array of values
 * with same units into others with iterative methods.
 * Does not take care of rounding issues.
 *
 * @param {string} srcUnits Units of values to convert
 * @param {string} dstUnits Units to convert to
 *
 * @returns {Function} Converting function accepting Number value
 *   and returning converted value
 *
 * @throws "Incompatible units" if units are incompatible
 *
 * @example
 * // Converting large array of numbers with the same units
 * // into other units
 * var converter = Qty.swiftConverter("m/h", "ft/s");
 * var convertedSerie = largeSerie.map(converter);
 *
 */
function swiftConverter(srcUnits, dstUnits) {
  var srcQty = Qty(srcUnits);
  var dstQty = Qty(dstUnits);

  if (srcQty.eq(dstQty)) {
    return identity;
  }

  var convert;
  if (!srcQty.isTemperature()) {
    convert = function(value) {
      return value * srcQty.baseScalar / dstQty.baseScalar;
    };
  }
  else {
    convert = function(value) {
      // TODO Not optimized
      return srcQty.mul(value).to(dstQty).scalar;
    };
  }

  return function converter(value) {
    var i, length, result;
    if (!Array.isArray(value)) {
      return convert(value);
    }
    else {
      length = value.length;
      result = [];
      for (i = 0; i < length; i++) {
        result.push(convert(value[i]));
      }
      return result;
    }
  };
}

var baseUnitCache = {};

function toBaseUnits(numerator,denominator) {
  var num = [];
  var den = [];
  var q = 1;
  var unit;
  for (var i = 0; i < numerator.length; i++) {
    unit = numerator[i];
    if (PREFIX_VALUES[unit]) {
      // workaround to fix
      // 0.1 * 0.1 => 0.010000000000000002
      q = mulSafe(q, PREFIX_VALUES[unit]);
    }
    else {
      if (UNIT_VALUES[unit]) {
        q *= UNIT_VALUES[unit].scalar;

        if (UNIT_VALUES[unit].numerator) {
          num.push(UNIT_VALUES[unit].numerator);
        }
        if (UNIT_VALUES[unit].denominator) {
          den.push(UNIT_VALUES[unit].denominator);
        }
      }
    }
  }
  for (var j = 0; j < denominator.length; j++) {
    unit = denominator[j];
    if (PREFIX_VALUES[unit]) {
      q /= PREFIX_VALUES[unit];
    }
    else {
      if (UNIT_VALUES[unit]) {
        q /= UNIT_VALUES[unit].scalar;

        if (UNIT_VALUES[unit].numerator) {
          den.push(UNIT_VALUES[unit].numerator);
        }
        if (UNIT_VALUES[unit].denominator) {
          num.push(UNIT_VALUES[unit].denominator);
        }
      }
    }
  }

  // Flatten
  num = num.reduce(function(a,b) {
    return a.concat(b);
  }, []);
  den = den.reduce(function(a,b) {
    return a.concat(b);
  }, []);

  return Qty({"scalar": q, "numerator": num, "denominator": den});
}

Qty.parse = globalParse;

Qty.getUnits = getUnits;
Qty.getAliases = getAliases;

Qty.mulSafe = mulSafe;
Qty.divSafe = divSafe;

Qty.getKinds = getKinds;

Qty.swiftConverter = swiftConverter;

Qty.Error = QtyError;

assign(Qty.prototype, {
  // Returns new instance with units of this
  add: function(other) {
    if (isString(other)) {
      other = Qty(other);
    }

    if (!this.isCompatible(other)) {
      throwIncompatibleUnits(this.units(), other.units());
    }

    if (this.isTemperature() && other.isTemperature()) {
      throw new QtyError("Cannot add two temperatures");
    }
    else if (this.isTemperature()) {
      return addTempDegrees(this, other);
    }
    else if (other.isTemperature()) {
      return addTempDegrees(other, this);
    }

    return Qty({"scalar": this.scalar + other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator});
  },

  sub: function(other) {
    if (isString(other)) {
      other = Qty(other);
    }

    if (!this.isCompatible(other)) {
      throwIncompatibleUnits(this.units(), other.units());
    }

    if (this.isTemperature() && other.isTemperature()) {
      return subtractTemperatures(this,other);
    }
    else if (this.isTemperature()) {
      return subtractTempDegrees(this,other);
    }
    else if (other.isTemperature()) {
      throw new QtyError("Cannot subtract a temperature from a differential degree unit");
    }

    return Qty({"scalar": this.scalar - other.to(this).scalar, "numerator": this.numerator, "denominator": this.denominator});
  },

  mul: function(other) {
    if (isNumber(other)) {
      return Qty({"scalar": mulSafe(this.scalar, other), "numerator": this.numerator, "denominator": this.denominator});
    }
    else if (isString(other)) {
      other = Qty(other);
    }

    if ((this.isTemperature() || other.isTemperature()) && !(this.isUnitless() || other.isUnitless())) {
      throw new QtyError("Cannot multiply by temperatures");
    }

    // Quantities should be multiplied with same units if compatible, with base units else
    var op1 = this;
    var op2 = other;

    // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den
    // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC"
    if (op1.isCompatible(op2) && op1.signature !== 400) {
      op2 = op2.to(op1);
    }
    var numdenscale = cleanTerms(op1.numerator, op1.denominator, op2.numerator, op2.denominator);

    return Qty({"scalar": mulSafe(op1.scalar, op2.scalar, numdenscale[2]), "numerator": numdenscale[0], "denominator": numdenscale[1]});
  },

  div: function(other) {
    if (isNumber(other)) {
      if (other === 0) {
        throw new QtyError("Divide by zero");
      }
      return Qty({"scalar": this.scalar / other, "numerator": this.numerator, "denominator": this.denominator});
    }
    else if (isString(other)) {
      other = Qty(other);
    }

    if (other.scalar === 0) {
      throw new QtyError("Divide by zero");
    }

    if (other.isTemperature()) {
      throw new QtyError("Cannot divide with temperatures");
    }
    else if (this.isTemperature() && !other.isUnitless()) {
      throw new QtyError("Cannot divide with temperatures");
    }

    // Quantities should be multiplied with same units if compatible, with base units else
    var op1 = this;
    var op2 = other;

    // so as not to confuse results, multiplication and division between temperature degrees will maintain original unit info in num/den
    // multiplication and division between deg[CFRK] can never factor each other out, only themselves: "degK*degC/degC^2" == "degK/degC"
    if (op1.isCompatible(op2) && op1.signature !== 400) {
      op2 = op2.to(op1);
    }
    var numdenscale = cleanTerms(op1.numerator, op1.denominator, op2.denominator, op2.numerator);

    return Qty({"scalar": mulSafe(op1.scalar, numdenscale[2]) / op2.scalar, "numerator": numdenscale[0], "denominator": numdenscale[1]});
  },

  // Returns a Qty that is the inverse of this Qty,
  inverse: function() {
    if (this.isTemperature()) {
      throw new QtyError("Cannot divide with temperatures");
    }
    if (this.scalar === 0) {
      throw new QtyError("Divide by zero");
    }
    return Qty({"scalar": 1 / this.scalar, "numerator": this.denominator, "denominator": this.numerator});
  }
});

function cleanTerms(num1, den1, num2, den2) {
  function notUnity(val) {
    return val !== UNITY;
  }

  num1 = num1.filter(notUnity);
  num2 = num2.filter(notUnity);
  den1 = den1.filter(notUnity);
  den2 = den2.filter(notUnity);

  var combined = {};

  function combineTerms(terms, direction) {
    var k;
    var prefix;
    var prefixValue;
    for (var i = 0; i < terms.length; i++) {
      if (PREFIX_VALUES[terms[i]]) {
        k = terms[i + 1];
        prefix = terms[i];
        prefixValue = PREFIX_VALUES[prefix];
        i++;
      }
      else {
        k = terms[i];
        prefix = null;
        prefixValue = 1;
      }
      if (k && k !== UNITY) {
        if (combined[k]) {
          combined[k][0] += direction;
          var combinedPrefixValue = combined[k][2] ? PREFIX_VALUES[combined[k][2]] : 1;
          combined[k][direction === 1 ? 3 : 4] *= divSafe(prefixValue, combinedPrefixValue);
        }
        else {
          combined[k] = [direction, k, prefix, 1, 1];
        }
      }
    }
  }

  combineTerms(num1, 1);
  combineTerms(den1, -1);
  combineTerms(num2, 1);
  combineTerms(den2, -1);

  var num = [];
  var den = [];
  var scale = 1;

  for (var prop in combined) {
    if (combined.hasOwnProperty(prop)) {
      var item = combined[prop];
      var n;
      if (item[0] > 0) {
        for (n = 0; n < item[0]; n++) {
          num.push(item[2] === null ? item[1] : [item[2], item[1]]);
        }
      }
      else if (item[0] < 0) {
        for (n = 0; n < -item[0]; n++) {
          den.push(item[2] === null ? item[1] : [item[2], item[1]]);
        }
      }
      scale *= divSafe(item[3], item[4]);
    }
  }

  if (num.length === 0) {
    num = UNITY_ARRAY;
  }
  if (den.length === 0) {
    den = UNITY_ARRAY;
  }

  // Flatten
  num = num.reduce(function(a,b) {
    return a.concat(b);
  }, []);
  den = den.reduce(function(a,b) {
    return a.concat(b);
  }, []);

  return [num, den, scale];
}

assign(Qty.prototype, {
  eq: function(other) {
    return this.compareTo(other) === 0;
  },

  lt: function(other) {
    return this.compareTo(other) === -1;
  },

  lte: function(other) {
    return this.eq(other) || this.lt(other);
  },

  gt: function(other) {
    return this.compareTo(other) === 1;
  },

  gte: function(other) {
    return this.eq(other) || this.gt(other);
  },

  // Compare two Qty objects. Throws an exception if they are not of compatible types.
  // Comparisons are done based on the value of the quantity in base SI units.
  //
  // NOTE: We cannot compare inverses as that breaks the general compareTo contract:
  //   if a.compareTo(b) < 0 then b.compareTo(a) > 0
  //   if a.compareTo(b) == 0 then b.compareTo(a) == 0
  //
  //   Since "10S" == ".1ohm" (10 > .1) and "10ohm" == ".1S" (10 > .1)
  //     Qty("10S").inverse().compareTo("10ohm") == -1
  //     Qty("10ohm").inverse().compareTo("10S") == -1
  //
  //   If including inverses in the sort is needed, I suggest writing: Qty.sort(qtyArray,units)
  compareTo: function(other) {
    if (isString(other)) {
      return this.compareTo(Qty(other));
    }
    if (!this.isCompatible(other)) {
      throwIncompatibleUnits(this.units(), other.units());
    }
    if (this.baseScalar < other.baseScalar) {
      return -1;
    }
    else if (this.baseScalar === other.baseScalar) {
      return 0;
    }
    else if (this.baseScalar > other.baseScalar) {
      return 1;
    }
  },

  // Return true if quantities and units match
  // Unit("100 cm").same(Unit("100 cm"))  # => true
  // Unit("100 cm").same(Unit("1 m"))     # => false
  same: function(other) {
    return (this.scalar === other.scalar) && (this.units() === other.units());
  }
});

assign(Qty.prototype, {
  // returns true if no associated units
  // false, even if the units are "unitless" like 'radians, each, etc'
  isUnitless: function() {
    return [this.numerator, this.denominator].every(function(item) {
      return compareArray(item, UNITY_ARRAY);
    });
  },

  /*
  check to see if units are compatible, but not the scalar part
  this check is done by comparing signatures for performance reasons
  if passed a string, it will create a unit object with the string and then do the comparison
  this permits a syntax like:
  unit =~ "mm"
  if you want to do a regexp on the unit string do this ...
  unit.units =~ /regexp/
  */
  isCompatible: function(other) {
    if (isString(other)) {
      return this.isCompatible(Qty(other));
    }

    if (!(isQty(other))) {
      return false;
    }

    if (other.signature !== undefined) {
      return this.signature === other.signature;
    }
    else {
      return false;
    }
  },

  /*
  check to see if units are inverse of each other, but not the scalar part
  this check is done by comparing signatures for performance reasons
  if passed a string, it will create a unit object with the string and then do the comparison
  this permits a syntax like:
  unit =~ "mm"
  if you want to do a regexp on the unit string do this ...
  unit.units =~ /regexp/
  */
  isInverse: function(other) {
    return this.inverse().isCompatible(other);
  },

  // Returns 'true' if the Unit is represented in base units
  isBase: function() {
    if (this._isBase !== undefined) {
      return this._isBase;
    }
    if (this.isDegrees() && this.numerator[0].match(/<(kelvin|temp-K)>/)) {
      this._isBase = true;
      return this._isBase;
    }

    this.numerator.concat(this.denominator).forEach(function(item) {
      if (item !== UNITY && BASE_UNITS.indexOf(item) === -1 ) {
        this._isBase = false;
      }
    }, this);
    if (this._isBase === false) {
      return this._isBase;
    }
    this._isBase = true;
    return this._isBase;
  }
});

function NestedMap() {}

NestedMap.prototype.get = function(keys) {

  // Allows to pass key1, key2, ... instead of [key1, key2, ...]
  if (arguments.length > 1) {
    // Slower with Firefox but faster with Chrome than
    // Array.prototype.slice.call(arguments)
    // See http://jsperf.com/array-apply-versus-array-prototype-slice-call
    keys = Array.apply(null, arguments);
  }

  return keys.reduce(function(map, key, index) {
    if (map) {

      var childMap = map[key];

      if (index === keys.length - 1) {
        return childMap ? childMap.data : undefined;
      }
      else {
        return childMap;
      }
    }
  },
  this);
};

NestedMap.prototype.set = function(keys, value) {

  if (arguments.length > 2) {
    keys = Array.prototype.slice.call(arguments, 0, -1);
    value = arguments[arguments.length - 1];
  }

  return keys.reduce(function(map, key, index) {

    var childMap = map[key];
    if (childMap === undefined) {
      childMap = map[key] = {};
    }

    if (index === keys.length - 1) {
      childMap.data = value;
      return value;
    }
    else {
      return childMap;
    }
  }, this);
};

/**
 * Default formatter
 *
 * @param {number} scalar - scalar value
 * @param {string} units - units as string
 *
 * @returns {string} formatted result
 */
function defaultFormatter(scalar, units) {
  return (scalar + " " + units).trim();
}

/**
 *
 * Configurable Qty default formatter
 *
 * @type {function}
 *
 * @param {number} scalar
 * @param {string} units
 *
 * @returns {string} formatted result
 */
Qty.formatter = defaultFormatter;

assign(Qty.prototype, {

  // returns the 'unit' part of the Unit object without the scalar
  units: function() {
    if (this._units !== undefined) {
      return this._units;
    }

    var numIsUnity = compareArray(this.numerator, UNITY_ARRAY);
    var denIsUnity = compareArray(this.denominator, UNITY_ARRAY);
    if (numIsUnity && denIsUnity) {
      this._units = "";
      return this._units;
    }

    var numUnits = stringifyUnits(this.numerator);
    var denUnits = stringifyUnits(this.denominator);
    this._units = numUnits + (denIsUnity ? "" : ("/" + denUnits));
    return this._units;
  },

  /**
   * Stringifies the quantity
   * Deprecation notice: only units parameter is supported.
   *
   * @param {(number|string|Qty)} targetUnitsOrMaxDecimalsOrPrec -
   *                              target units if string,
   *                              max number of decimals if number,
   *                              passed to #toPrec before converting if Qty
   *
   * @param {number=} maxDecimals - Maximum number of decimals of
   *                                formatted output
   *
   * @returns {string} reparseable quantity as string
   */
  toString: function(targetUnitsOrMaxDecimalsOrPrec, maxDecimals) {
    var targetUnits;
    if (isNumber(targetUnitsOrMaxDecimalsOrPrec)) {
      targetUnits = this.units();
      maxDecimals = targetUnitsOrMaxDecimalsOrPrec;
    }
    else if (isString(targetUnitsOrMaxDecimalsOrPrec)) {
      targetUnits = targetUnitsOrMaxDecimalsOrPrec;
    }
    else if (isQty(targetUnitsOrMaxDecimalsOrPrec)) {
      return this.toPrec(targetUnitsOrMaxDecimalsOrPrec).toString(maxDecimals);
    }

    var out = this.to(targetUnits);

    var outScalar = maxDecimals !== undefined ? round(out.scalar, maxDecimals) : out.scalar;
    out = (outScalar + " " + out.units()).trim();
    return out;
  },

  /**
   * Format the quantity according to optional passed target units
   * and formatter
   *
   * @param {string} [targetUnits=current units] -
   *                 optional units to convert to before formatting
   *
   * @param {function} [formatter=Qty.formatter] -
   *                   delegates formatting to formatter callback.
   *                   formatter is called back with two parameters (scalar, units)
   *                   and should return formatted result.
   *                   If unspecified, formatting is delegated to default formatter
   *                   set to Qty.formatter
   *
   * @example
   * var roundingAndLocalizingFormatter = function(scalar, units) {
   *   // localize or limit scalar to n max decimals for instance
   *   // return formatted result
   * };
   * var qty = Qty('1.1234 m');
   * qty.format(); // same units, default formatter => "1.234 m"
   * qty.format("cm"); // converted to "cm", default formatter => "123.45 cm"
   * qty.format(roundingAndLocalizingFormatter); // same units, custom formatter => "1,2 m"
   * qty.format("cm", roundingAndLocalizingFormatter); // convert to "cm", custom formatter => "123,4 cm"
   *
   * @returns {string} quantity as string
   */
  format: function(targetUnits, formatter) {
    if (arguments.length === 1) {
      if (typeof targetUnits === "function") {
        formatter = targetUnits;
        targetUnits = undefined;
      }
    }

    formatter = formatter || Qty.formatter;
    var targetQty = this.to(targetUnits);
    return formatter.call(this, targetQty.scalar, targetQty.units());
  }
});

var stringifiedUnitsCache = new NestedMap();
/**
 * Returns a string representing a normalized unit array
 *
 * @param {string[]} units Normalized unit array
 * @returns {string} String representing passed normalized unit array and
 *   suitable for output
 *
 */
function stringifyUnits(units) {

  var stringified = stringifiedUnitsCache.get(units);
  if (stringified) {
    return stringified;
  }

  var isUnity = compareArray(units, UNITY_ARRAY);
  if (isUnity) {
    stringified = "1";
  }
  else {
    stringified = simplify(getOutputNames(units)).join("*");
  }

  // Cache result
  stringifiedUnitsCache.set(units, stringified);

  return stringified;
}

function getOutputNames(units) {
  var unitNames = [], token, tokenNext;
  for (var i = 0; i < units.length; i++) {
    token = units[i];
    tokenNext = units[i + 1];
    if (PREFIX_VALUES[token]) {
      unitNames.push(OUTPUT_MAP[token] + OUTPUT_MAP[tokenNext]);
      i++;
    }
    else {
      unitNames.push(OUTPUT_MAP[token]);
    }
  }
  return unitNames;
}

function simplify(units) {
  // this turns ['s','m','s'] into ['s2','m']

  var unitCounts = units.reduce(function(acc, unit) {
    var unitCounter = acc[unit];
    if (!unitCounter) {
      acc.push(unitCounter = acc[unit] = [unit, 0]);
    }

    unitCounter[1]++;

    return acc;
  }, []);

  return unitCounts.map(function(unitCount) {
    return unitCount[0] + (unitCount[1] > 1 ? unitCount[1] : "");
  });
}

Qty.version = "1.8.0";

export { Qty as default };


================================================
FILE: lib/JSLitmus.js
================================================
// JSLitmus.js
//
// Copyright (c) 2010, Robert Kieffer, http://broofa.com
// Available under MIT license (http://en.wikipedia.org/wiki/MIT_License)

(function() {
  // Private methods and state

  // Get platform info but don't go crazy trying to recognize everything
  // that's out there.  This is just for the major platforms and OSes.
  var platform = 'unknown platform', ua = navigator.userAgent;

  // Detect OS
  var oses = ['Windows','iPhone OS','(Intel |PPC )?Mac OS X','Linux'].join('|');
  var pOS = new RegExp('((' + oses + ') [^ \);]*)').test(ua) ? RegExp.$1 : null;
  if (!pOS) pOS = new RegExp('((' + oses + ')[^ \);]*)').test(ua) ? RegExp.$1 : null;

  // Detect browser
  var pName = /(Chrome|MSIE|Safari|Opera|Firefox)/.test(ua) ? RegExp.$1 : null;

  // Detect version
  var vre = new RegExp('(Version|' + pName + ')[ \/]([^ ;]*)');
  var pVersion = (pName && vre.test(ua)) ? RegExp.$2 : null;
  var platform = (pOS && pName && pVersion) ? pName + ' '  + pVersion + ' on ' + pOS : 'unknown platform';

  /**
  * A smattering of methods that are needed to implement the JSLitmus testbed.
  */
  var jsl = {
    /**
    * Enhanced version of escape()
    */
    escape: function(s) {
      s = s.replace(/,/g, '\\,');
      s = escape(s);
      s = s.replace(/\+/g, '%2b');
      s = s.replace(/ /g, '+');
      return s;
    },

    /**
    * Get an element by ID.
    */
    $: function(id) {
      return document.getElementById(id);
    },

    /**
    * Null function
    */
    F: function() {},

    /**
    * Set the status shown in the UI
    */
    status: function(msg) {
      var el = jsl.$('jsl_status');
      if (el) el.innerHTML = msg || '';
    },

    /**
    * Convert a number to an abbreviated string like, "15K" or "10M"
    */
    toLabel: function(n) {
      if (n == Infinity) {
        return 'Infinity';
      } else if (n > 1e9) {
        n = Math.round(n/1e8);
        return n/10 + 'B';
      } else if (n > 1e6) {
        n = Math.round(n/1e5);
        return n/10 + 'M';
      } else if (n > 1e3) {
        n = Math.round(n/1e2);
        return n/10 + 'K';
      }
      return n;
    },

    /**
    * Copy properties from src to dst
    */
    extend: function(dst, src) {
      for (var k in src) dst[k] = src[k]; return dst;
    },

    /**
    * Like Array.join(), but for the key-value pairs in an object
    */
    join: function(o, delimit1, delimit2) {
      if (o.join) return o.join(delimit1);  // If it's an array
      var pairs = [];
      for (var k in o) pairs.push(k + delimit1 + o[k]);
      return pairs.join(delimit2);
    },

    /**
    * Array#indexOf isn't supported in IE, so we use this as a cross-browser solution
    */
    indexOf: function(arr, o) {
      if (arr.indexOf) return arr.indexOf(o);
      for (var i = 0; i < this.length; i++) if (arr[i] === o) return i;
      return -1;
    }
  };

  /**
  * Test manages a single test (created with
  * JSLitmus.test())
  *
  * @private
  */
  var Test = function (name, f) {
    if (!f) throw new Error('Undefined test function');
    if (!/function[^\(]*\(([^,\)]*)/.test(f.toString())) {
      throw new Error('"' + name + '" test: Test is not a valid Function object');
    }
    this.loopArg = RegExp.$1;
    this.name = name;
    this.f = f;
  };

  jsl.extend(Test, /** @lends Test */ {
    /** Calibration tests for establishing iteration loop overhead */
    CALIBRATIONS: [
      new Test('calibrating loop', function(count) {while (count--);}),
      new Test('calibrating function', jsl.F)
    ],

    /**
    * Run calibration tests.  Returns true if calibrations are not yet
    * complete (in which case calling code should run the tests yet again).
    * onCalibrated - Callback to invoke when calibrations have finished
    */
    calibrate: function(onCalibrated) {
      for (var i = 0; i < Test.CALIBRATIONS.length; i++) {
        var cal = Test.CALIBRATIONS[i];
        if (cal.running) return true;
        if (!cal.count) {
          cal.isCalibration = true;
          cal.onStop = onCalibrated;
          //cal.MIN_TIME = .1; // Do calibrations quickly
          cal.run(2e4);
          return true;
        }
      }
      return false;
    }
  });

  jsl.extend(Test.prototype, {/** @lends Test.prototype */
    /** Initial number of iterations */
    INIT_COUNT: 10,
    /** Max iterations allowed (i.e. used to detect bad looping functions) */
    MAX_COUNT: 1e9,
    /** Minimum time a test should take to get valid results (secs) */
    MIN_TIME: .5,

    /** Callback invoked when test state changes */
    onChange: jsl.F,

    /** Callback invoked when test is finished */
    onStop: jsl.F,

    /**
     * Reset test state
     */
    reset: function() {
      delete this.count;
      delete this.time;
      delete this.running;
      delete this.error;
    },

    /**
    * Run the test (in a timeout). We use a timeout to make sure the browser
    * has a chance to finish rendering any UI changes we've made, like
    * updating the status message.
    */
    run: function(count) {
      count = count || this.INIT_COUNT;
      jsl.status(this.name + ' x ' + count);
      this.running = true;
      var me = this;
      setTimeout(function() {me._run(count);}, 200);
    },

    /**
     * The nuts and bolts code that actually runs a test
     */
    _run: function(count) {
      var me = this;

      // Make sure calibration tests have run
      if (!me.isCalibration && Test.calibrate(function() {me.run(count);})) return;
      this.error = null;

      try {
        var start, f = this.f, now, i = count;

        // Start the timer
        start = new Date();

        // Now for the money shot.  If this is a looping function ...
        if (this.loopArg) {
          // ... let it do the iteration itself
          f(count);
        } else {
          // ... otherwise do the iteration for it
          while (i--) f();
        }

        // Get time test took (in secs)
        this.time = Math.max(1,new Date() - start)/1000;

        // Store iteration count and per-operation time taken
        this.count = count;
        this.period = this.time/count;

        // Do we need to do another run?
        this.running = this.time <= this.MIN_TIME;

        // ... if so, compute how many times we should iterate
        if (this.running) {
          // Bump the count to the nearest power of 2
          var x = this.MIN_TIME/this.time;
          var pow = Math.pow(2, Math.max(1, Math.ceil(Math.log(x)/Math.log(2))));
          count *= pow;
          if (count > this.MAX_COUNT) {
            throw new Error('Max count exceeded.  If this test uses a looping function, make sure the iteration loop is working properly.');
          }
        }
      } catch (e) {
        // Exceptions are caught and displayed in the test UI
        this.reset();
        this.error = e;
      }

      // Figure out what to do next
      if (this.running) {
        me.run(count);
      } else {
        jsl.status('');
        me.onStop(me);
      }

      // Finish up
      this.onChange(this);
    },

    /**
    * Get the number of operations per second for this test.
    *
    * @param normalize if true, iteration loop overhead taken into account
    */
    getHz: function(/**Boolean*/ normalize) {
      var p = this.period;

      // Adjust period based on the calibration test time
      if (normalize && !this.isCalibration) {
        var cal = Test.CALIBRATIONS[this.loopArg ? 0 : 1];

        // If the period is within 20% of the calibration time, then zero the
        // it out
        p = p < cal.period*1.2 ? 0 : p - cal.period;
      }

      return Math.round(1/p);
    },

    /**
    * Get a friendly string describing the test
    */
    toString: function() {
      return this.name + ' - '  + this.time/this.count + ' secs';
    }
  });

  // CSS we need for the UI
  var STYLESHEET = '<style> \
    #jslitmus {font-family:sans-serif; font-size: 12px;} \
    #jslitmus a {text-decoration: none;} \
    #jslitmus a:hover {text-decoration: underline;} \
    #jsl_status { \
      margin-top: 10px; \
      font-size: 10px; \
      color: #888; \
    } \
    A IMG  {border:none} \
    #test_results { \
      margin-top: 10px; \
      font-size: 12px; \
      font-family: sans-serif; \
      border-collapse: collapse; \
      border-spacing: 0px; \
    } \
    #test_results th, #test_results td { \
      border: solid 1px #ccc; \
      vertical-align: top; \
      padding: 3px; \
    } \
    #test_results th { \
      vertical-align: bottom; \
      background-color: #ccc; \
      padding: 1px; \
      font-size: 10px; \
    } \
    #test_results #test_platform { \
      color: #444; \
      text-align:center; \
    } \
    #test_results .test_row { \
      color: #006; \
      cursor: pointer; \
    } \
    #test_results .test_nonlooping { \
      border-left-style: dotted; \
      border-left-width: 2px; \
    } \
    #test_results .test_looping { \
      border-left-style: solid; \
      border-left-width: 2px; \
    } \
    #test_results .test_name {white-space: nowrap;} \
    #test_results .test_pending { \
    } \
    #test_results .test_running { \
      font-style: italic; \
    } \
    #test_results .test_done {} \
    #test_results .test_done { \
      text-align: right; \
      font-family: monospace; \
    } \
    #test_results .test_error {color: #600;} \
    #test_results .test_error .error_head {font-weight:bold;} \
    #test_results .test_error .error_body {font-size:85%;} \
    #test_results .test_row:hover td { \
      background-color: #ffc; \
      text-decoration: underline; \
    } \
    #chart { \
      margin: 10px 0px; \
      width: 250px; \
    } \
    #chart img { \
      border: solid 1px #ccc; \
      margin-bottom: 5px; \
    } \
    #chart #tiny_url { \
      height: 40px; \
      width: 250px; \
    } \
    #jslitmus_credit { \
      font-size: 10px; \
      color: #888; \
      margin-top: 8px; \
    } \
    </style>';

  // HTML markup for the UI
  var MARKUP = '<div id="jslitmus"> \
      <button onclick="JSLitmus.runAll(event)">Run Tests</button> \
      <button id="stop_button" disabled="disabled" onclick="JSLitmus.stop()">Stop Tests</button> \
      <br \> \
      <br \> \
      <input type="checkbox" style="vertical-align: middle" id="test_normalize" checked="checked" onchange="JSLitmus.renderAll()""> Normalize results \
      <table id="test_results"> \
        <colgroup> \
          <col /> \
          <col width="100" /> \
        </colgroup> \
        <tr><th id="test_platform" colspan="2">' + platform + '</th></tr> \
        <tr><th>Test</th><th>Ops/sec</th></tr> \
        <tr id="test_row_template" class="test_row" style="display:none"> \
          <td class="test_name"></td> \
          <td class="test_result">Ready</td> \
        </tr> \
      </table> \
      <div id="jsl_status"></div> \
      <div id="chart" style="display:none"> \
      <a id="chart_link" target="_blank"><img id="chart_image"></a> \
      TinyURL (for chart): \
      <iframe id="tiny_url" frameBorder="0" scrolling="no" src=""></iframe> \
      </div> \
      <a id="jslitmus_credit" title="JSLitmus home page" href="http://code.google.com/p/jslitmus" target="_blank">Powered by JSLitmus</a> \
    </div>';

  /**
   * The public API for creating and running tests
   */
  window.JSLitmus = {
    /** The list of all tests that have been registered with JSLitmus.test */
    _tests: [],
    /** The queue of tests that need to be run */
    _queue: [],

    /**
    * The parsed query parameters the current page URL.  This is provided as a
    * convenience for test functions - it's not used by JSLitmus proper
    */
    params: {},

    /**
     * Initialize
     */
    _init: function() {
      // Parse query params into JSLitmus.params[] hash
      var match = (location + '').match(/([^?#]*)(#.*)?$/);
      if (match) {
        var pairs = match[1].split('&');
        for (var i = 0; i < pairs.length; i++) {
          var pair = pairs[i].split('=');
          if (pair.length > 1) {
            var key = pair.shift();
            var value = pair.length > 1 ? pair.join('=') : pair[0];
            this.params[key] = value;
          }
        }
      }

      // Write out the stylesheet.  We have to do this here because IE
      // doesn't honor sheets written after the document has loaded.
      document.write(STYLESHEET);

      // Setup the rest of the UI once the document is loaded
      if (window.addEventListener) {
        window.addEventListener('load', this._setup, false);
      } else if (document.addEventListener) {
        document.addEventListener('load', this._setup, false);
      } else if (window.attachEvent) {
        window.attachEvent('onload', this._setup);
      }

      return this;
    },

    /**
     * Set up the UI
     */
    _setup: function() {
      var el = jsl.$('jslitmus_container');
      if (!el) document.body.appendChild(el = document.createElement('div'));

      el.innerHTML = MARKUP;

      // Render the UI for all our tests
      for (var i=0; i < JSLitmus._tests.length; i++)
        JSLitmus.renderTest(JSLitmus._tests[i]);
    },

    /**
     * (Re)render all the test results
     */
    renderAll: function() {
      for (var i = 0; i < JSLitmus._tests.length; i++)
        JSLitmus.renderTest(JSLitmus._tests[i]);
      JSLitmus.renderChart();
    },

    /**
     * (Re)render the chart graphics
     */
    renderChart: function() {
      var url = JSLitmus.chartUrl();
      jsl.$('chart_link').href = url;
      jsl.$('chart_image').src = url;
      jsl.$('chart').style.display = '';

      // Update the tiny URL
      jsl.$('tiny_url').src = 'http://tinyurl.com/api-create.php?url='+escape(url);
    },

    /**
     * (Re)render the results for a specific test
     */
    renderTest: function(test) {
      // Make a new row if needed
      if (!test._row) {
        var trow = jsl.$('test_row_template');
        if (!trow) return;

        test._row = trow.cloneNode(true);
        test._row.style.display = '';
        test._row.id = '';
        test._row.onclick = function() {JSLitmus._queueTest(test);};
        test._row.title = 'Run ' + test.name + ' test';
        trow.parentNode.appendChild(test._row);
        test._row.cells[0].innerHTML = test.name;
      }

      var cell = test._row.cells[1];
      var cns = [test.loopArg ? 'test_looping' : 'test_nonlooping'];

      if (test.error) {
        cns.push('test_error');
        cell.innerHTML =
        '<div class="error_head">' + test.error + '</div>' +
        '<ul class="error_body"><li>' +
          jsl.join(test.error, ': ', '</li><li>') +
          '</li></ul>';
      } else {
        if (test.running) {
          cns.push('test_running');
          cell.innerHTML = 'running';
        } else if (jsl.indexOf(JSLitmus._queue, test) >= 0) {
          cns.push('test_pending');
          cell.innerHTML = 'pending';
        } else if (test.count) {
          cns.push('test_done');
          var hz = test.getHz(jsl.$('test_normalize').checked);
          cell.innerHTML = hz != Infinity ? hz : '&infin;';
          cell.title = 'Looped ' + test.count + ' times in ' + test.time + ' seconds';
        } else {
          cell.innerHTML = 'ready';
        }
      }
      cell.className = cns.join(' ');
    },

    /**
     * Create a new test
     */
    test: function(name, f) {
      // Create the Test object
      var test = new Test(name, f);
      JSLitmus._tests.push(test);

      // Re-render if the test state changes
      test.onChange = JSLitmus.renderTest;

      // Run the next test if this one finished
      test.onStop = function(test) {
        if (JSLitmus.onTestFinish) JSLitmus.onTestFinish(test);
        JSLitmus.currentTest = null;
        JSLitmus._nextTest();
      };

      // Render the new test
      this.renderTest(test);
    },

    /**
     * Add all tests to the run queue
     */
    runAll: function(e) {
      e = e || window.event;
      var reverse = e && e.shiftKey, len = JSLitmus._tests.length;
      for (var i = 0; i < len; i++) {
        JSLitmus._queueTest(JSLitmus._tests[!reverse ? i : (len - i - 1)]);
      }
    },

    /**
     * Remove all tests from the run queue.  The current test has to finish on
     * it's own though
     */
    stop: function() {
      while (JSLitmus._queue.length) {
        var test = JSLitmus._queue.shift();
        JSLitmus.renderTest(test);
      }
    },

    /**
     * Run the next test in the run queue
     */
    _nextTest: function() {
      if (!JSLitmus.currentTest) {
        var test = JSLitmus._queue.shift();
        if (test) {
          jsl.$('stop_button').disabled = false;
          JSLitmus.currentTest = test;
          test.run();
          JSLitmus.renderTest(test);
          if (JSLitmus.onTestStart) JSLitmus.onTestStart(test);
        } else {
          jsl.$('stop_button').disabled = true;
          JSLitmus.renderChart();
        }
      }
    },

    /**
     * Add a test to the run queue
     */
    _queueTest: function(test) {
      if (jsl.indexOf(JSLitmus._queue, test) >= 0) return;
      JSLitmus._queue.push(test);
      JSLitmus.renderTest(test);
      JSLitmus._nextTest();
    },

    /**
     * Generate a Google Chart URL that shows the data for all tests
     */
    chartUrl: function() {
      var n = JSLitmus._tests.length, markers = [], data = [];
      var d, min = 0, max = -1e10;
      var normalize = jsl.$('test_normalize').checked;

      // Gather test data
      for (var i=0; i < JSLitmus._tests.length; i++) {
        var test = JSLitmus._tests[i];
        if (test.count) {
          var hz = test.getHz(normalize);
          var v = hz != Infinity ? hz : 0;
          data.push(v);
          markers.push('t' + jsl.escape(test.name + '(' + jsl.toLabel(hz)+ ')') + ',000000,0,' +
            markers.length + ',10');
          max = Math.max(v, max);
        }
      }
      if (markers.length <= 0) return null;

      // Build chart title
      var title = document.getElementsByTagName('title');
      title = (title && title.length) ? title[0].innerHTML : null;
      var chart_title = [];
      if (title) chart_title.push(title);
      chart_title.push('Ops/sec (' + platform + ')');

      // Build labels
      var labels = [jsl.toLabel(min), jsl.toLabel(max)];

      var w = 250, bw = 15;
      var bs = 5;
      var h = markers.length*(bw + bs) + 30 + chart_title.length*20;

      var params = {
        chtt: escape(chart_title.join('|')),
        chts: '000000,10',
        cht: 'bhg',                     // chart type
        chd: 't:' + data.join(','),     // data set
        chds: min + ',' + max,          // max/min of data
        chxt: 'x',                      // label axes
        chxl: '0:|' + labels.join('|'), // labels
        chsp: '0,1',
        chm: markers.join('|'),         // test names
        chbh: [bw, 0, bs].join(','),    // bar widths
        // chf: 'bg,lg,0,eeeeee,0,eeeeee,.5,ffffff,1', // gradient
        chs: w + 'x' + h
      };
      return 'http://chart.apis.google.com/chart?' + jsl.join(params, '=', '&');
    }
  };

  JSLitmus._init();
})();


================================================
FILE: lib/jasmine-3.4.0/boot.js
================================================
/**
 Starting with version 2.0, this file "boots" Jasmine, performing all of the necessary initialization before executing the loaded environment and all of a project's specs. This file should be loaded after `jasmine.js` and `jasmine_html.js`, but before any project source files or spec files are loaded. Thus this file can also be used to customize Jasmine for a project.

 If a project is using Jasmine via the standalone distribution, this file can be customized directly. If a project is using Jasmine via the [Ruby gem][jasmine-gem], this file can be copied into the support directory via `jasmine copy_boot_js`. Other environments (e.g., Python) will have different mechanisms.

 The location of `boot.js` can be specified and/or overridden in `jasmine.yml`.

 [jasmine-gem]: http://github.com/pivotal/jasmine-gem
 */

(function() {

  /**
   * ## Require &amp; Instantiate
   *
   * Require Jasmine's core files. Specifically, this requires and attaches all of Jasmine's code to the `jasmine` reference.
   */
  window.jasmine = jasmineRequire.core(jasmineRequire);

  /**
   * Since this is being run in a browser and the results should populate to an HTML page, require the HTML-specific Jasmine code, injecting the same reference.
   */
  jasmineRequire.html(jasmine);

  /**
   * Create the Jasmine environment. This is used to run all specs in a project.
   */
  var env = jasmine.getEnv();

  /**
   * ## The Global Interface
   *
   * Build up the functions that will be exposed as the Jasmine public interface. A project can customize, rename or alias any of these functions as desired, provided the implementation remains unchanged.
   */
  var jasmineInterface = jasmineRequire.interface(jasmine, env);

  /**
   * Add all of the Jasmine global/public interface to the global scope, so a project can use the public interface directly. For example, calling `describe` in specs instead of `jasmine.getEnv().describe`.
   */
  extend(window, jasmineInterface);

  /**
   * ## Runner Parameters
   *
   * More browser specific code - wrap the query string in an object and to allow for getting/setting parameters from the runner user interface.
   */

  var queryString = new jasmine.QueryString({
    getWindowLocation: function() { return window.location; }
  });

  var filterSpecs = !!queryString.getParam("spec");

  var config = {
    failFast: queryString.getParam("failFast"),
    oneFailurePerSpec: queryString.getParam("oneFailurePerSpec"),
    hideDisabled: queryString.getParam("hideDisabled")
  };

  var random = queryString.getParam("random");

  if (random !== undefined && random !== "") {
    config.random = random;
  }

  var seed = queryString.getParam("seed");
  if (seed) {
    config.seed = seed;
  }

  /**
   * ## Reporters
   * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
   */
  var htmlReporter = new jasmine.HtmlReporter({
    env: env,
    navigateWithNewParam: function(key, value) { return queryString.navigateWithNewParam(key, value); },
    addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
    getContainer: function() { return document.body; },
    createElement: function() { return document.createElement.apply(document, arguments); },
    createTextNode: function() { return document.createTextNode.apply(document, arguments); },
    timer: new jasmine.Timer(),
    filterSpecs: filterSpecs
  });

  /**
   * The `jsApiReporter` also receives spec results, and is used by any environment that needs to extract the results  from JavaScript.
   */
  env.addReporter(jasmineInterface.jsApiReporter);
  env.addReporter(htmlReporter);

  /**
   * Filter which specs will be run by matching the start of the full name against the `spec` query param.
   */
  var specFilter = new jasmine.HtmlSpecFilter({
    filterString: function() { return queryString.getParam("spec"); }
  });

  config.specFilter = function(spec) {
    return specFilter.matches(spec.getFullName());
  };

  env.configure(config);

  /**
   * Setting up timing functions to be able to be overridden. Certain browsers (Safari, IE 8, phantomjs) require this hack.
   */
  window.setTimeout = window.setTimeout;
  window.setInterval = window.setInterval;
  window.clearTimeout = window.clearTimeout;
  window.clearInterval = window.clearInterval;

  /**
   * ## Execution
   *
   * Replace the browser window's `onload`, ensure it's called, and then run all of the loaded specs. This includes initializing the `HtmlReporter` instance and then executing the loaded Jasmine environment. All of this will happen after all of the specs are loaded.
   */
  var currentWindowOnload = window.onload;

  window.onload = function() {
    if (currentWindowOnload) {
      currentWindowOnload();
    }
    htmlReporter.initialize();
    env.execute();
  };

  /**
   * Helper function for readability above.
   */
  function extend(destination, source) {
    for (var property in source) destination[property] = source[property];
    return destination;
  }

}());


================================================
FILE: lib/jasmine-3.4.0/jasmine-html.js
================================================
/*
Copyright (c) 2008-2019 Pivotal Labs

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
jasmineRequire.html = function(j$) {
  j$.ResultsNode = jasmineRequire.ResultsNode();
  j$.HtmlReporter = jasmineRequire.HtmlReporter(j$);
  j$.QueryString = jasmineRequire.QueryString();
  j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter();
};

jasmineRequire.HtmlReporter = function(j$) {
  function ResultsStateBuilder() {
    this.topResults = new j$.ResultsNode({}, '', null);
    this.currentParent = this.topResults;
    this.specsExecuted = 0;
    this.failureCount = 0;
    this.pendingSpecCount = 0;
  }

  ResultsStateBuilder.prototype.suiteStarted = function(result) {
    this.currentParent.addChild(result, 'suite');
    this.currentParent = this.currentParent.last();
  };

  ResultsStateBuilder.prototype.suiteDone = function(result) {
    this.currentParent.updateResult(result);
    if (this.currentParent !== this.topResults) {
      this.currentParent = this.currentParent.parent;
    }

    if (result.status === 'failed') {
      this.failureCount++;
    }
  };

  ResultsStateBuilder.prototype.specStarted = function(result) {
  };

  ResultsStateBuilder.prototype.specDone = function(result) {
    this.currentParent.addChild(result, 'spec');

    if (result.status !== 'excluded') {
      this.specsExecuted++;
    }

    if (result.status === 'failed') {
      this.failureCount++;
    }

    if (result.status == 'pending') {
      this.pendingSpecCount++;
    }
  };



  function HtmlReporter(options) {
    var config = function() { return (options.env && options.env.configuration()) || {}; },
      getContainer = options.getContainer,
      createElement = options.createElement,
      createTextNode = options.createTextNode,
      navigateWithNewParam = options.navigateWithNewParam || function() {},
      addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
      filterSpecs = options.filterSpecs,
      timer = options.timer || j$.noopTimer,
      htmlReporterMain,
      symbols,
      deprecationWarnings = [];

    this.initialize = function() {
      clearPrior();
      htmlReporterMain = createDom('div', {className: 'jasmine_html-reporter'},
        createDom('div', {className: 'jasmine-banner'},
          createDom('a', {className: 'jasmine-title', href: 'http://jasmine.github.io/', target: '_blank'}),
          createDom('span', {className: 'jasmine-version'}, j$.version)
        ),
        createDom('ul', {className: 'jasmine-symbol-summary'}),
        createDom('div', {className: 'jasmine-alert'}),
        createDom('div', {className: 'jasmine-results'},
          createDom('div', {className: 'jasmine-failures'})
        )
      );
      getContainer().appendChild(htmlReporterMain);
    };

    var totalSpecsDefined;
    this.jasmineStarted = function(options) {
      totalSpecsDefined = options.totalSpecsDefined || 0;
      timer.start();
    };

    var summary = createDom('div', {className: 'jasmine-summary'});

    var stateBuilder = new ResultsStateBuilder();

    this.suiteStarted = function(result) {
      stateBuilder.suiteStarted(result);
    };

    this.suiteDone = function(result) {
      stateBuilder.suiteDone(result);

      if (result.status === 'failed') {
        failures.push(failureDom(result));
      }
      addDeprecationWarnings(result);
    };

    this.specStarted = function(result) {
      stateBuilder.specStarted(result);
    };

    var failures = [];
    this.specDone = function(result) {
      stateBuilder.specDone(result);

      if(noExpectations(result) && typeof console !== 'undefined' && typeof console.error !== 'undefined') {
        console.error('Spec \'' + result.fullName + '\' has no expectations.');
      }

      if (!symbols){
        symbols = find('.jasmine-symbol-summary');
      }

      symbols.appendChild(createDom('li', {
          className: this.displaySpecInCorrectFormat(result),
          id: 'spec_' + result.id,
          title: result.fullName
        }
      ));

      if (result.status === 'failed') {
        failures.push(failureDom(result));
      }

      addDeprecationWarnings(result);
    };

    this.displaySpecInCorrectFormat = function(result) {
      return noExpectations(result) ? 'jasmine-empty' : this.resultStatus(result.status);
    };

    this.resultStatus = function(status) {
      if(status === 'excluded') {
        return config().hideDisabled ? 'jasmine-excluded-no-display' : 'jasmine-excluded';
      }
      return 'jasmine-' + status;
    };

    this.jasmineDone = function(doneResult) {
      var banner = find('.jasmine-banner');
      var alert = find('.jasmine-alert');
      var order = doneResult && doneResult.order;
      var i;
      alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));

      banner.appendChild(optionsMenu(config()));

      if (stateBuilder.specsExecuted < totalSpecsDefined) {
        var skippedMessage = 'Ran ' + stateBuilder.specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';
        var skippedLink = addToExistingQueryString('spec', '');
        alert.appendChild(
          createDom('span', {className: 'jasmine-bar jasmine-skipped'},
            createDom('a', {href: skippedLink, title: 'Run all specs'}, skippedMessage)
          )
        );
      }
      var statusBarMessage = '';
      var statusBarClassName = 'jasmine-overall-result jasmine-bar ';
      var globalFailures = (doneResult && doneResult.failedExpectations) || [];
      var failed = stateBuilder.failureCount + globalFailures.length > 0;

      if (totalSpecsDefined > 0 || failed) {
        statusBarMessage += pluralize('spec', stateBuilder.specsExecuted) + ', ' + pluralize('failure', stateBuilder.failureCount);
        if (stateBuilder.pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', stateBuilder.pendingSpecCount); }
      }

      if (doneResult.overallStatus === 'passed') {
        statusBarClassName += ' jasmine-passed ';
      } else if (doneResult.overallStatus === 'incomplete') {
        statusBarClassName += ' jasmine-incomplete ';
        statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage;
      } else {
        statusBarClassName += ' jasmine-failed ';
      }

      var seedBar;
      if (order && order.random) {
        seedBar = createDom('span', {className: 'jasmine-seed-bar'},
          ', randomized with seed ',
          createDom('a', {title: 'randomized with seed ' + order.seed, href: seedHref(order.seed)}, order.seed)
        );
      }

      alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar));

      var errorBarClassName = 'jasmine-bar jasmine-errored';
      var afterAllMessagePrefix = 'AfterAll ';

      for(i = 0; i < globalFailures.length; i++) {
        alert.appendChild(createDom('span', {className: errorBarClassName}, globalFailureMessage(globalFailures[i])));
      }

      function globalFailureMessage(failure) {
        if (failure.globalErrorType === 'load') {
          var prefix = 'Error during loading: ' + failure.message;

          if (failure.filename) {
            return prefix + ' in ' + failure.filename + ' line ' + failure.lineno;
          } else {
            return prefix;
          }
        } else {
          return afterAllMessagePrefix + failure.message;
        }
      }

      addDeprecationWarnings(doneResult);

      var warningBarClassName = 'jasmine-bar jasmine-warning';
      for(i = 0; i < deprecationWarnings.length; i++) {
        var warning = deprecationWarnings[i];
        alert.appendChild(createDom('span', {className: warningBarClassName}, 'DEPRECATION: ' + warning));
      }

      var results = find('.jasmine-results');
      results.appendChild(summary);

      summaryList(stateBuilder.topResults, summary);

      if (failures.length) {
        alert.appendChild(
          createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'},
            createDom('span', {}, 'Spec List | '),
            createDom('a', {className: 'jasmine-failures-menu', href: '#'}, 'Failures')));
        alert.appendChild(
          createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-failure-list'},
            createDom('a', {className: 'jasmine-spec-list-menu', href: '#'}, 'Spec List'),
            createDom('span', {}, ' | Failures ')));

        find('.jasmine-failures-menu').onclick = function() {
          setMenuModeTo('jasmine-failure-list');
        };
        find('.jasmine-spec-list-menu').onclick = function() {
          setMenuModeTo('jasmine-spec-list');
        };

        setMenuModeTo('jasmine-failure-list');

        var failureNode = find('.jasmine-failures');
        for (i = 0; i < failures.length; i++) {
          failureNode.appendChild(failures[i]);
        }
      }
    };

    return this;

    function failureDom(result) {
      var failure =
        createDom('div', {className: 'jasmine-spec-detail jasmine-failed'},
          failureDescription(result, stateBuilder.currentParent),
          createDom('div', {className: 'jasmine-messages'})
        );
      var messages = failure.childNodes[1];

      for (var i = 0; i < result.failedExpectations.length; i++) {
        var expectation = result.failedExpectations[i];
        messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message));
        messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack));
      }

      return failure;
    }

    function summaryList(resultsTree, domParent) {
      var specListNode;
      for (var i = 0; i < resultsTree.children.length; i++) {
        var resultNode = resultsTree.children[i];
        if (filterSpecs && !hasActiveSpec(resultNode)) {
          continue;
        }
        if (resultNode.type === 'suite') {
          var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id},
            createDom('li', {className: 'jasmine-suite-detail jasmine-' + resultNode.result.status},
              createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description)
            )
          );

          summaryList(resultNode, suiteListNode);
          domParent.appendChild(suiteListNode);
        }
        if (resultNode.type === 'spec') {
          if (domParent.getAttribute('class') !== 'jasmine-specs') {
            specListNode = createDom('ul', {className: 'jasmine-specs'});
            domParent.appendChild(specListNode);
          }
          var specDescription = resultNode.result.description;
          if(noExpectations(resultNode.result)) {
            specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription;
          }
          if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') {
            specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason;
          }
          specListNode.appendChild(
            createDom('li', {
                className: 'jasmine-' + resultNode.result.status,
                id: 'spec-' + resultNode.result.id
              },
              createDom('a', {href: specHref(resultNode.result)}, specDescription)
            )
          );
        }
      }
    }

    function optionsMenu(config) {
      var optionsMenuDom = createDom('div', { className: 'jasmine-run-options' },
        createDom('span', { className: 'jasmine-trigger' }, 'Options'),
        createDom('div', { className: 'jasmine-payload' },
          createDom('div', { className: 'jasmine-stop-on-failure' },
            createDom('input', {
              className: 'jasmine-fail-fast',
              id: 'jasmine-fail-fast',
              type: 'checkbox'
            }),
            createDom('label', { className: 'jasmine-label', 'for': 'jasmine-fail-fast' }, 'stop execution on spec failure')),
          createDom('div', { className: 'jasmine-throw-failures' },
            createDom('input', {
              className: 'jasmine-throw',
              id: 'jasmine-throw-failures',
              type: 'checkbox'
            }),
            createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')),
          createDom('div', { className: 'jasmine-random-order' },
            createDom('input', {
              className: 'jasmine-random',
              id: 'jasmine-random-order',
              type: 'checkbox'
            }),
            createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')),
          createDom('div', { className: 'jasmine-hide-disabled' },
            createDom('input', {
              className: 'jasmine-disabled',
              id: 'jasmine-hide-disabled',
              type: 'checkbox'
            }),
            createDom('label', { className: 'jasmine-label', 'for': 'jasmine-hide-disabled' }, 'hide disabled tests'))
        )
      );

      var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast');
      failFastCheckbox.checked = config.failFast;
      failFastCheckbox.onclick = function() {
        navigateWithNewParam('failFast', !config.failFast);
      };

      var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures');
      throwCheckbox.checked = config.oneFailurePerSpec;
      throwCheckbox.onclick = function() {
        navigateWithNewParam('throwFailures', !config.oneFailurePerSpec);
      };

      var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order');
      randomCheckbox.checked = config.random;
      randomCheckbox.onclick = function() {
        navigateWithNewParam('random', !config.random);
      };

      var hideDisabled = optionsMenuDom.querySelector('#jasmine-hide-disabled');
      hideDisabled.checked = config.hideDisabled;
      hideDisabled.onclick = function() {
        navigateWithNewParam('hideDisabled', !config.hideDisabled);
      };

      var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'),
        optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'),
        isOpen = /\bjasmine-open\b/;

      optionsTrigger.onclick = function() {
        if (isOpen.test(optionsPayload.className)) {
          optionsPayload.className = optionsPayload.className.replace(isOpen, '');
        } else {
          optionsPayload.className += ' jasmine-open';
        }
      };

      return optionsMenuDom;
    }

    function failureDescription(result, suite) {
      var wrapper = createDom('div', {className: 'jasmine-description'},
        createDom('a', {title: result.description, href: specHref(result)}, result.description)
      );
      var suiteLink;

      while (suite && suite.parent) {
        wrapper.insertBefore(createTextNode(' > '), wrapper.firstChild);
        suiteLink = createDom('a', {href: suiteHref(suite)}, suite.result.description);
        wrapper.insertBefore(suiteLink, wrapper.firstChild);

        suite = suite.parent;
      }

      return wrapper;
    }

    function suiteHref(suite) {
      var els = [];

      while (suite && suite.parent) {
        els.unshift(suite.result.description);
        suite = suite.parent;
      }

      return addToExistingQueryString('spec', els.join(' '));
    }

    function addDeprecationWarnings(result) {
      if (result && result.deprecationWarnings) {
        for(var i = 0; i < result.deprecationWarnings.length; i++) {
          var warning = result.deprecationWarnings[i].message;
          if (!j$.util.arrayContains(warning)) {
            deprecationWarnings.push(warning);
          }
        }
    
Download .txt
gitextract_9tyqrl1_/

├── .eslintrc
├── .gitignore
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── Makefile
├── README.md
├── RELEASE
├── SpecRunner.html
├── bench/
│   ├── index.html
│   └── main.js
├── bin/
│   ├── bench.rb
│   ├── prepare-release.sh
│   └── release.sh
├── build/
│   ├── quantities.js
│   └── quantities.mjs
├── lib/
│   ├── JSLitmus.js
│   ├── jasmine-3.4.0/
│   │   ├── boot.js
│   │   ├── jasmine-html.js
│   │   ├── jasmine.css
│   │   └── jasmine.js
│   └── require.js
├── package.json
├── spec/
│   ├── blns.json
│   ├── quantitiesSpec.js
│   └── support/
│       └── jasmine.json
└── src/
    ├── quantities/
    │   ├── comparators.js
    │   ├── constructor.js
    │   ├── conversion.js
    │   ├── definitions.js
    │   ├── error.js
    │   ├── format.js
    │   ├── global-api.js
    │   ├── kind.js
    │   ├── nested-map.js
    │   ├── operators.js
    │   ├── parse.js
    │   ├── predicates.js
    │   ├── signature.js
    │   ├── temperature.js
    │   └── utils.js
    └── quantities.js
Download .txt
SYMBOL INDEX (275 symbols across 19 files)

FILE: bench/main.js
  function bench (line 15) | function bench(name, func) {
  function benchCurrent (line 19) | function benchCurrent(name, func) {
  function benchPrevious (line 23) | function benchPrevious(name, func) {
  function compare (line 27) | function compare(name, func) {

FILE: build/quantities.js
  function isString (line 37) | function isString(value) {
  function isNumber (line 54) | function isNumber(value) {
  function identity (line 62) | function identity(value) {
  function uniq (line 74) | function uniq(strings) {
  function compareArray (line 81) | function compareArray(array1, array2) {
  function assign (line 98) | function assign(target, properties) {
  function mulSafe (line 112) | function mulSafe() {
  function divSafe (line 131) | function divSafe(num, den) {
  function round (line 150) | function round(val, decimals) {
  function getFractional (line 154) | function getFractional(num) {
  function QtyError (line 174) | function QtyError() {
  function throwIncompatibleUnits (line 194) | function throwIncompatibleUnits(left, right) {
  function validateUnitDefinition (line 492) | function validateUnitDefinition(unitDef, definition) {
  function getUnits (line 552) | function getUnits(kind) {
  function getAliases (line 592) | function getAliases(unitName) {
  function unitSignature (line 610) | function unitSignature() {
  function unitSignatureVector (line 628) | function unitSignatureVector() {
  function parse (line 690) | function parse(val) {
  function parseUnits (line 795) | function parseUnits(units) {
  function globalParse (line 835) | function globalParse(value) {
  function isQty (line 855) | function isQty(value) {
  function Qty (line 859) | function Qty(initValue, initUnits) {
  function assertValidConstructorArgs (line 921) | function assertValidConstructorArgs(value, units) {
  function isDefinitionObject (line 946) | function isDefinitionObject(value) {
  function updateBaseScalar (line 950) | function updateBaseScalar() {
  function getKinds (line 1023) | function getKinds() {
  function subtractTemperatures (line 1047) | function subtractTemperatures(lhs,rhs) {
  function subtractTempDegrees (line 1054) | function subtractTempDegrees(temp,deg) {
  function addTempDegrees (line 1059) | function addTempDegrees(temp,deg) {
  function getDegreeUnits (line 1064) | function getDegreeUnits(units) {
  function toDegrees (line 1082) | function toDegrees(src,dst) {
  function toDegK (line 1106) | function toDegK(qty) {
  function toTemp (line 1131) | function toTemp(src,dst) {
  function toTempK (line 1154) | function toTempK(qty) {
  function swiftConverter (line 1336) | function swiftConverter(srcUnits, dstUnits) {
  function toBaseUnits (line 1375) | function toBaseUnits(numerator,denominator) {
  function cleanTerms (line 1564) | function cleanTerms(num1, den1, num2, den2) {
  function NestedMap (line 1780) | function NestedMap() {}
  function defaultFormatter (line 1840) | function defaultFormatter(scalar, units) {
  function stringifyUnits (line 1962) | function stringifyUnits(units) {
  function getOutputNames (line 1983) | function getOutputNames(units) {
  function simplify (line 1999) | function simplify(units) {

FILE: build/quantities.mjs
  function isString (line 31) | function isString(value) {
  function isNumber (line 48) | function isNumber(value) {
  function identity (line 56) | function identity(value) {
  function uniq (line 68) | function uniq(strings) {
  function compareArray (line 75) | function compareArray(array1, array2) {
  function assign (line 92) | function assign(target, properties) {
  function mulSafe (line 106) | function mulSafe() {
  function divSafe (line 125) | function divSafe(num, den) {
  function round (line 144) | function round(val, decimals) {
  function getFractional (line 148) | function getFractional(num) {
  function QtyError (line 168) | function QtyError() {
  function throwIncompatibleUnits (line 188) | function throwIncompatibleUnits(left, right) {
  function validateUnitDefinition (line 486) | function validateUnitDefinition(unitDef, definition) {
  function getUnits (line 546) | function getUnits(kind) {
  function getAliases (line 586) | function getAliases(unitName) {
  function unitSignature (line 604) | function unitSignature() {
  function unitSignatureVector (line 622) | function unitSignatureVector() {
  function parse (line 684) | function parse(val) {
  function parseUnits (line 789) | function parseUnits(units) {
  function globalParse (line 829) | function globalParse(value) {
  function isQty (line 849) | function isQty(value) {
  function Qty (line 853) | function Qty(initValue, initUnits) {
  function assertValidConstructorArgs (line 915) | function assertValidConstructorArgs(value, units) {
  function isDefinitionObject (line 940) | function isDefinitionObject(value) {
  function updateBaseScalar (line 944) | function updateBaseScalar() {
  function getKinds (line 1017) | function getKinds() {
  function subtractTemperatures (line 1041) | function subtractTemperatures(lhs,rhs) {
  function subtractTempDegrees (line 1048) | function subtractTempDegrees(temp,deg) {
  function addTempDegrees (line 1053) | function addTempDegrees(temp,deg) {
  function getDegreeUnits (line 1058) | function getDegreeUnits(units) {
  function toDegrees (line 1076) | function toDegrees(src,dst) {
  function toDegK (line 1100) | function toDegK(qty) {
  function toTemp (line 1125) | function toTemp(src,dst) {
  function toTempK (line 1148) | function toTempK(qty) {
  function swiftConverter (line 1330) | function swiftConverter(srcUnits, dstUnits) {
  function toBaseUnits (line 1369) | function toBaseUnits(numerator,denominator) {
  function cleanTerms (line 1558) | function cleanTerms(num1, den1, num2, den2) {
  function NestedMap (line 1774) | function NestedMap() {}
  function defaultFormatter (line 1834) | function defaultFormatter(scalar, units) {
  function stringifyUnits (line 1956) | function stringifyUnits(units) {
  function getOutputNames (line 1977) | function getOutputNames(units) {
  function simplify (line 1993) | function simplify(units) {

FILE: lib/jasmine-3.4.0/boot.js
  function extend (line 131) | function extend(destination, source) {

FILE: lib/jasmine-3.4.0/jasmine-html.js
  function ResultsStateBuilder (line 31) | function ResultsStateBuilder() {
  function HtmlReporter (line 76) | function HtmlReporter(options) {
  function HtmlSpecFilter (line 534) | function HtmlSpecFilter(options) {
  function ResultsNode (line 547) | function ResultsNode(result, type, parent) {
  function QueryString (line 571) | function QueryString(options) {

FILE: lib/jasmine-3.4.0/jasmine.js
  function getJasmineRequire (line 41) | function getJasmineRequire() {
  function anyMatch (line 499) | function anyMatch(pattern, lines) {
  function callerFile (line 528) | function callerFile() {
  function Spec (line 551) | function Spec(attrs) {
  function Order (line 746) | function Order(options) {
  function Env (line 797) | function Env(options) {
  function JsApiReporter (line 1761) | function JsApiReporter(options) {
  function Any (line 1881) | function Any(expectedObject) {
  function Anything (line 1931) | function Anything() {}
  function ArrayContaining (line 1945) | function ArrayContaining(sample) {
  function ArrayWithExactContents (line 1973) | function ArrayWithExactContents(sample) {
  function Empty (line 2005) | function Empty() {}
  function Falsy (line 2031) | function Falsy() {}
  function NotEmpty (line 2046) | function NotEmpty() {}
  function ObjectContaining (line 2073) | function ObjectContaining(sample) {
  function getPrototype (line 2077) | function getPrototype(obj) {
  function hasProperty (line 2089) | function hasProperty(obj, property) {
  function StringMatching (line 2123) | function StringMatching(expected) {
  function Truthy (line 2144) | function Truthy() {}
  function CallTracker (line 2162) | function CallTracker() {
  function messageChannelImpl (line 2276) | function messageChannelImpl(global, setTimeout) {
  function getClearStack (line 2313) | function getClearStack(global) {
  function Clock (line 2353) | function Clock(global, delayedFunctionSchedulerFactory, mockDate) {
  function FakeTimeout (line 2517) | function FakeTimeout() {}
  function DelayedFunctionScheduler (line 2531) | function DelayedFunctionScheduler() {
  function ExpectationFailed (line 2697) | function ExpectationFailed() {}
  function ExceptionFormatter (line 2710) | function ExceptionFormatter(options) {
  function Expectation (line 2806) | function Expectation(options) {
  function AsyncExpectation (line 2844) | function AsyncExpectation(options) {
  function wrapSyncCompare (line 2884) | function wrapSyncCompare(name, matcherFactory) {
  function wrapAsyncCompare (line 2891) | function wrapAsyncCompare(name, matcherFactory) {
  function addCoreMatchers (line 2905) | function addCoreMatchers(prototype, matchers, wrapper) {
  function addFilter (line 2912) | function addFilter(source, filter) {
  function negatedFailureMessage (line 2918) | function negatedFailureMessage(result, matcherName, args, util) {
  function negate (line 2933) | function negate(result) {
  function defaultNegativeCompare (line 2940) | function defaultNegativeCompare() {
  function defaultNegativeCompare (line 2951) | function defaultNegativeCompare() {
  function ContextAddingFilter (line 2960) | function ContextAddingFilter(message) {
  function ExpectationFilterChain (line 2985) | function ExpectationFilterChain(maybeFilter, prev) {
  function buildExpectationResult (line 3033) | function buildExpectationResult(options) {
  function Expector (line 3098) | function Expector(options) {
  function defaultMessage (line 3128) | function defaultMessage() {
  function generateErrorMsg (line 3179) | function generateErrorMsg(domain, usage) {
  function GlobalErrors (line 3191) | function GlobalErrors(global) {
  function prefix (line 3304) | function prefix(passed) {
  function prefix (line 3374) | function prefix(passed) {
  function defaultFormatter (line 3429) | function defaultFormatter (actual, expected, path) {
  function isAsymmetric (line 3493) | function isAsymmetric(obj) {
  function asymmetricMatch (line 3497) | function asymmetricMatch(a, b, customTesters, diffBuilder) {
  function equals (line 3523) | function equals(a, b, customTesters, diffBuilder) {
  function eq (line 3532) | function eq(a, b, aStack, bStack, customTesters, diffBuilder) {
  function keys (line 3829) | function keys(obj, isArray) {
  function has (line 3859) | function has(obj, key) {
  function isFunction (line 3863) | function isFunction(obj) {
  function objectKeysAreDifferentFormatter (line 3867) | function objectKeysAreDifferentFormatter(actual, expected, path) {
  function constructorsAreDifferentFormatter (line 3889) | function constructorsAreDifferentFormatter(actual, expected, path) {
  function actualArrayIsLongerFormatter (line 3900) | function actualArrayIsLongerFormatter(actual, expected, path) {
  function formatKeyValuePairs (line 3907) | function formatKeyValuePairs(obj) {
  function nothing (line 3924) | function nothing() {
  function ObjectPath (line 3949) | function ObjectPath(components) {
  function formatPropertyAccess (line 3969) | function formatPropertyAccess(prop) {
  function map (line 3981) | function map(array, fn) {
  function isValidIdentifier (line 3989) | function isValidIdentifier(string) {
  function toBe (line 4022) | function toBe(util) {
  function toBeCloseTo (line 4053) | function toBeCloseTo() {
  function toBeDefined (line 4088) | function toBeDefined() {
  function toBeFalsy (line 4109) | function toBeFalsy() {
  function toBeGreaterThan (line 4131) | function toBeGreaterThan() {
  function toBeGreaterThanOrEqual (line 4154) | function toBeGreaterThanOrEqual() {
  function toBeLessThan (line 4176) | function toBeLessThan() {
  function toBeLessThanOrEqual (line 4199) | function toBeLessThanOrEqual() {
  function toBeNaN (line 4221) | function toBeNaN() {
  function toBeNegativeInfinity (line 4250) | function toBeNegativeInfinity() {
  function toBeNull (line 4279) | function toBeNull() {
  function toBePositiveInfinity (line 4300) | function toBePositiveInfinity() {
  function toBeTruthy (line 4329) | function toBeTruthy() {
  function toBeUndefined (line 4350) | function toBeUndefined() {
  function toContain (line 4373) | function toContain(util, customEqualityTesters) {
  function toEqual (line 4398) | function toEqual(util, customEqualityTesters) {
  function toHaveBeenCalled (line 4433) | function toHaveBeenCalled() {
  function toHaveBeenCalledBefore (line 4472) | function toHaveBeenCalledBefore() {
  function toHaveBeenCalledTimes (line 4533) | function toHaveBeenCalledTimes() {
  function toHaveBeenCalledWith (line 4574) | function toHaveBeenCalledWith(util, customEqualityTesters) {
  function toHaveClass (line 4617) | function toHaveClass(util, customEqualityTesters) {
  function isElement (line 4631) | function isElement(maybeEl) {
  function toMatch (line 4653) | function toMatch() {
  function toThrow (line 4685) | function toThrow(util) {
  function toThrowError (line 4747) | function toThrowError () {
  function pass (line 4880) | function pass(message) {
  function fail (line 4887) | function fail(message) {
  function toThrowMatching (line 4908) | function toThrowMatching() {
  function thrownDescription (line 4940) | function thrownDescription(thrown) {
  function pass (line 4949) | function pass(message) {
  function fail (line 4956) | function fail(message) {
  function MockDate (line 4967) | function MockDate(global) {
  function PrettyPrinter (line 5051) | function PrettyPrinter() {
  function hasCustomToString (line 5058) | function hasCustomToString(value) {
  function truncate (line 5327) | function truncate(s, maxlen) {
  function MaxCharsReachedError (line 5336) | function MaxCharsReachedError() {
  function keys (line 5343) | function keys(obj, isArray) {
  function StopExecutionError (line 5380) | function StopExecutionError() {}
  function once (line 5384) | function once(fn) {
  function emptyFn (line 5396) | function emptyFn() {}
  function QueueRunner (line 5398) | function QueueRunner(attrs) {
  function runNext (line 5465) | function runNext() {
  function onException (line 5525) | function onException(e) {
  function onPromiseRejection (line 5530) | function onPromiseRejection(e) {
  function ReportDispatcher (line 5568) | function ReportDispatcher(methods, queueRunnerFactory) {
  function Spy (line 5994) | function Spy(name, originalFn, customStrategies) {
  function SpyStrategyDispatcher (line 6080) | function SpyStrategyDispatcher(strategyArgs) {
  function StrategyDict (line 6107) | function StrategyDict(strategyFactory) {
  function SpyFactory (line 6145) | function SpyFactory(getCustomStrategies) {
  function SpyRegistry (line 6193) | function SpyRegistry(options) {
  function SpyStrategy (line 6345) | function SpyStrategy(options) {
  function createCustomPlan (line 6367) | function createCustomPlan(factory) {
  function StackTrace (line 6472) | function StackTrace(error) {
  function tryParseFrames (line 6508) | function tryParseFrames(lines) {
  function first (line 6538) | function first(items, fn) {
  function extractMessage (line 6550) | function extractMessage(message, stackLines) {
  function messagePrefixLength (line 6561) | function messagePrefixLength(message, stackLines) {
  function Suite (line 6582) | function Suite(attrs) {
  function removeFns (line 6667) | function removeFns(queueableFns) {
  function isFailure (line 6755) | function isFailure(args) {
  function Timer (line 6772) | function Timer(options) {
  function TreeProcessor (line 6797) | function TreeProcessor(attrs) {
  function UserContext (line 7013) | function UserContext() {

FILE: lib/require.js
  function H (line 7) | function H(b){return"[object Function]"===L.call(b)}
  function I (line 7) | function I(b){return"[object Array]"===L.call(b)}
  function y (line 7) | function y(b,c){if(b){var d;for(d=0;d<b.length&&(!b[d]||!c(b[d],d,b));d+...
  function M (line 7) | function M(b,c){if(b){var d;for(d=b.length-1;-1<d&&(!b[d]||!c(b[d],d,b))...
  function s (line 7) | function s(b,c){return ga.call(b,c)}
  function l (line 7) | function l(b,c){return s(b,c)&&b[c]}
  function F (line 7) | function F(b,c){for(var d in b)if(s(b,d)&&c(b[d],d))break}
  function Q (line 7) | function Q(b,c,d,h){c&&F(c,function(c,j){if(d||!s(b,j))h&&"string"!==typ...
  function u (line 8) | function u(b,c){return function(){return c.apply(b,arguments)}}
  function aa (line 8) | function aa(b){throw b;}
  function ba (line 8) | function ba(b){if(!b)return b;var c=Z;y(b.split("."),function(b){c=c[b]}...
  function A (line 8) | function A(b,c,d,h){c=Error(c+"\nhttp://requirejs.org/docs/errors.html#"...
  function ha (line 8) | function ha(b){function c(a,f,b){var e,m,c,g,d,h,j,i=f&&f.split("/");e=i...

FILE: src/quantities/constructor.js
  function isQty (line 18) | function isQty(value) {
  function Qty (line 22) | function Qty(initValue, initUnits) {
  function assertValidConstructorArgs (line 84) | function assertValidConstructorArgs(value, units) {
  function isDefinitionObject (line 109) | function isDefinitionObject(value) {
  function updateBaseScalar (line 113) | function updateBaseScalar() {

FILE: src/quantities/conversion.js
  function swiftConverter (line 171) | function swiftConverter(srcUnits, dstUnits) {
  function toBaseUnits (line 210) | function toBaseUnits(numerator,denominator) {

FILE: src/quantities/definitions.js
  function validateUnitDefinition (line 298) | function validateUnitDefinition(unitDef, definition) {
  function getUnits (line 358) | function getUnits(kind) {
  function getAliases (line 398) | function getAliases(unitName) {

FILE: src/quantities/error.js
  function QtyError (line 5) | function QtyError() {
  function throwIncompatibleUnits (line 25) | function throwIncompatibleUnits(left, right) {

FILE: src/quantities/format.js
  function defaultFormatter (line 24) | function defaultFormatter(scalar, units) {
  function stringifyUnits (line 146) | function stringifyUnits(units) {
  function getOutputNames (line 167) | function getOutputNames(units) {
  function simplify (line 183) | function simplify(units) {

FILE: src/quantities/kind.js
  function getKinds (line 62) | function getKinds() {

FILE: src/quantities/nested-map.js
  function NestedMap (line 1) | function NestedMap() {}

FILE: src/quantities/operators.js
  function cleanTerms (line 131) | function cleanTerms(num1, den1, num2, den2) {

FILE: src/quantities/parse.js
  function parse (line 37) | function parse(val) {
  function parseUnits (line 142) | function parseUnits(units) {
  function globalParse (line 182) | function globalParse(value) {

FILE: src/quantities/signature.js
  function unitSignature (line 14) | function unitSignature() {
  function unitSignatureVector (line 32) | function unitSignatureVector() {

FILE: src/quantities/temperature.js
  function subtractTemperatures (line 20) | function subtractTemperatures(lhs,rhs) {
  function subtractTempDegrees (line 27) | function subtractTempDegrees(temp,deg) {
  function addTempDegrees (line 32) | function addTempDegrees(temp,deg) {
  function getDegreeUnits (line 37) | function getDegreeUnits(units) {
  function toDegrees (line 55) | function toDegrees(src,dst) {
  function toDegK (line 79) | function toDegK(qty) {
  function toTemp (line 104) | function toTemp(src,dst) {
  function toTempK (line 127) | function toTempK(qty) {

FILE: src/quantities/utils.js
  function isString (line 8) | function isString(value) {
  function isNumber (line 25) | function isNumber(value) {
  function identity (line 33) | function identity(value) {
  function uniq (line 45) | function uniq(strings) {
  function compareArray (line 52) | function compareArray(array1, array2) {
  function assign (line 69) | function assign(target, properties) {
  function mulSafe (line 83) | function mulSafe() {
  function divSafe (line 102) | function divSafe(num, den) {
  function round (line 121) | function round(val, decimals) {
  function getFractional (line 125) | function getFractional(num) {
Condensed preview — 42 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (625K chars).
[
  {
    "path": ".eslintrc",
    "chars": 1463,
    "preview": "{\n  \"env\": {\n    \"browser\": true,\n    \"es6\": true\n  },\n  \"globals\": {\n    \"define\": true,\n    \"module\": true\n  },\n  pars"
  },
  {
    "path": ".gitignore",
    "chars": 71,
    "preview": ".envrc\nshell.nix\nsrc/.committed-quantities.js\n*.swp\nnode_modules/\ntags\n"
  },
  {
    "path": ".travis.yml",
    "chars": 68,
    "preview": "language: node_js\nnode_js:\n  - \"node\"\n  - \"lts/*\"\nscript: make test\n"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 4259,
    "preview": "Unreleased\n----------\n\n1.8.0 / 2023-08-21\n------------------\n\n### Added\n\n* Add \"Joules\" alias\n* Add some parts-per notat"
  },
  {
    "path": "LICENSE",
    "chars": 1148,
    "preview": "The MIT License (MIT)\nCopyright © 2006-2007 Kevin C. Olbrich\nCopyright © 2010-2016 LIM SAS (http://lim.eu) - Julien Sanc"
  },
  {
    "path": "Makefile",
    "chars": 975,
    "preview": "SPEC_DIR := spec\nSPECFILES := $(shell find $(SPEC_DIR) -iname '*.js')\nSOURCE_DIR := src\nSOURCES := $(shell find $(SOURCE"
  },
  {
    "path": "README.md",
    "chars": 12162,
    "preview": "# JS-quantities\n\n[![Build Status](https://travis-ci.org/gentooboontoo/js-quantities.png)](https://travis-ci.org/gentoobo"
  },
  {
    "path": "RELEASE",
    "chars": 6,
    "preview": "1.8.0\n"
  },
  {
    "path": "SpecRunner.html",
    "chars": 623,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>Jasmine Spec Runner</title>\n\n  <link rel=\"shortcut icon\""
  },
  {
    "path": "bench/index.html",
    "chars": 316,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n  <title>JS-quantities Benchmarks</title>\n  <script src=\"../lib/require.js\"></script>\n  <!"
  },
  {
    "path": "bench/main.js",
    "chars": 957,
    "preview": "/* global requirejs, JSLitmus */\nrequirejs.config({\n  baseUrl: \"../lib\",\n  paths: {\n    \"src\": \"../src\"\n  }\n});\n\nrequire"
  },
  {
    "path": "bin/bench.rb",
    "chars": 473,
    "preview": "#!/usr/bin/env ruby\n\nDir.chdir(File.join(File.dirname(__FILE__), '..'))\n\nport = ENV['PORT'] || '3000'\ncommit = ENV['COMM"
  },
  {
    "path": "bin/prepare-release.sh",
    "chars": 465,
    "preview": "#!/bin/bash\n\nif [[ $# != 1 ]]; then\n  echo \"Usage: $0 VERSION\"\n  exit 1\nfi\n\nVERSION=\"$1\"\nLOG_ENTRY=\"${VERSION} / $(date "
  },
  {
    "path": "bin/release.sh",
    "chars": 429,
    "preview": "#!/bin/bash\n\nset -e\n\nVERSION=$(cat RELEASE)\nTAG=v${VERSION}\n\ngit add CHANGELOG.md README.md package.json \\\n        RELEA"
  },
  {
    "path": "build/quantities.js",
    "chars": 68516,
    "preview": "/*\nThe MIT License (MIT)\nCopyright © 2006-2007 Kevin C. Olbrich\nCopyright © 2010-2016 LIM SAS (http://lim.eu) - Julien S"
  },
  {
    "path": "build/quantities.mjs",
    "chars": 64669,
    "preview": "/*\nThe MIT License (MIT)\nCopyright © 2006-2007 Kevin C. Olbrich\nCopyright © 2010-2016 LIM SAS (http://lim.eu) - Julien S"
  },
  {
    "path": "lib/JSLitmus.js",
    "chars": 19184,
    "preview": "// JSLitmus.js\n//\n// Copyright (c) 2010, Robert Kieffer, http://broofa.com\n// Available under MIT license (http://en.wik"
  },
  {
    "path": "lib/jasmine-3.4.0/boot.js",
    "chars": 5160,
    "preview": "/**\n Starting with version 2.0, this file \"boots\" Jasmine, performing all of the necessary initialization before executi"
  },
  {
    "path": "lib/jasmine-3.4.0/jasmine-html.js",
    "chars": 21020,
    "preview": "/*\nCopyright (c) 2008-2019 Pivotal Labs\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of"
  },
  {
    "path": "lib/jasmine-3.4.0/jasmine.css",
    "chars": 21088,
    "preview": "@charset \"UTF-8\";\nbody { overflow-y: scroll; }\n\n.jasmine_html-reporter { background-color: #eee; padding: 5px; margin: -"
  },
  {
    "path": "lib/jasmine-3.4.0/jasmine.js",
    "chars": 205952,
    "preview": "/*\nCopyright (c) 2008-2019 Pivotal Labs\n\nPermission is hereby granted, free of charge, to any person obtaining\na copy of"
  },
  {
    "path": "lib/require.js",
    "chars": 15060,
    "preview": "/*\n RequireJS 2.1.8 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.\n Available via the MIT or new BSD "
  },
  {
    "path": "package.json",
    "chars": 1202,
    "preview": "{\n  \"name\": \"js-quantities\",\n  \"main\": \"./build/quantities.js\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./build/quant"
  },
  {
    "path": "spec/blns.json",
    "chars": 22603,
    "preview": "[\n  \"\", \n  \"undefined\", \n  \"undef\", \n  \"null\", \n  \"NULL\", \n  \"(null)\", \n  \"nil\", \n  \"NIL\", \n  \"true\", \n  \"false\", \n  \"Tr"
  },
  {
    "path": "spec/quantitiesSpec.js",
    "chars": 50831,
    "preview": "/* global __dirname, describe, expect, it, require, beforeEach, afterEach */\nvar Qty;\n/*\n * Needed when run through jasm"
  },
  {
    "path": "spec/support/jasmine.json",
    "chars": 171,
    "preview": "{\n  \"spec_dir\": \"spec\",\n  \"spec_files\": [\n    \"**/*[sS]pec.js\"\n  ],\n  \"helpers\": [\n    \"helpers/**/*.js\"\n  ],\n  \"stopSpe"
  },
  {
    "path": "src/quantities/comparators.js",
    "chars": 1870,
    "preview": "import Qty from \"./constructor.js\";\nimport { assign, isString } from \"./utils.js\";\nimport { throwIncompatibleUnits } fro"
  },
  {
    "path": "src/quantities/constructor.js",
    "chars": 3382,
    "preview": "import { UNITY_ARRAY } from \"./definitions.js\";\nimport { unitSignature } from \"./signature.js\";\nimport parse from \"./par"
  },
  {
    "path": "src/quantities/conversion.js",
    "chars": 6801,
    "preview": "import Qty from \"./constructor.js\";\nimport { PREFIX_VALUES, UNIT_VALUES } from \"./definitions.js\";\nimport {toDegrees, to"
  },
  {
    "path": "src/quantities/definitions.js",
    "chars": 21170,
    "preview": "import { isNumber } from \"./utils.js\";\nimport QtyError from \"./error.js\";\n\nexport var UNITS = {\n  /* prefixes */\n  \"<goo"
  },
  {
    "path": "src/quantities/error.js",
    "chars": 785,
    "preview": "/**\n * Custom error type definition\n * @constructor\n */\nexport default function QtyError() {\n  var err;\n  if (!this) { /"
  },
  {
    "path": "src/quantities/format.js",
    "chars": 5611,
    "preview": "import Qty, { isQty } from \"./constructor.js\";\nimport {\n  PREFIX_VALUES,\n  OUTPUT_MAP,\n  UNITY_ARRAY\n} from \"./definitio"
  },
  {
    "path": "src/quantities/global-api.js",
    "chars": 529,
    "preview": "import { globalParse } from \"./parse.js\";\nimport {\n  divSafe,\n  mulSafe\n} from \"./utils.js\";\nimport {\n  getAliases,\n  ge"
  },
  {
    "path": "src/quantities/kind.js",
    "chars": 1652,
    "preview": "import Qty from \"./constructor.js\";\nimport { uniq } from \"./utils.js\";\n\nvar KINDS = {\n  \"-312078\": \"elastance\",\n  \"-3120"
  },
  {
    "path": "src/quantities/nested-map.js",
    "chars": 1154,
    "preview": "export default function NestedMap() {}\n\nNestedMap.prototype.get = function(keys) {\n\n  // Allows to pass key1, key2, ... "
  },
  {
    "path": "src/quantities/operators.js",
    "chars": 6518,
    "preview": "import Qty from \"./constructor.js\";\nimport QtyError, { throwIncompatibleUnits } from \"./error.js\";\nimport { PREFIX_VALUE"
  },
  {
    "path": "src/quantities/parse.js",
    "chars": 5383,
    "preview": "import { isString } from \"./utils.js\";\nimport QtyError from \"./error.js\";\nimport { PREFIX_MAP, UNIT_MAP } from \"./defini"
  },
  {
    "path": "src/quantities/predicates.js",
    "chars": 2204,
    "preview": "import Qty, { isQty } from \"./constructor.js\";\nimport { BASE_UNITS, UNITY, UNITY_ARRAY } from \"./definitions.js\";\nimport"
  },
  {
    "path": "src/quantities/signature.js",
    "chars": 1774,
    "preview": "import { UNITS } from \"./definitions.js\";\n\nvar SIGNATURE_VECTOR = [\"length\", \"time\", \"temperature\", \"mass\", \"current\", \""
  },
  {
    "path": "src/quantities/temperature.js",
    "chars": 4053,
    "preview": "import Qty from \"./constructor.js\";\nimport { UNITY_ARRAY } from \"./definitions.js\";\nimport QtyError from \"./error.js\";\ni"
  },
  {
    "path": "src/quantities/utils.js",
    "chars": 3126,
    "preview": "/**\n * Tests if a value is a string\n *\n * @param {*} value - Value to test\n *\n * @returns {boolean} true if value is a s"
  },
  {
    "path": "src/quantities.js",
    "chars": 310,
    "preview": "import Qty from \"./quantities/constructor.js\";\nimport \"./quantities/global-api.js\";\nimport \"./quantities/operators.js\";\n"
  }
]

About this extraction

This page contains the full source code of the gentooboontoo/js-quantities GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 42 files (571.5 KB), approximately 169.3k tokens, and a symbol index with 275 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!