npm package: @oliasoft-open-source/units
Description: Package for units management
Author: Oliasoft and contributors
Latest version: 4.7.0

# Oliasoft Unit Handling

##### Table of Contents
- [Introduction](#introduction)
- [Install](#install)
- [Basic usage](#basic-usage)
- [Input and conversions](#input-and-conversions)
- [Formatting](#formatting)
- [Working with Tables](#working-with-tables)
- [Methods](#methods)
- [Constants](#constants)

## Introduction

Hi, and welcome to Oliasoft's Unit handling repository! [Oliasoft](https://www.oliasoft.com) is using this to convert numbers between different units in all of our products. As you probably know we are doing various physics calculation that relies on precise definitions for all input data. We need to know the valid *range* and *precision* of a value as well as what *unit* the given value represent. Before doing calculations we therefor convert all numbers to proper JavaScript numbers in a given known unit used in the calculations. We refer to these values as **calculation units**. The users of our products are however allowed to input values, either through the GUI or directly using the APIs, in any unit they prefer. We refer to these values as **input units**. We always store the input value together with its unit *unconverted*, hence input units is the same as the **stored units**. Finally, we offer the users to view all graphs and numbers in their preferred units. We refer these values as **view units** (GUI) or **output units** (API).

All values with numbers are stored as a string with the following special format;

`'number|unit'`

Number here is very flexible and all of the following is valid "number" strings:

*  2.13
*  -2,13
*  4e-05
*  4E+3
* -120,000.02
* -20 1/2

I.e. it support both . and , as comma/thousands separator, exponents and fractions. Examples of different values can be seen in the [units tests](src\__tests__\units.test.ts). By storing the inputted value without any conversions, we maintain valuable precision where needed and also provide the user with recognizable values. We use *base* units, as described in for example International System of Units (SI), for conversions. By converting all units to the base first, through *intermediate convesions*, we are able to keep the amount of conversion permutations to a minimum. The same unit can exists in multiple *quantities* and we allow custom quantity categories for application flexibility.


## Install


```bash
npm i @oliasoft/units --save
```

## Basic usage

### Adding units to a number
As mentioned in the introduction this repository is working with numbers and units represented in a special string format. In order to get a string with number and unit use [withUnit](#withunitvalue-stringnumber-unit-stringnull-defaultval). Example:

```js
const myValueWithUnit = withUnit(3.14, 'cm'); // '3.14|cm'
```

It's also possible to convert the number to a different unit before getting the string, by calling [unumWithUnit](#unumwithunitnumwithunit-string--number-tounit-string-fromunit-string). Given a string with unit you can use [getValue](#getvaluenumwithunit) and [getUnit](#getunitnumwithunit) to get the unit part, or use [split](#split-string-into-value-and-unit) to get both as an array.

```js
const myValue = getValue(myValueWithUnit); // '3.14'
const myUnit = getUnit(myValueWithUnit); // 'cm'
const [value, unit] = split(myValueWithUnit); // ['3.14, 'cm']
```

> Never manipulate the strings directly in case we ever change the syntax!

### Converting number to calculation units
Before doing any calculations with variables stored in our special string format, we need to convert the values from the *stored units* to the *calculation units*. Knowing the target unit we will use the [convertAndGetValue](#convertandgetvaluenumwithunit-tounit-fromunit) method for converting and returning the number in the target calculation unit:

```javascript
const value = convertAndGetValue(number, toUnit, fromUnit);
```

The first argument is the number with or without unit. The second parameter is the target unit, while the last parameter is an optional from unit that's used as a fallback if the first parameter is given without unit.

Examples:

```js
const value = convertAndGetValue('10|m', 'm'); // 10
value = convertAndGetValue('10|m', 'in'); // 393.7007874015748
value = convertAndGetValue('10', 'in', 'm'); // 393.7007874015748
```

If you want to convert values *without* units directly to another unit, you can use the [to](#tovalue-fromunit-tounit) method. Both pure numbers and string representing numbers are converted.

```js
const convertedValue = to(10, 'm', 'in'); // 393.7007874015748
```
same as

```js
const convertedValue = to('10', 'm', 'in'); // 393.7007874015748
```

### Working with Quantities
**Quantities** are lists of units that represent the same measurement of a given quantity, for example *length* measured in *m*, *in*, *ft* etc. We have a few different methods for getting the units and quantities. One can use [getQuantities](#get-list-of-all-defined-quantities) to get a list of all the defined quantities or unit categories if you will. Given a quantity there's multiple methods to get units for that quantity. Use [getUnitsForQuantity](#getunitsforquantityquantity) to get a pure list of units, or you can get a list of the objects following the `AltUnitWithLabel` interface by calling [getAltUnitsListByQuantity](#getaltunitslistbyquantityquantity) which will also return formatted labels for the given unit. For example, m³ for cubic meters. The same labels can also be looked up directly by calling [label](#labelunitkey). To get the base unit for a given quantity one can use the method [unitFromQuantity](#unitfromquantityquantity).


```js
getQuantities(); // ['acceleration', 'angleGradient', 'angles', 'areaOther', ...]
getUnitsForQuantity('angle'); // ['deg', 'rad']
unitFromQuantity('force') // 'N'
```

### Working with fractions
Since this library is representing numbers and units with strings, we also have the flexibility to work directly with fractions. Use [fraction](#fractionstr) to convert a fraction, given as a string, to the corresponding numeric representation. Similar one can use [asFraction](#asfractionstr) to convert a decimal number to a fraction (if it exist). One can also use the method [numFraction](#numfractionstr) to convert from fraction to number, which will return the input if the conversion fails unlike `fraction` which returns `NaN` for failed conversions.

```js
const half = asFraction('0.5') // '1/2';
const numeric = fraction(half) // 0.5;
fraction('13/0') // Infinity;
fraction('Garbage') // NaN;
numFraction('Garbage') // 'Garbage';
```

## Input and conversions
Several methods exist for processing GUI inputs and making sure they are valid numbers. Raw inputs from the user can be fed through [validateAndClean](#validateandcleanpreviousvalue-nexttext) method, which returns a new valid *number-unit* string.

```js
validateAndClean('123-', '1234-'); // '1234';
validateAndClean('123|m', '1234'); // '1234|m'
```

Other similar method is [cleanNumStr](#cleannumstrstr) and [toNum](#tonuminput-defaultvalue-minimum) which works directly with numbers or numbers given as string. For directly returning the number use [cleanNum](#cleannumstr) which is identical to [clenNumStr] except for the return value.

```js
cleanNumStr('1000,000,000.1.1'); // '100000000011'
toNum(',1'); // 0.1
cleanNumStr(',1')); // '0.1';
cleanNum(',1')); // 0.1
```

For showing a list of all values in different units use [altUnitsList](#altunitslistvalue-quantity-defaultunit) which returns a list of the value in all units defined in the given quantity. In order to convert between units with similar precision one can use [convertSamePrecision](#convertsameprecisionnumwithunit-tounit-digits).

```js
altUnitsList('180', 'deg'); // [['180', 'deg', '°'], ['3.14', 'rad', 'rad']]
```

Given a value-unit string we offer a few methods for query the nature of it. Use [isEmptyValueWithUnit](#isemptyvaluewithunitval) to check if the unit is un-initialized (empty) and related use [isValueWithUnit](#isvaluewithunitvalue-stringnumber) to check if a string contains both a value and an "valid" unit. Valid here means an unit known to our repository. Use [isNumeric](#isnumericval) to check if a string represents a valid javascript number.

```js
isEmptyValueWithUnit('10|m'); // false
isEmptyValueWithUnit('|m'); // true
isValueWithUnit('10|m'); // true
isValueWithUnit('10'); // false
isNumeric(1e20); //true
isNumeric('1e20'); //true
isNumeric('e20'); // false
```

## Formatting
For printing nice numbers in GUI and reports we offer different helpers. As already mentioned, one can get formatted unit labels, for example m³ for cubic meters, by calling [label](#labelunitkey). Calling [roundNumberWithLabel](#roundnumberwithlabelvalue-roundto--2) gives you a value, rounded to the wanted precision, with unit label. Use [round](#roundnumbernum-round--4) to round just the value. Depending on what value that is stored in the string it can sometimes be hard to know what precision to use for display. The method [getNumberOfDigitsToShow](#getnumberofdigitstoshownum-maxnumdigits--20) will analyse the input and suggest a reasonable precision for you.

```js
const forPrint = roundNumberWithLabel('1000.1284325|kg/m3'); // '1000.13 kg/m³'
const noOfDigits = getNumberOfDigitsToShow(1e9); // 12
const outputValue = round(1e9, noOfDigits); // 0.0000000001
```

## Working with Tables
Most of the methods in the library works with single *value-unit* strings and for tables we encourage you to store the *value-unit* string directly as keyed objects.
Example:

```js
const myTable = [
  { depth: '15|m', anotherDepth: '42|ft', od: '35|in', density: '42|kg/m3' },
  { depth: '16|m', anotherDepth: '43|ft', od: '36|in', density: '43|kg/m3' },
];
```

That said, we also have support for a special custom table format where the first row specifies the **unit** and the following rows stores pure **numbers** *without unit*.

Example:

```js
const myExcelTable = [
  ['m', 'ft', 'in', 'kg/m3'],
  [15, 42, 35, 42],
  [16, 43, 36, 50]
];
```

This then represent the values 15 m, 42 ft, 35 in, 42 kg/m³, 16 m, 50 in, etc. This format is convenient for storing data imported/exported to i.e. Excel. With data in this shape you can call [convertTable](#convert-table-of-values-to-another-unit) to convert all values in the table to different units.

```js
convertTable(['cm', 'ft', 'cm', 'sg'], myExcelTable);
/*
  [
    ['cm', 'ft', 'cm', 'sg'],
    [1500, 42, 88.9, 0.042],
    [1600, 43, 91.44, 0.05]
  ]
*/
```


## Methods

### withUnit(value, unit, defaultVal = '') {...}
#### Get a *value-unit* string, i.e. value with unit splitted by | separator

```js
withUnit(1.123, 'm'); // '1.123|m'
withUnit(-10.314, 'K/100m'); // '-10.314|K/100m'
```

### unumWithUnit(numWithUnit, toUnit, fromUnit?) {...}
#### Converts to given *toUnit* and return the converted value with unit
```js
unumWithUnit(2.2, 'kg/m3', 'sg'); // '2200|kg/m3'
```

### isEmptyValueWithUnit(val) {...}
#### Checks if input is a string that starts with '|', e.g. '|m' or '|in'

```js
isEmptyValueWithUnit('|m'); // true
isEmptyValueWithUnit(NaN); // false
isEmptyValueWithUnit('m'); // false
```

### isValueWithUnit(value) {...}
#### Takes user input and returns `true` if is value with unit and false in every other case
```js
isValueWithUnit('m'); // false
isValueWithUnit('5'); // false
isValueWithUnit(5); // false
isValueWithUnit('5|m'); // true
```

### isNumeric(val) {...}
#### Check if provided argument is number

 ```js
isNumeric(1e20); // true
isNumeric('1e20'); // true
isNumeric(NaN); // false
isNumeric(Infinity); // false
```

### allNumbers(arr) {...}
#### Check array values if all are numbers
```js
allNumbers([1, 2, 1.2, 5]); // true
allNumbers([1, 2, '1.2', 5]); // false
allNumbers([1, 1, 2, Infinity, 1.2]); // true
```

### formatNumber(number) {...}
#### Outputs "pretty" number (with thousands separators), taken from [this](https://www.wikitechy.com/tutorials/javascript/print-a-number-with-commas-as-thousands-separators-in-javascript)

```js
formatNumber(1.1); // '1.1'
formatNumber(100000.123); // '100,000.123';
formatNumber('100000.123'); // '100,000.123';
```

### charCount(chr, str)
#### Counts all occurence of given character

```js
charCount('1', '1.1'); // 2;
charCount(1, '1.1'); // 2;
charCount(0, '100000.123'); // 5;
```

### validateAndClean(previousValue, nextText) {...}
#### Validates and cleans raw text numeric user input, typically from user input. The previous value is for optionally determining the pre-existing unit. The next text is a raw input string. The return value is reformatted from the next text (removing invalid patterns)

```js
validateAndClean('123', '1234'); // '1234'
validateAndClean('123e15', '123e154'); // '123e154'
validateAndClean('123.|m', '123.'); // '123.|m');
validateAndClean('-2e50|m', '-2e500'); // '-2e50|m'
validateAndClean('2|m', '2e-3'); // '2e-3|m'
```

### getNumberOfDigitsToShow(num, maxNumDigits = 20) {...}
#### Calculates the number of digits to be rounded off, typically for values less than 1 E.g. when trying to format 1e-9 byroundNumber(), the output value will only show '0' if just rounded off with 4 digits fromroundNumber(val) Then it is more useful to calculate the number of digits to be rounded off, and pass this in, i.e.round(val, getNumberOfDigitsToShow(val)) which will return 0.0000000001.

```js
getNumberOfDigitsToShow('1'); // 4
getNumberOfDigitsToShow(0.1); // 4
getNumberOfDigitsToShow(1.1); // 4
getNumberOfDigitsToShow(4.2e-8); // 11
getNumberOfDigitsToShow('100000.123'); // 4
getNumberOfDigitsToShow('100000.123', 3); // 3
getNumberOfDigitsToShow(0); // 4
```
### round(num, round = 4)
#### Formating / rounding number provided in argument

```js
round(1.11231231); // 1.1123
round('100000.123'); // '100000.123'
round('100000.123', 2); // '100000.12'
round('100000.123|m', 2); // '100000.12|m'
round(null, 2) // null
```

### roundNumberWithLabel(value, roundTo = 2) {...}
#### Round input value and return with labeled unit
```js
roundNumberWithLabel('1000.1284325|kg/m3'); // '1000.13 kg/m³'
roundNumberWithLabel('-999.999991|kg/m3', 5); // '-999.99999 kg/m³'
roundNumberWithLabel('-999.999999|kg/m3', 5); // '-1000 kg/m³'
```

### fraction(str)
####  Convert set fraction to decimal value will return either number in decimal format, Infinity if fraction is divided by 0

```js
fraction('1/3'); // 0.333
fraction('1/10'); // 0.1
fraction('13/0'); // Infinity
fraction('13e2b'); // NaN
fraction(null); // NaN
fraction(undefined); // NaN
fraction([1, 2]); // NaN
fraction(''); // NaN
```

###  unitFromQuantity(quantity) {...}
#### Get base unit from given quantity
```js
unitFromQuantity('force'); // 'N';
unitFromQuantity('notsupported');  // undefined;
```

### getAltUnitsListByQuantity(quantity) {...}
#### Get list of alternative units, with labels, for a given quantity.
```js
getAltUnitsListByQuantity('angles'); // [{unit: 'deg', label: '°'}, {unit: 'rad', label: 'rad'}];
getAltUnitsListByQuantity('qwe123'); // undefined;
```

### getUnitsForQuantity(quantity) {...}
#### Get list of units for a given quantity
```js
getUnitsForQuantity('force'); // ['tonnes', 'lbf', 'kgf', 'N', 'kN', 'tonneForce', 'klbf']
getUnitsForQuantity('depth'); // ['m', 'ft']
getUnitsForQuantity('acceleration'); // ['ft/s2', 'm/s2'])
getUnitsForQuantity('angles');// ['deg', 'rad']);
getUnitsForQuantity('dls'); // ['deg/10m', 'deg/30m', 'deg/100ft'])
getUnitsForQuantity('shit'); // undefined
```

### toBase(value, quantity) {...}
#### Convert value to the base unit given by the quantity
```js
toBase('1|m', 'length'); // 1
toBase('1|cm', 'length'); // 0.01
toBase('1|tonnes', 'weight'); // 1000
```
### altUnitsList(value, quantity, defaultUnit?) {...}
#### Get list of values, with same precision as the given value, in all the units of the given quantity
```js
altUnitsList('10|m', 'length');
 /* [['10', 'm', 'm'],
     ['32.8', 'ft', 'ft'],
     ['0.01', 'km', 'km'],
     ['394', 'in', 'in'],
     ['10000', 'mm', 'mm']]
 */
altUnitsList('180', 'deg');
/*
  [['180', 'deg', '°'],
   ['3.14', 'rad', 'rad']]
*/
```
### convertTable(toUnitRow, table, defaultUnitRow?, removeFinalUnitsRow=false) {...}
#### Convert table of values to another unit
```js
const table = [
  ['m', 'ft', 'in', 'kg/m3'],
  [15, 42, 35, 42],
  [16, 43, 36, 50]];
convertTable(['cm', 'ft', 'cm', 'sg'], table);
/*
  [
    ['cm', 'ft', 'cm', 'sg'],
    [1500, 42, 88.9, 0.042],
    [1600, 43, 91.44, 0.05]
  ]
*/
```


### getQuantities() {...}
#### Get list of all defined quantities

```js
getQuantities(); // ['acceleration', 'angleGradient', 'angles', 'areaOther', ...]
```

### checkAndCleanDecimalComma(val) {...}
#### Find double dot and comma in value and replace it with decimal dot. For example: 123,4 => 123.4 or 123..4 => 123,4

```js
checkAndCleanDecimalComma(123); // 123;
checkAndCleanDecimalComma('36,6'); // '36.6';
checkAndCleanDecimalComma('36..6'); // '36.6';
```

### to(value, fromUnit, toUnit) {...}
#### Convert value to another unit

```js
to(1, 'm', 'ft'); // 1 / 0.3048
to(0, 'C', 'F'); // 32
to(32, 'F', 'C'); // 0
to(50, '%', 'fr'); // 0.5
to(0.125, 'fr', '%'); // 12.5
to(180, 'deg', 'rad').toFixed(4); // '3.1416'
to(1, 'rad', 'deg').toFixed(4); // '57.2958'
to('1,12', 'rad', 'deg').toFixed(4); // '64.1713'
to('1....12', 'rad', 'deg').toFixed(4); // '64.1713'
to('1,,12', 'rad', 'deg').toFixed(4); // '64.1713'
to('1,,.12', 'rad', 'deg').toFixed(4); // '64.1713'
```

### split(numWithUnit) {...}
#### Split string into value and unit.
```js
split('-12,2m');  // ['-12.2', 'm'];
split('-12 1/2m');  // ['-12 1/2', 'm'];
```

### getValue(numWithUnit) {...}
#### Get unit of the number with unit string ("1|m") will return "m"
```js
getValue('12.2'); // '12.2'
getValue('12.2m'); // '12.2'
getValue('12.2|m'); // '12.2'
getValue('|m'); // '';
getValue('m'); // '';
```

### getUnit(numWithUnit) {...}
#### Get unit of the number with unit string
```js
getUnit('-2|in2'); // 'in2'
getUnit('12.2|m'); // 'm'
getUnit('12.2m'); // 'm';
getUnit('|m'); // 'm';
getUnit('12.2'); // '';
```


### label(unitKey) {...}
#### Returns a print friendly unit representation
```js
label('m3');  // 'm³'
label('1/bar'); // 'bar⁻¹'
```

### convertAndGetValue(numWithUnit, toUnit, fromUnit?) {...}
#### Convert value with unit to another unit Will try to pick `fromUnit` from `numWithUnit` if it was not provided
```js
convertAndGetValue('1 1/2', 'in', 'in'); // 1.5
convertAndGetValue('1 1/2 in', 'in');  // 1.5
convertAndGetValue('-1 1/2 in', 'in'); // -1.5
convertAndGetValue(2.2, 'notsupported', 'notsupported'); // 2.2
convertAndGetValue(2.2, 'kg/m3', 'sg'); // 2200
convertAndGetValue('2.2', 'kg/m3', 'sg'); // 2200
```

### convertSamePrecision(numWithUnit, toUnit, digits?) {...}
#### Convert value with unit to another unit and display it in pretty format. It will preserv the number of digits in the input or alternativly converting to the given number of digits.
```js
convertSamePrecision('1|in', 'cm', 8); // '2.54|cm'
convertSamePrecision('102e-6|in', 'cm'); // '0.000259|cm'
convertSamePrecision(2.54, 'cm', 1); // '3|cm'
convertSamePrecision('10.000|m', 'in'); // '393.7|in'
```

### asFraction(str) {...}
#### Converts decimal number to fractional format return string with fractional format of set value
```js
asFraction(''); // '0'
asFraction('0.1'); // '1/10'
```

### numFraction(str) {...}
#### Convert fraction string to number (return input value if conversion fails) For historical reasons, numFraction returns the string value unmodified if it is not able to convert to a number. This is useful where user inputs are filtered through calls to numFraction. For "detecting" when numFraction fails, check if the return value is a string or a number. If it is a string it means number conversion failed. will return  string with decimal format of fraction

```js
numFraction(''); // ''
numFraction('1/10'); // 0.1
numFraction(null); // null
```


### cleanNumStr(str){...}
#### Cleaning up and fixing provided number to correct numerical format removing redundant '.' dots, ',' commas, spaces
```js
cleanNumStr('1000,000.1'); // '1000000.1'
cleanNumStr('1000,000,000'); // '1000000000'
cleanNumStr('1/10'); // '1/10'
cleanNumStr('1000,000,000.1.1'); // '100000000011'
```

### cleanNum(str): {...}
#### Cleaning and fixing numerical string but returns it as number

```js
cleanNum(1); // 1
cleanNum(',1'); // 0.1
```

### toNum(input, defaultValue?, minimum?) {...}
#### Convert provided argument to number or return it if impossible to convert
```js
toNum(1); // 1
toNum(',1'); // 0.1
```

### isNonNumerical(value)
#### Check if value is non numerical

```js
isNonNumerical('123.32asdasds4') // true
isNonNumerical('123.32') // false
```

## Constants
#### LABELS
##### Units labels
```js
LABELS.cm // 'cm'
LABELS.lps // 'L/s'
```

#### ALT_UNITS
##### Alternative units grouped by quantity
```js
ALT_UNITS.angles // ['deg', 'rad']
ALT_UNITS.density // ['sg', 'ppg', 'kg/m3', 'lbm/ft3', 'g/cm3', 'lb/ft3']
```

#### UNIT_FROM_KEY
##### Units list
```js
UNIT_FROM_KEY.length // 'm'
UNIT_FROM_KEY.latitude // '°N'
```

#### KNOWN_CONVERSIONS
##### Conversions list where each key 'from unit|to unit' pair
```js
KNOWN_CONVERSIONS['m|mm'](1) // 1000
```

#### DEPRECATED_UNITS
##### List of deprecated units
```js
DEPRECATED_UNITS['N-m'] // 'Nm'
DEPRECATED_UNITS['ft-lbf'] // 'ftlbf'
```

#### UNIT_ALIASES
##### This list is mapping from legal alternative unit names to our selected unit name
```js
UNIT_ALIASES['lbs/ft'] // 'lb/ft'
```

#### INTERMEDIATE_CONVERSIONS
##### Intermediate conversions
```js
INTERMEDIATE_CONVERSIONS.mm // 'm'
INTERMEDIATE_CONVERSIONS.t // 'kg'
```

#### SPECIAL_NUMBERS_STRING
##### Special numbers in string format
```js
SPECIAL_NUMBERS_STRING // ['NaN', '-Infinity', 'Infinity']
```