├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.json ├── docs ├── css │ └── index.css ├── index.html └── js │ ├── UnitMath.d.ts │ ├── UnitMath.js │ ├── UnitMath.min.js │ └── demo.js ├── examples └── decimal.js ├── index.js ├── jest.config.js ├── migrating-to-v1.md ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── BuiltIns.ts ├── Parser.ts ├── Unit.ts ├── UnitStore.ts ├── types.ts └── utils.ts ├── test ├── Unit.test-compiled.js ├── Unit.test.ts ├── UnitMath.test-bundle.js └── approx.ts ├── tsconfig.json └── types ├── BuiltIns.d.ts ├── Parser.d.ts ├── Unit.d.ts ├── Unit.js ├── UnitStore.d.ts ├── types.d.ts └── utils.d.ts /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Workflow for Codecov example-javascript 2 | on: [push, pull_request] 3 | jobs: 4 | run: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v2 9 | - name: Set up Node 18 10 | uses: actions/setup-node@v3 11 | with: 12 | node-version: 18 13 | - name: Install dependencies 14 | run: npm install 15 | - name: Run tests and collect coverage 16 | run: npm run coverage 17 | - name: Upload coverage to Codecov 18 | uses: codecov/codecov-action@v3 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/ 3 | es/ 4 | HOWTO.md 5 | .vscode/launch.json 6 | .nyc_output 7 | coverage 8 | *.bash 9 | call-tree 10 | .vscode/settings.json 11 | *.tgz -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 12 5 | - 10 6 | - 8 7 | 8 | script: 9 | - npm run test:src 10 | 11 | jobs: 12 | include: 13 | - stage: other 14 | script: npm run lint 15 | name: Lint 16 | - script: npm run test:bundle 17 | name: Bundling Test 18 | - script: npm run coverage && npx codecov 19 | name: Coverage 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## [1.1.1] - 2024-10-5 4 | 5 | - Fix package.json so that when using `import`, it imports the correct ES module. Thank you @fezproof! 6 | 7 | ## [1.1.0] - 2024-03-16 8 | 9 | - Add `applyBestPrefix` method to choose and apply the best prefix for a Unit. Thank you @adrfantini for the contribution. 10 | 11 | ## [1.0.2] - 2023-11-24 12 | 13 | - Set `exports` in `package.json`, which fixes imports in Node and Deno when using `require` or `import()` 14 | 15 | ## [1.0.1] - 2023-10-21 16 | 17 | _This is an attempt to correctly export types._ 18 | 19 | ### Fixed 20 | 21 | - Set `module` entry point in package.json to `es/UnitMath.js` 22 | - Set `types` in package.json to `es/UnitMath.d.ts` 23 | 24 | ## [1.0.0] - 2023-02-20 25 | 26 | V1 is a major rewrite of UnitMath in TypeScript. While most of the API remains the same, V1 does introduce several breaking changes from v0.8.7. See [Migrating to v1](migrating-to-v1.md) for details. 27 | 28 | - `toString` no longer simplifies units. You must now explicitly call `simplify` for the unit to be simplified. 29 | - Removed `simplify` and `simplifyThreshold` options. 30 | - Removed `definitions.quantities` and `definitions.baseQuantities`. 31 | - Renaned `definitions.unitSystems` to `definitions.systems` 32 | - Renamed `definitions.prefixes` to `definitions.prefixGroups` 33 | - Each system defined in `definitions.systems` is now just a string array of units assigned to that system. 34 | - Removed `autoAddToSystem` option, since it is now much easier to add units to a system. 35 | - Customer formatters no longer accept additional user arguments. 36 | 37 | ## [1.0.0-rc.2] - 2023-02-19 38 | - `toString` no longer simplifies units. The user must now explicitly call `simplify` for the unit to be simplified. 39 | - Removed `simplifyThreshold` option, since units are now only simplified when calling `simplify`. 40 | - Many updates to the README. 41 | - Many tests had to be altered to reflect the changes to `toString` and `simplify`. 42 | 43 | ## [0.8.7] - 2021-09-28 44 | - Parse strings `NaN`, `Infinity`, and `-Infinity` 45 | 46 | ## [1.0.0-rc.1] - 2020-11-20 47 | - Convert to TypeScript 48 | - Removed concept of "quantities" and "base quantities" 49 | - Simplified how systems are defined and used in formatting 50 | - Simpler way to define units 51 | - Renamed many variables and API functions to make their meaning less ambiguous 52 | - Updated README.md 53 | 54 | ## [0.8.6] - 2020-07-13 55 | - Standardized on US customary fluid volumes 56 | - Corrected values for `teaspoon` and `fluidounce` 57 | 58 | ## [0.8.5] - 2019-11-23 59 | - `compare` now handles NaNs consistently 60 | 61 | ## [0.8.4] - 2019-08-05 62 | - Added `prefixesToChooseFrom` option 63 | - Bugfix when auto-prefixing negative numbers 64 | 65 | ## [0.8.3] - 2019-06-04 66 | - `split` now supports custom types 67 | 68 | ## [0.8.2] - 2019-06-01 69 | - Fixed unit complexity calculation for deciding whether to simplify units 70 | - Added undocumented second parameter to `conv` function, which could be removed at any time 71 | 72 | ## [0.8.1] - 2019-06-01 73 | - Format function can now be used with number or custom types 74 | - Now supports passing parameters to custom format function 75 | 76 | ## [0.8.0] - 2019-05-30 77 | - Added `getValue`, `getNormalizedValue`, and `setNormalizedValue` 78 | - Added format option for custom types 79 | 80 | ## [0.7.0] - 2019-05-28 81 | - Added compare 82 | 83 | ## [0.6.0] - 2019-05-28 84 | - Implement valueOf() 85 | - Added lessThan, lessThanOrEqual, greaterThan, greaterThanOrEqual 86 | 87 | ## [0.5.0] - 2019-05-27 88 | - Added setValue 89 | - Fixed bug with custom type formatting 90 | - Fixed candela unit definition 91 | 92 | ## [0.4.0] - 2019-05-26 93 | - Added split 94 | - Better support for parsing of custom types 95 | 96 | ## [0.3.0] - 2019-05-23 97 | - Added abs 98 | - Basic functionality with custom types (tested with Decimal.js, some features may not work) 99 | 100 | ## [0.2.1] - 2019-05-19 101 | - Added numerous tests to improve test coverage 102 | - Removed some unnecessary statements and branches 103 | - Minor bug fixes 104 | - Updated README 105 | 106 | ## [0.2.0] - 2019-05-18 107 | - Major refactoring of UnitStore.js to simplify the units definitions 108 | - Custom units now work with config options 109 | - Added code coverage (but doesn't seem to be instrumenting all the files yet) 110 | 111 | ## [0.1.1] - 2019-05-09 112 | - This patch actually includes the built files (I hope). 113 | 114 | ## [0.1.0] - 2019-05-08 115 | 116 | - First public release of UnitMath. 117 | - README is mostly up-to-date. Some of the API has not yet been implemented. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnitMath 2 | UnitMath is a JavaScript library for unit conversion and arithmetic. 3 | 4 | [![Build Status](https://travis-ci.org/ericman314/UnitMath.svg?branch=master)](https://travis-ci.org/ericman314/UnitMath) 5 | [![codecov](https://codecov.io/gh/ericman314/UnitMath/branch/master/graph/badge.svg)](https://codecov.io/gh/ericman314/UnitMath) 6 | 7 | 8 | 9 | - [Install](#install) 10 | - [Getting Started](#getting-started) 11 | - [Creating Units](#creating-units) 12 | * [Parsing Rules](#parsing-rules) 13 | - [Unit Conversion](#unit-conversion) 14 | - [Arithmetic](#arithmetic) 15 | - [Simplify](#simplify) 16 | - [Formatting](#formatting) 17 | - [Configuring](#configuring) 18 | * [Querying the current configuration](#querying-the-current-configuration) 19 | - [Extending UnitMath](#extending-unitmath) 20 | * [User-Defined Units](#user-defined-units) 21 | * [Querying current unit definitions](#querying-current-unit-definitions) 22 | * [Custom Types](#custom-types) 23 | - [API Reference](#api-reference) 24 | * [Constructor](#constructor) 25 | * [Member Functions](#member-functions) 26 | * [Static Functions](#static-functions) 27 | - [Contributing](#contributing) 28 | - [Acknowledgements](#acknowledgements) 29 | * [Contributors](#contributors) 30 | - [License](#license) 31 | 32 | 33 | 34 | ## Demo 35 | 36 | https://ericman314.github.io/UnitMath/ 37 | 38 | ## Install 39 | 40 | ``` 41 | npm install unitmath 42 | ``` 43 | 44 | ## Getting Started 45 | 46 | ```js 47 | const unit = require('unitmath') 48 | 49 | let a = unit('5 m').div('2 s') // 2.5 m / s 50 | let b = unit('40 km').to('mile') // 24.8548476894934 mile 51 | b.toString({ precision: 4 }) // "24.85 mile" 52 | ``` 53 | 54 | ## Creating Units 55 | 56 | To create a unit, call `unit` with either a single string, or a number and a string: 57 | 58 | ```js 59 | // String 60 | let a = unit('40 mile') 61 | let b = unit('hour') 62 | 63 | // Number and string 64 | let g = unit(9.8, 'm/s^2') 65 | let h = unit(19.6, 'm') 66 | ``` 67 | 68 | Units can be simple (`4 kg`) or compound (`8.314 J/mol K`). They may also be valueless (`hour`). 69 | 70 | ### Parsing Rules 71 | 72 | Parsing rules are different than typical JavaScript syntax. For instance: 73 | - The string may only contain one `"/"`. Units appearing before the `/` will be in the numerator of the resulting unit, and units appearing after the `/` will be in the denominator. 74 | - Parentheses `()` and asterisk `*` are ignored: multiplication is implicit. 75 | - Extra whitespace is allowed. 76 | 77 | The following all form valid units: 78 | 79 | ```js 80 | // These are valid: 81 | unit("2 in") 82 | unit("60/s") 83 | unit("8.314 * kg * m^2 / (s^2 * mol * K)") 84 | unit("6.022e-23 mol^-1") 85 | unit("kW / kg K") 86 | unit("3.1415926535897932384626433832795 rad") 87 | 88 | // These are invalid: 89 | unit("2 in + 3 in") // Unexpected "+" (Use .add() instead) 90 | unit("60 / s / s") // Unexpected additional "/" 91 | unit("0x123 kg") // Unit "x123" not found 92 | ``` 93 | 94 | The exact syntax is as follows: 95 | 96 | ``` 97 | [value][numerator][/denominator] 98 | 99 | numerator, denominator: 100 | atomicUnit [atomicUnit ...] 101 | 102 | atomicUnit: 103 | [prefix]unit[^power] 104 | 105 | value, power: 106 | Any floating-point number 107 | 108 | unit, prefix: 109 | Any built-in or user-defined unit or prefix 110 | 111 | ``` 112 | 113 | 114 | ## Unit Conversion 115 | 116 | The `to` method converts from one unit to another. The two units must have the same dimension. 117 | 118 | ```js 119 | // Convert units 120 | unit('40 km').to('mile') // 24.8548476894934 mile 121 | unit('kg').to('lbm') // 2.20462262184878 lbm 122 | unit(5000, 'kg').to('N s') // Cannot convert 5000 kg to N s: dimensions do not match 123 | ``` 124 | 125 | The `split` method will convert one unit into an array of units like so: 126 | 127 | ```js 128 | // Split a unit into parts 129 | unit('10 km').split([ 'mi', 'ft', 'in' ]) // [ 6 mi, 1128 ft, 4.78740157486361 in ] 130 | unit('51.4934 deg').split([ 'deg', 'arcmin', 'arcsec' ]) // [ 51 deg, 29 arcmin, 36.24 arcsec ] 131 | ``` 132 | 133 | ## Arithmetic 134 | 135 | Use the methods `add`, `sub`, `mul`, `div`, `pow`, `sqrt`, and others to perform arithmetic on units. Multiple operations can be chained together: 136 | 137 | ```js 138 | // Chain operations together 139 | let g = unit(9.8, 'm/s^2') 140 | let h = unit(19.6, 'm') 141 | h.mul(2).div(g).sqrt() // 2 s 142 | ``` 143 | 144 | Strings and numbers are implicitly converted to units within a chained expression. When chaining operators, they will execute in order from left to right. This may not be the usual, mathematical order of operations: 145 | 146 | ```js 147 | // Strings are implicitly converted to units 148 | unit('3 ft').add('6 in').mul(2) // 7 ft 149 | ``` 150 | 151 | All of the operators are also available on the top-level `unit` object as static functions: 152 | 153 | ```js 154 | // Using static functions 155 | unit.mul(unit.add('3 ft', '6 in'), 2) // 7 ft 156 | ``` 157 | 158 | Units are immutable, so every operation on a unit creates a new unit. 159 | 160 | ## Simplify 161 | 162 | UnitMath has a `simplify` method that will attempt to simplify a unit: 163 | 164 | ```js 165 | // Simplify units 166 | unit('10 / s').simplify() // 10 Hz 167 | unit('J / m').simplify() // N 168 | ``` 169 | 170 | Because units are immutable, `simplify` always returns a *new* unit with the simplified units. It does not modify the original unit. 171 | 172 | In the `simplify` method, simplification is performed in two steps: 173 | 1. Attempting to simplify a compound unit into a single unit in the desired unit system 174 | 2. Choosing an appropriate prefix (if `options.autoPrefix` is true) 175 | 176 | *Note: To choose the best prefix without automatically simplifying the Unit, use the `applyBestPrefix` method.* 177 | 178 | Here are the options available for the `simplify` method: 179 | 180 | 188 | 189 | - `system` - *Default:* `'auto'`. The unit system to use when simplifying a `unit`. Available systems are `si`, `cgs`, `us`, and `auto`. 190 | 191 | ```js 192 | // Simplify units using a specific system 193 | unit('8 kg m / s^2').simplify({ system: 'si' }) // 8 N 194 | unit('8 kg m / s^2').simplify({ system: 'cgs' }) // 800 kdyn 195 | unit('8 kg m / s^2').simplify({ system: 'us' }) // 1.79847154479768 lbf 196 | ``` 197 | 198 | When `system` is `'auto'` (the default), UnitMath will try to infer the unit system from the individual units that make up that `unit`. This is not guaranteed to succeed in all cases, so whenever possible, the system should be specified explicitly. 199 | 200 | ```js 201 | unit = unit.config({ system: 'auto' }) 202 | 203 | // Automatically infer the unit system while simplifying 204 | unit('150 lbf').div('10 in^2').simplify() // 15 psi 205 | unit('400 N').div('10 cm^2').simplify() // 400 kPa 206 | ``` 207 | 208 | Specifying a unit system other than `'auto'` will force UnitMath to use the specified system. Use the `config` function to apply the system everywhere, or pass a configuration object to `simplify` to apply to a single statement: 209 | 210 | ```js 211 | unit = unit.config({ system: 'us' }) 212 | 213 | let a = unit('5 m').div('2 s') 214 | a.simplify() // 8.202099737532809 ft / s 215 | 216 | // Override the system for this statement 217 | a.simplify({ system: 'si'}) // 2.5 m / s 218 | ``` 219 | 220 | - `autoPrefix` - *Default:* `true`. This option specifies whether `simplify` will try to choose an appropriate prefix in case of very small or very large quantities. 221 | 222 | ```js 223 | unit('1e-10 kg m / s^2').simplify() // 0.0001 uN 224 | unit('1e-10 kg m / s^2').simplify({ autoPrefix: false }) // 1e-10 N 225 | ``` 226 | 227 | - `prefixMin` and `prefixMax` - *Defaults:* `0.1` and `1000`. Sets the threshold for choosing a different prefix when `autoPrefix` is true. 228 | 229 | ```js 230 | unit('0.005 m').simplify() // 0.5 cm 231 | unit('0.005 m').simplify({ prefixMin: 0.001 }) // 0.005 m 232 | ``` 233 | 234 | - `formatPrefixDefault` - *Default:* `none`. By default, certain units are never assigned a new prefix (this is controlled by the `formatPrefixes` property of the unit definition.) By setting `formatPrefixDefault` to `"all"`, this behavior can be changed. 235 | 236 | ```js 237 | unit('1000001 lumen').simplify() // 1000001 lumen 238 | unit('1000001 lumen').simplify({ formatPrefixDefault: 'all' }) // 1.000001 megalumen 239 | ``` 240 | 241 | ## Formatting 242 | 243 | Use `toString` to output a unit as a string: 244 | 245 | ```js 246 | unit('1 lb').to('kg').toString() // "0.45359237 kg" 247 | ``` 248 | 249 | The `toString` method accepts a configuration object. The following options are available: 250 | 251 | - `precision` - *Default:* `15`. The number of significant figures to output when converting a unit to a string. Reducing this can help reduce the appearance of round-off errors. Setting a value of 0 will disable rounding. 252 | 253 | ```js 254 | // Output a unit with a specific precision 255 | unit('180 deg').to('rad').toString({ precision: 6 }) // 3.14159 rad 256 | unit('1 lb').to('kg').toString({ precision: 4 }) // "0.4536 kg" 257 | ``` 258 | 259 | - `parentheses` - *Default:* `false`. When formatting a unit, group the numerator and/or denominator in parentheses if multiple units are present. This is useful when the resulting string will be used in a mathematical expression. 260 | 261 | ```js 262 | // Output a unit with parentheses 263 | unit('45 W / m K').toString({ parentheses: true }) // 45 W / (m K) 264 | ``` 265 | 266 | - `formatter`. Define a custom formatter for the _numeric_ portion of the unit. The formatter will be passed the numeric value of the unit. For example: 267 | 268 | ```js 269 | let unit = require('unitmath') 270 | 271 | // Custom formatter 272 | let formatter = new Intl.NumberFormat('en-US', { 273 | style: 'currency', 274 | currency: 'USD', 275 | }); 276 | 277 | // Specify formatter in argument to toString 278 | unit('25000 / ton').toString({ formatter: formatter.format }) // "$25,000.00 ton^-1" 279 | 280 | // Specify formatter in config 281 | let unitMoney = unit.config({ 282 | formatter: formatter.format 283 | }) 284 | unitMoney('25000 / ton').toString() // "$25,000.00 ton^-1" 285 | ``` 286 | 287 | The `toString` method outputs a unit exactly as it is represented internally. It does _not_ automatically simplify a unit. This means you will usually want to call `simplify` before calling `toString`. How and when to simplify a unit is very subjective. UnitMath cannot anticipate all needs, so the user must explicitly call `simplify` when needed. 288 | 289 | ```js 290 | // toString outputs units "as-is". 291 | unit('10 / s').toString() // "10 s^-1" 292 | unit('10 / s').simplify().toString() // "10 Hz" 293 | ``` 294 | 295 | 296 | 297 | ## Configuring 298 | 299 | UnitMath can be configured using `unit.config(options)`. The function returns a *new* instance of UnitMath with the specified configuration options. 300 | 301 | ```js 302 | // Set the default unit system to "us" 303 | const unit = require('unitmath').config({ system: 'us' }) 304 | ``` 305 | 306 | Available options are: 307 | 308 | - Any of the [simplify options](#simplify). 309 | 310 | - Any of the [formatting options](#formatting). 311 | 312 | - `type`. An object that allows UnitMath to work with custom numeric types. See [Custom Types](#custom-types) for complete details and examples. 313 | 314 | - `definitions`. An object that allows you to add to, modify, or remove the built-in units. See [User-Defined Units](#user-defined-units) for complete details. 315 | 316 | ```js 317 | // Create a new configuration 318 | unit = unit.config({ 319 | definitions: { 320 | units: { 321 | furlong: { value: '220 yards' }, 322 | fortnight: { value: '2 weeks' } 323 | } 324 | }, 325 | precision: 6 326 | }) 327 | 328 | unit('6 furlong/fortnight').to('m/s') // 0.000997857 m / s 329 | ``` 330 | 331 | 332 | 333 | Because `unit.config(options)` returns a new instance of UnitMath, is is technically possible to perform operations between units created from different instances. The resulting behavior is undefined, however, so it is probably best to avoid doing this. 334 | 335 | **Important:** `unit.config(options)` returns a *new* instance of the factory function, so you must assign the return value of `unit.config(options)` to some variable, otherwise the new options won't take effect: 336 | 337 | ```js 338 | let unit = require('unitmath') 339 | 340 | // Incorrect, has no effect! 341 | unit.config(options) 342 | 343 | // Correct 344 | unit = unit.config(options) 345 | ``` 346 | 347 | ### Querying the current configuration 348 | 349 | Call `unit.getConfig()` to return the current configuration. 350 | 351 | ```js 352 | // Get the current configuration 353 | unit.getConfig() // { system: 'si', ... } 354 | ``` 355 | 356 | ## Extending UnitMath 357 | 358 | ### User-Defined Units 359 | 360 | To create a user-defined unit, pass an object with a `definitions` property to `unit.config()`: 361 | 362 | ```js 363 | // Create a new configuration adding a user-defined unit 364 | unit = unit.config({ 365 | definitions: { 366 | units: { 367 | lightyear: { value: '9460730472580800 m' } 368 | } 369 | } 370 | }) 371 | 372 | unit('1 lightyear').to('mile') // 5878625373183.61 mile 373 | ``` 374 | 375 | The `definitions` object contains four properties which allow additional customization of the unit system: `units`, `prefixGroups`, `systems`, and `skipBuiltIns`. 376 | 377 | **definitions.units** 378 | 379 | This object contains the units that are made available by UnitMath. Each key in `definitions.units` becomes a new unit. The easiest way to define a unit is to provide a string representation in terms of other units: 380 | 381 | ```js 382 | // Define new units in terms of existing ones 383 | definitions: { 384 | units: { 385 | minute: { value: '60 seconds' }, 386 | newton: { value: '1 kg m/s^2' } 387 | } 388 | } 389 | ``` 390 | 391 | Here are all the options you can specify: 392 | 393 | - `value`: (Required) The value of the unit. It can be a string or an array containing two items: 394 | 395 | ```js 396 | // value can be a string... 397 | definitions: { 398 | units: { 399 | minute: { value: '60 seconds' }, 400 | newton: { value: '1 kg m/s^2' } 401 | } 402 | } 403 | 404 | // ...or an array. 405 | definitions: { 406 | units: { 407 | minute: { value: [ 60, 'seconds' ] }, 408 | newton: { value: [ 1, 'kg m/s^2' ] } 409 | } 410 | } 411 | ``` 412 | 413 | - `quantity`: Specifying a `quantity` will create a new _base unit_. This is required for units that are not defined in terms of other units, such as `meter` and `second`. In this case, `value` will just be a number (or custom type): 414 | 415 | ```js 416 | // Specify `quantity` creates a new base unit 417 | definitions: { 418 | units: { 419 | seconds: { quantity: 'TIME', value: 1 } 420 | } 421 | } 422 | ``` 423 | 424 | Only use `quantity` to define _base units_. Do **not** use `quantity` to define a derived unit: 425 | 426 | ```js 427 | // Incorrect 428 | definitions: { 429 | units: { 430 | meter: { quantity: 'LENGTH', value: 1 }, 431 | squareMeter: { quantity: 'LENGTH^2', value: 1 } 432 | } 433 | } 434 | 435 | // Correct 436 | definitions: { 437 | units: { 438 | meter: { quantity: 'LENGTH', value: 1 }, 439 | squareMeter: { value: '1 meter^2' } 440 | } 441 | } 442 | ``` 443 | 444 | - `prefixGroup` - *Default:* `'NONE'`. Specifies which prefix group will be used when parsing and formatting the unit. 445 | 446 | ```js 447 | definitions: { 448 | units: { 449 | // Will parse 'nanometer', 'micrometer', 'millimeter', 'kilometer', 'megameter', etc. 450 | meter: { prefixGroup: 'LONG', ... }, 451 | 452 | // Will parse 'nm', 'um', 'mm', 'km', 'Mm', etc. 453 | m: { prefixGroup: 'SHORT', ... } 454 | } 455 | } 456 | ``` 457 | 458 | - `formatPrefixes`: A string array that specifies individual items of the unit's prefix group which will be used when formatting a unit. If this option is omitted, the global option `formatPrefixDefault` determines whether the unit will be formatted using all prefixes in the prefix group, or none at all. 459 | 460 | ```js 461 | definitions: { 462 | units: { 463 | L: { 464 | value: '1e-3 m^3' 465 | // Parse any prefix in the 'SHORT' prefix group 466 | prefixGroup: 'SHORT', 467 | // Format only as 'nL', 'uL', 'mL', and 'L'. 468 | formatPrefixes: ['n', 'u', 'm', ''], 469 | }, 470 | lumen: { 471 | value: '1 cd sr' 472 | // Parse any prefix in the 'LONG' prefix group 473 | prefixGroup: 'LONG', 474 | // formatPrefixes is not given, so lumen will be formatted 475 | // only as "lumen" if formatPrefixDefault === false, 476 | // or formatted using any of the prefixes in the 'LONG' 477 | // prefix group if formatPrefixDefault === true. 478 | } 479 | } 480 | } 481 | ``` 482 | 483 | - `basePrefix`: Optional. The prefix to use for a _base unit_, if the base unit has one. This is necessary for units such as kilogram, which is a base unit but has a prefix. 484 | 485 | ```js 486 | // Special case for base units with prefixes, such as kg 487 | definitions: { 488 | units: { 489 | g: { 490 | quantity: 'MASS', 491 | prefixGroup: 'SHORT', 492 | formatPrefixes: ['n', 'u', 'm', '', 'k'], 493 | value: 0.001, 494 | // Treat as if 'kg' is the base unit, not 'g' 495 | basePrefix: 'k' 496 | } 497 | } 498 | } 499 | ``` 500 | 501 | - `aliases`: Shortcut to create additional units with identical definitions. 502 | 503 | ```js 504 | // Create aliases for a unit 505 | definitions: { 506 | units: { 507 | meter: { ... , aliases: [ 'meters' ] } 508 | } 509 | } 510 | ``` 511 | 512 | - `offset` - *Default:* `0`: Used when the zero-value of this unit is different from the zero-value of the base unit. 513 | 514 | ```js 515 | // Special case for units with an offset from the base unit, like celsius 516 | definitions: { 517 | units: { 518 | celsius: { 519 | value: '1 K', 520 | offset: 273.15 521 | } 522 | } 523 | } 524 | ``` 525 | 526 | **definitions.prefixGroups** 527 | 528 | The `definitions.prefixGroups` object is used to define strings and associated multipliers that are prefixed to units to change their value. For example, the `'k'` prefix in `km` multiplies the value of the `m` unit by 1000. 529 | 530 | For example: 531 | 532 | ```js 533 | // Define prefix groups 534 | definitions: { 535 | prefixGroups: { 536 | NONE: { '': 1 }, 537 | SHORT: { 538 | m: 0.001, 539 | '': 1, 540 | k: 1000 541 | }, 542 | LONG: { 543 | milli: 0.001, 544 | '': 1, 545 | kilo: 1000 546 | } 547 | } 548 | } 549 | ``` 550 | 551 | **definitions.systems** 552 | 553 | This object assigns one or more units to a number of systems. Each key in `definitions.systems` becomes a system. For each system, list all the units that should be associated with that system in an array. The units may be single or compound (`m` or `m/s`) and may include prefixes. 554 | 555 | Example: 556 | 557 | ```js 558 | // Define unit systems 559 | definitions: { 560 | systems: { 561 | si: ['m', 'kg', 's', 'N', 'J', 'm^3', 'm/s'], 562 | cgs: ['cm', 'g', 's', 'dyn', 'erg', 'cm^3', 'cm/s'], 563 | us: ['ft', 'lbm', 's', 'lbf', 'btu', 'gal', 'ft/s'] 564 | } 565 | } 566 | ``` 567 | 568 | When UnitMath formats a unit, it will try to use one of the units from the specified system. 569 | 570 | **definitions.skipBuiltIns** 571 | 572 | A boolean value indicating whether to skip the built-in units. If `true`, only the user-defined units, prefix groups, and systems that are explicitly included in `definitions` will be created. 573 | 574 | ```js 575 | // Skip built-in units: only the user-defined units, 576 | // prefix groups, and systems will be created 577 | definitions: { 578 | skipBuiltIns: true 579 | } 580 | ``` 581 | 582 | ### Querying current unit definitions ### 583 | 584 | You can view all the current definitions by calling `unit.definitions()`. This object contains all the units, prefix groups, and systems that you have configured, including the built-ins (unless `definitions.skipBuiltIns` is true). 585 | 586 | ```js 587 | unit.definitions() 588 | ``` 589 | 590 | Below is an abbreviated sample output from `unit.definitions()`. It can serve as a starting point to create your own definitions. 591 | 592 | ```js 593 | // Sample `definitions` config 594 | { 595 | units: { 596 | '': { quantity: 'UNITLESS', value: 1 }, 597 | meter: { 598 | quantity: 'LENGTH', 599 | prefixGroup: 'LONG', 600 | formatPrefixes: [ 'nano', 'micro', 'milli', 'centi', '', 'kilo' ], 601 | value: 1, 602 | aliases: [ 'meters' ] 603 | }, 604 | m: { 605 | prefixGroup: 'SHORT', 606 | formatPrefixes: [ 'n', 'u', 'm', 'c', '', 'k' ], 607 | value: '1 meter' 608 | }, 609 | inch: { value: '0.0254 meter', aliases: [ 'inches', 'in' ] }, 610 | foot: { value: '12 inch', aliases: [ 'ft', 'feet' ] }, 611 | yard: { value: '3 foot', aliases: [ 'yd', 'yards' ] }, 612 | mile: { value: '5280 ft', aliases: [ 'mi', 'miles' ] }, 613 | ... 614 | }, 615 | prefixGroups: { 616 | NONE: { '': 1 }, 617 | SHORT: { 618 | '': 1, 619 | da: 10, 620 | h: 100, 621 | k: 1000, 622 | ... 623 | d: 0.1, 624 | c: 0.01, 625 | m: 0.001, 626 | ... 627 | }, 628 | }, 629 | systems: { 630 | si: ['m', 'meter', 's', 'A', 'kg', ...], 631 | cgs: ['cm', 's', 'A', 'g', 'K', ...], 632 | us: ['ft', 's', 'A', 'lbm', 'degF', ...] 633 | } 634 | } 635 | ``` 636 | 637 | ### Custom Types ### 638 | 639 | You can extend UnitMath to work with custom types. The `type` option is an object containing several properties, where each property value is a function that replaces the normal `+`, `-`, `*`, `/`, and other arithmetic operators used internally by UnitMath. 640 | 641 | Example using Decimal.js as the custom type: 642 | 643 | ```js 644 | // Configure UnitMath to use Decimal.js 645 | const Decimal = require('decimal.js') 646 | const unit = unit.config({ 647 | type: { 648 | clone: (x) => new Decimal(x), 649 | conv: (x) => new Decimal(x), 650 | add: (a, b) => a.add(b), 651 | sub: (a, b) => a.sub(b), 652 | mul: (a, b) => a.mul(b), 653 | div: (a, b) => a.div(b), 654 | pow: (a, b) => a.pow(b), 655 | eq: (a, b) => a.eq(b), 656 | lt: (a, b) => a.lt(b), 657 | le: (a, b) => a.lte(b), 658 | gt: (a, b) => a.gt(b), 659 | ge: (a, b) => a.gte(b), 660 | abs: (a) => a.abs(), 661 | round: (a) => a.round(), 662 | trunc: (a) => Decimal.trunc(a) 663 | } 664 | }) 665 | 666 | let u = unit2('2.74518864784926316174649567946 m') 667 | ``` 668 | 669 | Below is a table of functions, their description, and when they are required: 670 | 671 | Function | Description | Required? 672 | ---------|-------------|------------ 673 | `clone: (a: T) => T` | Create a new instance of the custom type. | Always 674 | `conv: (a: number \| string \| T) => T` | Convert a number or string into the custom type. | Always 675 | `add: (a: T, b: T) => T` | Add two custom types. | Always 676 | `sub: (a: T, b: T) => T` | Subtract two custom types. | Always 677 | `mul: (a: T, b: T) => T` | Multiply two custom types. | Always 678 | `div: (a: T, b: T) => T` | Divide two custom types. | Always 679 | `pow: (a: T, b: number) => T` | Raise a custom type to a power. | Always 680 | `abs: (a: T) => T` | Return the absolute value of a custom type. | For autoPrefix: true 681 | `lt: (a: T, b: T) => boolean` | Compare two custom types for less than. | For autoPrefix: true 682 | `le: (a: T, b: T) => boolean` | Compare two custom types for less than or equal. | For autoPrefix: true 683 | `gt: (a: T, b: T) => boolean` | Compare two custom types for greater than. | For autoPrefix: true 684 | `ge: (a: T, b: T) => boolean` | Compare two custom types for greater than or equal. | For autoPrefix: true 685 | `eq: (a: T, b: T) => boolean` | Compare two custom types for equality. | For the `equals` function 686 | `round: (a: T) => T` | Round a custom type to the nearest integer. | For the `split` function 687 | `trunc: (a: T) => T` | Truncate a custom type to the nearest integer. | For the `split` function 688 | 689 | 690 | The `add`, `sub`, `mul`, `div`, and `pow` functions replace `+`, `-`, `*`, `/`, and `Math.pow`, respectively. The `clone` function should return a clone of your custom type (same value, different object). 691 | 692 | The `conv` function must, at a minimum, be capable of converting both strings and numbers into your custom type. If given a custom type, it should return it unchanged, or return a clone. Among other things, the `conv` function is used by UnitMath to convert the values of the built-in units to your custom type during initialization. 693 | 694 | UnitMath will also use the `conv` function when constructing units from numbers and strings. If your custom type is representable using decimal or scientific notation (such as `6.022e+23`), you can include both the value and the units in a single string: 695 | 696 | ```js 697 | // Supply a single string, and the numeric portion will be parsed using type.conv 698 | unit('3.1415926535897932384626433832795 rad') 699 | ``` 700 | 701 | If your custom type cannot be represented in decimal or scientific notation, such as is the case with complex numbers and fractions, you will have to pass your custom type and the unit string separately: 702 | 703 | ```js 704 | unit(Fraction(1, 2), 'kg') 705 | ``` 706 | 707 | The functions `clone`, `conv`, `add`, `sub`, `mul`, `div`, and `pow` are always required. Omitting any of these will cause the `config` method to throw an error. The other functions are conditionally required, and you will receive an error if you attempt something that depends on a function you haven't provided. 708 | 709 | ## API Reference ## 710 | 711 | *In the function signatures below, the `T` type is the custom type you have provided, or `number` if you have not provided a custom type.* 712 | ### Constructor ### 713 | 714 | - `unit(value: T, unitString: string): unit` 715 | `unit(value: T): unit` 716 | `unit(valueAndUnitString: string): unit` 717 | `unit(): unit` 718 | 719 | Creates a unit with the specified value and unit string. If `valueAndUnitString` is supplied, it must specify both the numeric portion and the units portion of the unit. 720 | 721 | ```js 722 | const unit = require('unitmath') 723 | unit(60, 'mile/hour') // 60 mile / hour 724 | unit(60) // 60 725 | unit('60 mile/hour') // 60 mile / hour 726 | unit('mile/hour') // mile / hour 727 | unit() // Empty unit 728 | ``` 729 | 730 | `unit()` will parse the special strings `"NaN"`, `"Infinity"`, and `"-Infinity"` as the corresponding numeric values: 731 | 732 | ```js 733 | const unit = require('unitmath') 734 | unit('Infinity kg') // Infinity kg 735 | ``` 736 | 737 | The string used to specify the unit (`valueAndUnitString` or `unitString`) must be in the following format: 738 | 739 | ``` 740 | [value][numerator][/denominator] 741 | 742 | numerator, denominator: 743 | atomicUnit [atomicUnit ...] 744 | 745 | atomicUnit: 746 | [prefix]unit[^power] 747 | 748 | value: 749 | Any floating-point number, or the strings "NaN", "Infinity", or "-Infinity" (without the quotes). 750 | 751 | power: 752 | Any floating-point number 753 | 754 | ``` 755 | 756 | ### Member Functions ### 757 | 758 | - `add(other: unit | string | T) : unit` 759 | 760 | Adds another unit to this unit. If a string or number is supplied as an argument, it is converted to a unit. Both units must have values and have matching dimensions. 761 | 762 | ```js 763 | let a = unit('20 kW') 764 | let b = unit('300 W') 765 | let sum = a.add(b) // 20.3 kW 766 | ``` 767 | 768 | - `sub(other: unit | string | T) : unit` 769 | 770 | Subtract another unit from this unit. If a string or number is supplied as an argument, it is converted to a unit. Both units must have values and have matching dimensions. 771 | 772 | ```js 773 | let a = unit('4 ft') 774 | let b = unit('1 yd') 775 | let difference = a.sub(b) // 1 ft 776 | ``` 777 | 778 | - `mul(other: unit | string | T) : unit` 779 | 780 | Multiplies this unit by another unit. If a string or number is supplied as an argument, it is converted to a unit. 781 | 782 | ```js 783 | let a = unit('8 m') 784 | let b = unit('200 N') 785 | let product = a.mul(b).simplify() // 1.6 kJ 786 | ``` 787 | 788 | - `div(other: unit | string | T) : unit` 789 | 790 | Divides this unit by another unit. If a string or number is supplied as an argument, it is converted to a unit. 791 | 792 | ```js 793 | let a = unit('64 kJ') 794 | let b = unit('16 s') 795 | let quotient = a.div(b).simplify() // 4 kW 796 | ``` 797 | 798 | - `pow(p: number)` 799 | 800 | Raises this unit to the power `p` and returns a new unit. 801 | 802 | ```js 803 | let result = unit('10 m').pow(3) // 1000 m^3 804 | ``` 805 | 806 | - `sqrt()` 807 | 808 | Returns the square root of this unit. 809 | 810 | ```js 811 | unit('1 hectare').sqrt().simplify() // 100 m 812 | ``` 813 | 814 | - `abs()` 815 | 816 | Returns the absolute value of this unit. If this unit has an offset, such as `degC`, this is applied before taking the absolute value. 817 | 818 | ```js 819 | unit('-5 m / s').abs() // 5 m / s 820 | unit('-293.15 degC').abs() // -253.15 degC 821 | ``` 822 | 823 | - `clone()` 824 | 825 | Clones this unit. 826 | 827 | ```js 828 | let unit = require('unitmath') 829 | 830 | let a = unit('40 m/s') // 40 m / s 831 | let b = a.clone() // 40 m / s 832 | ``` 833 | 834 | - `to(target: unit | string)` 835 | 836 | Converts this unit to the specified target unit or string. The returned unit will be "fixed", so it will not be auto-simplified or auto-prefixed in `format()`. 837 | 838 | ```js 839 | let r = unit('10 kg m^2 / s^3 A^2') 840 | r.simplify().toString() // 10 ohm 841 | r.to('kohm').toString() // 0.01 kohm 842 | ``` 843 | 844 | - `toBaseUnits()` 845 | 846 | Returns a new unit in the base representation. 847 | 848 | ```js 849 | unit('10 ft/s').toBaseUnits() // 3.048 m / s 850 | ``` 851 | 852 | - `getValue()` 853 | 854 | Returns the value of this unit, or `null` if the unit is valueless. 855 | 856 | ```js 857 | unit('70 mile/hour').getValue() // 70 858 | unit('km / hour').getValue() // null 859 | ``` 860 | 861 | - `setValue(x: number | string | custom)` 862 | 863 | Returns a copy of this unit but with its value replaced with the given value. Useful if you would like to perform your own operations on a unit's value. If supplied with no arguments, or `null`, will remove the value from the unit. 864 | 865 | ```js 866 | unit('10 m').setValue(20) // 20 m 867 | unit('m').setValue(20) // 20 m 868 | unit('10 ft').setValue(20) // 20 ft 869 | unit('10 ft').setValue() // ft 870 | ``` 871 | 872 | - `getNormalizedValue()` 873 | 874 | Returns the value of this unit if it were to be converted to SI base units (or whatever base units that are defined). Returns `null` if the unit is valueless. 875 | 876 | ```js 877 | unit('10 ft/s').getNormalizedValue() // 3.048 878 | ``` 879 | 880 | - `setNormalizedValue()` 881 | 882 | Returns a copy of this unit but with its value replaced with the given normalized value. 883 | 884 | ```js 885 | unit('ft / s').setNormalizedValue(3.048) // 10 ft / s 886 | ``` 887 | 888 | - `simplify()` 889 | 890 | Attempts to simplify this unit, and returns the simplified unit (or a clone of the original if unsuccessful). `simplify()` is called when a unit is being formatted as a string whenever the config option `simplify` is `'auto'` or `'always'`. 891 | 892 | ```js 893 | unit('10 N m').simplify() // 10 J 894 | ``` 895 | 896 | - `applyBestPrefix(PrefixOptions)` 897 | 898 | Chooses a prefix for this unit so that its value is between `prefixMin` and `prefixMax`, and returns a new unit with the prefix applied. If the value is already within the range, the unit is returned unchanged. The prefix is chosen from the unit's `formatPrefixes` property. 899 | 900 | ```js 901 | unit('0.0001 m').applyBestPrefix() // 0.1 mm 902 | unit('0.1 m').applyBestPrefix() // 10 cm 903 | unit('10000 m').applyBestPrefix() // 10 km 904 | ``` 905 | 906 | - `split(Array(string | unit))` 907 | 908 | Returns an array of units that result from splitting this unit into the given units. The sum of the resulting units is equal to this unit, and each of the returned units is the result of truncating this unit to an integer, and then passing the remainder to the next unit, until the final unit, which takes up all the remainder. 909 | 910 | ```js 911 | unit('51.4934 deg').split([ 'deg', 'arcmin', 'arcsec' ]) // [ 51 deg, 29 arcmin, 36.24 arcsec ] 912 | ``` 913 | 914 | - `getUnits()` 915 | 916 | Returns a clone of this unit with the value removed. Equivalent to `unit.setValue(null)`. 917 | 918 | ```js 919 | unit('8.314 J / mol K').getUnits() // J / mol K 920 | ``` 921 | 922 | - `isCompound()` 923 | 924 | Returns true if this unit's unit list contains two or more units, or one unit with a power not equal to 1. 925 | 926 | ```js 927 | unit('34 kg').isCompound() // false 928 | unit('34 kg/s').isCompound() // true 929 | unit('34 kg^2').isCompound() // true 930 | unit('34 N').isCompound() // false 931 | unit('34 kg m / s^2').isCompound() // true 932 | ``` 933 | 934 | - `isBase()` 935 | 936 | Returns true if this unit's unit list contains exactly one unit with a power equal to 1, and which is the of same dimension as one of the base dimensions length, time, mass, etc., or a user-defined base dimension. 937 | 938 | ```js 939 | 940 | unit = unit.config({ 941 | definitions: { 942 | units: { 943 | myUnit: { quantity: 'MY_NEW_BASE', value: 1 }, 944 | anotherUnit: { value: '4 myUnit' } 945 | } 946 | } 947 | }) 948 | 949 | unit('34 kg').isBase() // true 950 | unit('34 kg/s').isBase() // false 951 | unit('34 kg^2').isBase() // false 952 | unit('34 N').isBase() // false 953 | unit('34 myUnit').isBase() // true 954 | unit('34 anotherUnit').isBase() // true 955 | ``` 956 | 957 | - `getInferredSystem()` 958 | 959 | Examines this unit's unitList to determine the most likely system this unit is expressed in. 960 | 961 | ```js 962 | unit('10 N m').getInferredSystem() // 'si' 963 | unit('10 J / m').getInferredSystem() // 'si' 964 | unit('10 m^3 Pa').getInferredSystem() // 'si' 965 | unit('10 dyne/cm').getInferredSystem() // 'cgs' 966 | unit('10 ft/s').getInferredSystem() // 'us' 967 | unit('10').getInferredSystem() // null 968 | ``` 969 | 970 | - `equalsQuantity(other: unit | string)` 971 | 972 | Returns true if this unit and another unit have equal quantities or dimensions. 973 | 974 | ```js 975 | unit('5 m/s^2').equalsQuantity('4 ft/s^2') // true 976 | ``` 977 | 978 | - `equals(other: unit | string)` 979 | 980 | Returns true if the two units represent the same values. 981 | 982 | ```js 983 | unit('3 ft').equals('1 yard') // true 984 | ``` 985 | 986 | - `compare(other: unit | string)` 987 | 988 | Returns a value indicating whether this unit is less than (-1), greater than (1), or equal to (0), another unit. 989 | 990 | ```js 991 | unit('30 min').compare('1 hour') // -1 992 | unit('60 min').compare('1 hour') // 0 993 | unit('90 min').compare('1 hour') // 1 994 | ``` 995 | 996 | - `lessThan(other: unit | string)` 997 | 998 | Compares this and another unit and returns true if this unit is less than the other. 999 | 1000 | ```js 1001 | unit('80 cm').lessThan('1 m') // true 1002 | unit('100 cm').lessThan('1 m') // false 1003 | unit('120 cm').lessThan('1 m') // false 1004 | ``` 1005 | 1006 | - `lessThanOrEqual(other: unit | string)` 1007 | 1008 | Compares this and another unit and returns true if this unit is less than or equal to the other. 1009 | 1010 | ```js 1011 | unit('80 cm').lessThanOrEqual('1 m') // true 1012 | unit('100 cm').lessThanOrEqual('1 m') // true 1013 | unit('120 cm').lessThanOrEqual('1 m') // false 1014 | ``` 1015 | 1016 | - `greaterThan(other: unit | string)` 1017 | 1018 | Compares this and another unit and returns true if this unit is greater than the other. 1019 | 1020 | ```js 1021 | unit('80 cm').greaterThan('1 m') // false 1022 | unit('100 cm').greaterThan('1 m') // false 1023 | unit('120 cm').greaterThan('1 m') // true 1024 | ``` 1025 | 1026 | - `greaterThanOrEqual(other: unit | string)` 1027 | 1028 | Compares this and another unit and returns true if this unit is greater than or equal to the other. 1029 | 1030 | ```js 1031 | unit('80 cm').greaterThanOrEqual('1 m') // false 1032 | unit('100 cm').greaterThanOrEqual('1 m') // true 1033 | unit('120 cm').greaterThanOrEqual('1 m') // true 1034 | ``` 1035 | 1036 | - `toString(options)` 1037 | 1038 | Formats this unit as a string. Formatting options can be supplied which will override the configured options. See [Formatting](#formatting) for a list of all options and their effects. 1039 | 1040 | ### Static Functions ### 1041 | 1042 | - `add(a: unit | string | T, b: unit | string | T) : unit` 1043 | 1044 | Adds two units. If a string or number is supplied as an argument, it is converted to a unit. Both units must have values and have matching dimensions. 1045 | 1046 | ```js 1047 | let sum = unit.add('20 kW', '300 W') // 20.3 kW 1048 | ``` 1049 | 1050 | - `sub(a: unit | string | T, b: unit | string | T) : unit` 1051 | 1052 | Subtract two units. If a string or number is supplied as an argument, it is converted to a unit. Both units must have values and have matching dimensions. 1053 | 1054 | ```js 1055 | let difference = unit.sub('4 ft', '1 yd') // 1 ft 1056 | ``` 1057 | 1058 | - `mul(a: unit | string | T, b: unit | string | T) : unit` 1059 | 1060 | Multiplies two units. If a string or number is supplied as an argument, it is converted to a unit. 1061 | 1062 | ```js 1063 | let product = unit.mul('8 m/s', '200 N').simplify() // 1.6 kW 1064 | ``` 1065 | 1066 | - `div(a: unit | string | T, b: unit | string | T) : unit` 1067 | 1068 | Divides two units. If a string or number is supplied as an argument, it is converted to a unit. 1069 | 1070 | ```js 1071 | let quotient = unit.div('64 kJ', '16 s').simplify() // 4 kW 1072 | ``` 1073 | 1074 | - `pow(a: unit | string | T, p: number) : unit` 1075 | 1076 | Raises a unit to the power `p` and returns a new unit. 1077 | 1078 | ```js 1079 | let result = unit.pow('10 m', 3) // 1000 m^3 1080 | ``` 1081 | 1082 | - `sqrt(a: unit | string | T) : unit` 1083 | 1084 | Returns the square root of a unit. 1085 | 1086 | ```js 1087 | unit.sqrt('1 hectare').simplify() // 100 m 1088 | ``` 1089 | 1090 | - `abs(a: unit | string | T) : unit` 1091 | 1092 | Returns the absolute value of a unit. If the unit has an offset, such as `degC`, this is applied before taking the absolute value. 1093 | 1094 | ```js 1095 | unit.abs('-5 m / s') // 5 m / s 1096 | unit.abs('300 degC') // -246.3 degC 1097 | ``` 1098 | 1099 | - `to(a: unit | string | number, b: unit | string)` 1100 | 1101 | Converts a unit to the specified target unit or string. The returned unit will be "fixed", so it will not be auto-simplified or auto-prefixed in `format()`. 1102 | 1103 | ```js 1104 | unit.to('10 kg m^2 / s^3 A^2', 'kohm') // 0.01 kohm 1105 | ``` 1106 | 1107 | - `config(options:object)` 1108 | 1109 | Configure a new unit namespace with the given options (see [Configuring](#configuring)) 1110 | 1111 | ```js 1112 | const unit = require('unitmath').config({ option1, option2, ... }) 1113 | ``` 1114 | 1115 | - `getConfig()` 1116 | 1117 | Returns the current configuration. 1118 | 1119 | ```js 1120 | const unit = require('unitmath') 1121 | unit.getConfig() 1122 | ``` 1123 | 1124 | - `exists(singleUnitString:string)` 1125 | 1126 | Tests if the given unit, optionally with a prefix, exists. 1127 | 1128 | ```js 1129 | const unit = require('unitmath') 1130 | unit.exists('km') // true 1131 | ``` 1132 | 1133 | - `definitions()` 1134 | 1135 | Return the current unit definitions in effect. (User's own definitions can be queried through `unit.getConfig().definitions`.) 1136 | 1137 | ## Contributing 1138 | 1139 | This is a community-supported project; all contributions are welcome. Please open an issue or submit a pull request. 1140 | 1141 | ## Acknowledgements 1142 | 1143 | Many thanks to Jos de Jong (@josdejong), the original author of `Unit.js`, who suggested the idea of splitting the file off from [Math.js](https://mathjs.org/) and into its own library. 1144 | 1145 | ### Contributors 1146 | 1147 | - Harry Sarson (https://github.com/harrysarson) 1148 | - Nick Ewing (https://github.com/nickewing) 1149 | - Michal Grňo (https://github.com/m93a) 1150 | - Adriano Fantini (https://github.com/adrfantini) 1151 | - Ben Chidlow (https://github.com/fezproof) 1152 | 1153 | ## License 1154 | 1155 | UnitMath is released under the Apache-2.0 license. -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "targets": { 7 | "node": 10 8 | } 9 | } 10 | ] 11 | ] 12 | } -------------------------------------------------------------------------------- /docs/css/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family:Arial, Helvetica, sans-serif; 3 | } 4 | 5 | .container{ 6 | max-width: 800px; 7 | margin: 0 auto; 8 | } 9 | 10 | div.thingies{ 11 | height:30px; 12 | } 13 | 14 | .field { 15 | font-size:120%; 16 | margin-bottom:1em; 17 | max-width:400px; 18 | } 19 | 20 | .field label { 21 | display:block; 22 | } 23 | 24 | .field input { 25 | width:100%; 26 | font-size: 1em; 27 | padding:8px; 28 | border:1px solid #ccc; 29 | border-radius:4px; 30 | } 31 | 32 | code { 33 | background:#eee; 34 | padding:4px; 35 | border-radius:4px; 36 | } 37 | 38 | .buttonWrapper{ 39 | display:flex; 40 | } 41 | 42 | .buttonWrapper button { 43 | font-size: 24px; 44 | display:inline-block; 45 | width: 40px; 46 | height: 40px; 47 | } 48 | 49 | ul li { 50 | line-height: 1.8; 51 | } -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | UnitMath Demo 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 |

UnitMath Demo

15 | 16 |

Calculator

17 |
18 | 19 | 20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 | 29 | 30 |
31 |
32 | 33 | 34 | 35 | 36 |
37 | 38 |

Keyboard shortcuts

39 | 49 | 50 |

Examples

51 | 59 | 60 |

Note

61 |

This calculator cannot parse complicated grammar. It is just to highlight some of the capabilities of the 62 | UnitMath 63 | library. For a fully-featured math library with a parser that supports unit conversion and arithmetic, try Math.js.

65 | 66 | View Calculator Source 67 | 68 |

UnitMath Documentation

69 | https://github.com/ericman314/UnitMath 70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /docs/js/UnitMath.d.ts: -------------------------------------------------------------------------------- 1 | declare const symIsDefaultFun: unique symbol; 2 | interface TypeArithmetics { 3 | conv: { 4 | (a: any): T; 5 | [symIsDefaultFun]?: boolean; 6 | }; 7 | clone: { 8 | (a: T): T; 9 | [symIsDefaultFun]?: boolean; 10 | }; 11 | abs: { 12 | (a: T): T; 13 | [symIsDefaultFun]?: boolean; 14 | }; 15 | add: { 16 | (a: T, b: T): T; 17 | [symIsDefaultFun]?: boolean; 18 | }; 19 | sub: { 20 | (a: T, b: T): T; 21 | [symIsDefaultFun]?: boolean; 22 | }; 23 | mul: { 24 | (a: T, b: T): T; 25 | [symIsDefaultFun]?: boolean; 26 | }; 27 | div: { 28 | (a: T, b: T): T; 29 | [symIsDefaultFun]?: boolean; 30 | }; 31 | pow: { 32 | (a: T, b: T): T; 33 | [symIsDefaultFun]?: boolean; 34 | }; 35 | eq: { 36 | (a: T, b: T): boolean; 37 | [symIsDefaultFun]?: boolean; 38 | }; 39 | lt: { 40 | (a: T, b: T): boolean; 41 | [symIsDefaultFun]?: boolean; 42 | }; 43 | le: { 44 | (a: T, b: T): boolean; 45 | [symIsDefaultFun]?: boolean; 46 | }; 47 | ge: { 48 | (a: T, b: T): boolean; 49 | [symIsDefaultFun]?: boolean; 50 | }; 51 | gt: { 52 | (a: T, b: T): boolean; 53 | [symIsDefaultFun]?: boolean; 54 | }; 55 | round: { 56 | (a: T): T; 57 | [symIsDefaultFun]?: boolean; 58 | }; 59 | trunc: { 60 | (a: T): T; 61 | [symIsDefaultFun]?: boolean; 62 | }; 63 | } 64 | interface AtomicUnit { 65 | unit: UnitPropsExtended; 66 | prefix: string; 67 | power: number; 68 | } 69 | interface FormatOptions { 70 | precision?: number; 71 | parentheses?: boolean; 72 | formatter?: { 73 | (a: T): string; 74 | [symIsDefaultFun]?: boolean; 75 | }; 76 | } 77 | interface PrefixOptions { 78 | autoPrefix?: boolean; 79 | prefixMin?: T; 80 | prefixMax?: T; 81 | formatPrefixDefault?: 'all' | 'none'; 82 | } 83 | interface SimplifyOptions extends PrefixOptions { 84 | system?: string; 85 | } 86 | type Options = FormatOptions & SimplifyOptions & { 87 | type?: Partial>; 88 | definitions?: Partial & { 89 | skipBuiltIns?: boolean; 90 | }; 91 | }; 92 | interface PrefixGroups { 93 | [prefixGroup: string]: Record; 94 | } 95 | interface UnitSystems { 96 | [system: string]: string[]; 97 | } 98 | interface UnitPropsCommons { 99 | prefixGroup?: string; 100 | basePrefix?: string; 101 | formatPrefixes?: string[]; 102 | aliases?: string[]; 103 | offset?: number; 104 | } 105 | interface UnitPropsWithQuantity extends UnitPropsCommons { 106 | quantity: string; 107 | value: number; 108 | } 109 | interface UnitPropsStringValue extends UnitPropsCommons { 110 | quantity?: undefined; 111 | value: string; 112 | } 113 | interface UnitPropsTupleValue extends UnitPropsCommons { 114 | quantity?: undefined; 115 | value: [number, string]; 116 | } 117 | type UnitProps = UnitPropsStringValue | UnitPropsWithQuantity | UnitPropsTupleValue; 118 | interface UnitPropsExtended { 119 | name: string; 120 | quantity?: string; 121 | value: T; 122 | dimension: Record; 123 | prefixGroup: Record; 124 | basePrefix?: string; 125 | formatPrefixes?: string[]; 126 | aliases?: string[]; 127 | offset: T; 128 | } 129 | interface Definitions { 130 | prefixGroups: PrefixGroups; 131 | systems: UnitSystems; 132 | units: Record; 133 | } 134 | interface NullableDefinitions extends Omit { 135 | units: Record; 136 | } 137 | interface DefinitionsExtended { 138 | prefixGroups: PrefixGroups; 139 | systems: Record[]>; 140 | units: Record>; 141 | } 142 | interface Unit { 143 | readonly type: 'Unit'; 144 | value: T | null; 145 | unitList: AtomicUnit[]; 146 | dimension: Record; 147 | /** whether the prefix and the units are fixed */ 148 | /** 149 | * create a copy of this unit 150 | */ 151 | clone(): Unit; 152 | /** 153 | * Adds two units. Both units' dimensions must be equal. 154 | * @param {Unit|string|T} other The unit to add to this one. If a string is supplied, it will be converted to a unit. 155 | * @returns {Unit} The result of adding this and the other unit. 156 | */ 157 | add(other: Unit | string | T): Unit; 158 | add(value: T, unit: string): Unit; 159 | /** 160 | * Subtracts two units. Both units' dimensions must be equal. 161 | * @param {Unit|string|T} other The unit to subtract from this one. If a string is supplied, it will be converted to a unit. 162 | * @returns {Unit} The result of subtract this and the other unit. 163 | */ 164 | sub(other: Unit | string | T): Unit; 165 | sub(value: T, unit: string): Unit; 166 | /** 167 | * Multiplies two units. 168 | * @param {Unit|string|T} other The unit to multiply to this one. 169 | * @returns {Unit} The result of multiplying this and the other unit. 170 | */ 171 | mul(other: Unit | string | T): Unit; 172 | mul(value: T, unit: string): Unit; 173 | /** 174 | * Divides two units. 175 | * @param {Unit|string|T} other The unit to divide this unit by. 176 | * @returns {Unit} The result of dividing this by the other unit. 177 | */ 178 | div(other: Unit | string | T): Unit; 179 | div(value: T, unit: string): Unit; 180 | /** 181 | * Calculate the power of a unit 182 | * @memberof Unit 183 | * @param {number|custom} p 184 | * @returns {Unit} The result: this^p 185 | */ 186 | pow(p: number): Unit; 187 | /** 188 | * Takes the square root of a unit. 189 | * @memberof Unit 190 | * @returns {Unit} The square root of this unit. 191 | */ 192 | sqrt(): Unit; 193 | /** 194 | * Returns the absolute value of this unit. 195 | * @memberOf Unit 196 | * @returns {Unit} The absolute value of this unit. 197 | */ 198 | abs(): Unit; 199 | /** 200 | * Returns an array of units whose sum is equal to this unit, where each unit in the array is taken from the supplied string array. 201 | * @param {string[]} units A string array of units to split this unit into. 202 | * @returns {Unit[]} An array of units 203 | */ 204 | split(units: (string | Unit)[]): Unit[]; 205 | /** 206 | * Convert the unit to a specific unit. 207 | * @memberof Unit 208 | * @param {string | Unit} valuelessUnit A unit without value. Can have prefix, like "cm". 209 | * @returns {Unit} Returns a clone of the unit converted to the specified unit. 210 | */ 211 | to(valuelessUnit: string | Unit): Unit; 212 | /** 213 | * Fix the units and prevent them from being automatically simplified. 214 | * @memberof Unit 215 | * @returns {Unit} Returns a clone of the unit with a fixed prefix and unit. 216 | */ 217 | /** 218 | * Convert the unit to base units. 219 | * @memberof Unit 220 | * @returns {Unit} Returns a clone of the unit in the base units. 221 | */ 222 | toBaseUnits(): Unit; 223 | /** 224 | Get the complexity of this unit, or in other words, the number of symbols used to format the unit. 225 | @memberof Unit 226 | @returns {number} The complexity or number of symbols used to format the unit. 227 | */ 228 | getComplexity(): number; 229 | /** 230 | * Returns a new unit with the given value. 231 | * @param {number | string | custom} value 232 | * @returns A new unit with the given value. 233 | */ 234 | setValue(value?: string | T | null): Unit; 235 | /** 236 | * Returns this unit's value. 237 | * @returns The value of this unit. 238 | */ 239 | getValue(): T | null; 240 | /** 241 | * Returns this unit's normalized value, which is the value it would have if it were to be converted to SI base units (or whatever base units are defined) 242 | * @returns The notmalized value of the unit. 243 | */ 244 | getNormalizedValue(): T | null; 245 | /** 246 | * Returns a new unit with the given normalized value. 247 | * @param {number | string | custom} normalizedValue 248 | * @returns A new unit with the given normalized value. 249 | */ 250 | setNormalizedValue(normalizedValue: string | T): Unit; 251 | /** 252 | * Simplify this Unit's unit list and return a new Unit with the simplified list. 253 | * The returned Unit will contain a list of the "best" units for formatting. 254 | * @returns {Unit} A simplified unit if possible, or the original unit if it could not be simplified. 255 | */ 256 | simplify(options?: SimplifyOptions & PrefixOptions): Unit; 257 | /** 258 | * Choose the best prefix for the Unit. 259 | * @returns {Unit} A new unit that contains the "best" prefix, or, if no better prefix was found, returns the same unit unchanged. 260 | */ 261 | applyBestPrefix(prefixOptions?: PrefixOptions): Unit; 262 | /** 263 | * Returns this unit without a value. 264 | * @memberof Unit 265 | * @returns {Unit} A new unit formed by removing the value from this unit. 266 | */ 267 | getUnits(): Unit; 268 | /** 269 | * Examines this unit's unitList to determine the most likely system this unit is currently expressed in. 270 | * @returns {string | null} The system this unit is most likely expressed in, or null if no likely system was recognized. 271 | */ 272 | getInferredSystem(): string | null; 273 | /** 274 | * Returns whether the unit is compound (like m/s, cm^2) or not (kg, N, hogshead) 275 | * @memberof Unit 276 | * @returns True if the unit is compound 277 | */ 278 | isCompound(): boolean; 279 | /** 280 | * Return whether the given array of unit pieces is a base unit with single dimension such as kg or feet, but not m/s or N or J. 281 | * @param unitList Array of unit pieces 282 | * @returns True if the unit is base 283 | */ 284 | isBase(): boolean; 285 | /** 286 | * check if this unit matches the given quantity 287 | * @memberof Unit 288 | * @param {QUANTITY | string | undefined} quantity 289 | */ 290 | /** 291 | * Check if this unit has a dimension equal to another unit 292 | * @param {Unit} other 293 | * @return {boolean} true if equal dimensions 294 | */ 295 | equalsQuantity(other: Unit): boolean; 296 | /** 297 | * Returns a string array of all the quantities that match this unit. 298 | * @return {string[]} The matching quantities, or an empty array if there are no matching quantities. 299 | */ 300 | /** 301 | * Check if this unit equals another unit 302 | * @memberof Unit 303 | * @param {Unit} other 304 | * @return {boolean} true if both units are equal 305 | */ 306 | equals(other: Unit | string | T): boolean; 307 | /** 308 | * Compare this unit to another and return a value indicating whether this unit is less than, greater than, or equal to the other. 309 | * @param {Unit} other 310 | * @return {number} -1 if this unit is less than, 1 if this unit is greater than, and 0 if this unit is equal to the other unit. 311 | */ 312 | compare(other: Unit | string | T): -1 | 0 | 1; 313 | /** 314 | * Compare this unit to another and return whether this unit is less than the other. 315 | * @param {Unit} other 316 | * @return {boolean} true if this unit is less than the other. 317 | */ 318 | lessThan(other: Unit | string | T): boolean; 319 | /** 320 | * Compare this unit to another and return whether this unit is less than or equal to the other. 321 | * @param {Unit} other 322 | * @return {boolean} true if this unit is less than or equal the other. 323 | */ 324 | lessThanOrEqual(other: Unit | string | T): boolean; 325 | /** 326 | * Compare this unit to another and return whether this unit is greater than the other. 327 | * @param {Unit} other 328 | * @return {boolean} true if this unit is greater than the other. 329 | */ 330 | greaterThan(other: Unit | string | T): boolean; 331 | /** 332 | * Compare this unit to another and return whether this unit is greater than or equal to the other. 333 | * @param {Unit} other 334 | * @return {boolean} true if this unit is greater than or equal the other. 335 | */ 336 | greaterThanOrEqual(other: Unit | string | T): boolean; 337 | /** 338 | * Get a string representation of the Unit, with optional formatting options. Alias of `format`. 339 | * @memberof Unit 340 | * @param {Object} [opts] Formatting options. 341 | * @return {string} 342 | */ 343 | toString(formatOptions?: FormatOptions, ...userArgs: any[]): string; 344 | } 345 | interface UnitFactory { 346 | (): Unit; 347 | (str: string): Unit; 348 | (value: number | T | string | null, unitString?: string): Unit; 349 | config(newOptions: Options): UnitFactory; 350 | getConfig(): Options; 351 | definitions(): Definitions; 352 | add(a: Unit | string | T, b: Unit | string | T): Unit; 353 | sub(a: Unit | string | T, b: Unit | string | T): Unit; 354 | mul(a: Unit | string | T, b: Unit | string | T): Unit; 355 | div(a: Unit | string | T, b: Unit | string | T): Unit; 356 | pow(a: Unit | string | T, b: number): Unit; 357 | sqrt(a: Unit | string | T): Unit; 358 | abs(a: Unit | string | T): Unit; 359 | to(a: Unit | string | T, valuelessUnit: Unit | string): Unit; 360 | toBaseUnits(a: Unit | string | T): Unit; 361 | exists(unit: string): boolean; 362 | _unitStore: UnitStore; 363 | } 364 | interface UnitStore { 365 | parser(input: string): ParsedUnit; 366 | originalDefinitions: Definitions; 367 | defs: DefinitionsExtended; 368 | exists(name: string): boolean; 369 | findUnit(unitString: string): { 370 | unit: UnitPropsExtended; 371 | prefix: string; 372 | } | null; 373 | } 374 | interface ParsedUnit { 375 | type: 'Unit'; 376 | unitList: AtomicUnit[]; 377 | dimension: Record; 378 | value: T | null; 379 | } 380 | 381 | declare const firstUnit: UnitFactory; 382 | 383 | export { firstUnit as default }; 384 | -------------------------------------------------------------------------------- /docs/js/UnitMath.min.js: -------------------------------------------------------------------------------- 1 | const e=" \t()*";function t(t,i){let n,r,s;function u(){for(;s&&e.includes(s);)o()}function a(e){return e>="0"&&"9">=e}function o(){r++,s=n.charAt(r)}function l(e){r=e,s=n.charAt(r)}function f(){let e,t="";if(e=r,"+"===s?o():"-"===s&&(t+=s,o()),!function(e){return e>="0"&&"9">=e||"."===e}(s))return l(e),null;if("."===s){if(t+=s,o(),!a(s))return l(e),null}else{for(;a(s);)t+=s,o();"."===s&&(t+=s,o())}for(;a(s);)t+=s,o();if("E"===s||"e"===s){let e="";const i=r;if(e+=s,o(),"+"!==s&&"-"!==s||(e+=s,o()),!a(s))return l(i),t;for(t+=e;a(s);)t+=s,o()}return t}function p(){let e="",t=n.charCodeAt(r);for(;t>=48&&57>=t||t>=65&&90>=t||t>=97&&122>=t;)e+=s,o(),t=n.charCodeAt(r);return t=e.charCodeAt(0),t>=65&&90>=t||t>=97&&122>=t?e:null}function m(e){return s===e?(o(),e):null}return function(e){if(n=e,r=-1,s="","string"!=typeof n)throw new TypeError("Invalid argument in parse, string expected");const a={type:"Unit",value:null,unitList:[],dimension:{}};let l=1,c=!1;o(),u();const v=function(){const e=["NaN","Infinity","-Infinity"];for(let t of e)if(n.substr(r,t.length)===t)return r+=t.length,s=n.charAt(r),t;return null}()||f();for(v&&(a.value=t.type.conv(v),u(),m("/")&&(l=-1,c=!0));;){let t;if(u(),!s)break;{const e=s;if(t=p(),null===t)throw new SyntaxError('Unexpected "'+e+'" in "'+n+'" at index '+r)}const o=i(t);if(null===o)throw new SyntaxError('Unit "'+t+'" not found.');let v=l;if(u(),m("^")){u();const t=f();if(null===t)throw new SyntaxError('In "'+e+'", "^" must be followed by a floating-point number');v*=+t}a.unitList.push({unit:o.unit,prefix:o.prefix,power:v});for(let e of Object.keys(o.unit.dimension))a.dimension[e]=(a.dimension[e]||0)+(o.unit.dimension[e]||0)*v;if(u(),c=!1,m("/")){if(-1===l)throw new SyntaxError(`Unexpected additional "/" in "${n}" at index ${r}`);l=-1,c=!0}}if(c)throw new SyntaxError('Trailing characters: "'+e+'"');return a}}function i(e,t,i){let n,s,u,a;if(null==t||0===e.length)return t;if(r(e)){let r=t;for(let t=0;e.length>t;t++)n=i.conv(e[t].unit.value),a=i.conv(e[t].unit.prefixGroup[e[t].prefix]),u=i.conv(e[t].power),r=i.mul(r,i.pow(i.mul(n,a),u));return r}return n=i.conv(e[0].unit.value),s=i.conv(e[0].unit.offset),a=i.conv(e[0].unit.prefixGroup[e[0].prefix]),i.mul(i.add(i.mul(t,a),s),n)}function n(e,t,i){let n,s,u,a;if(null==t||0===e.length)return t;if(r(e)){let r=t;for(let t=0;e.length>t;t++)n=i.conv(e[t].unit.value),a=i.conv(e[t].unit.prefixGroup[e[t].prefix]),u=i.conv(e[t].power),r=i.div(r,i.pow(i.mul(n,a),u));return r}return n=i.conv(e[0].unit.value),a=i.conv(e[0].unit.prefixGroup[e[0].prefix]),s=i.conv(e[0].unit.offset),i.div(i.sub(i.div(t,n),s),a)}function r(e){return 0!==e.length&&(e.length>1||Math.abs(e[0].power-1)>1e-15)}function s(e){return 1===e.length&&1e-15>Math.abs(e[0].power-1)&&1===Object.keys(e[0].unit.dimension).length&&1===e[0].unit.dimension[Object.keys(e[0].unit.dimension)[0]]}const u={si:["m","meter","s","A","kg","K","mol","rad","b","F","C","S","V","J","N","Hz","ohm","H","cd","lm","lx","Wb","T","W","Pa","ohm","sr"],cgs:["cm","s","A","g","K","mol","rad","b","F","C","S","V","erg","dyn","Hz","ohm","H","cd","lm","lx","Wb","T","Pa","ohm","sr"],us:["ft","mi","mile","in","inch","s","A","lbm","degF","mol","rad","b","F","C","S","V","BTU","lbf","Hz","ohm","H","cd","lm","lx","Wb","T","psi","ohm","sr","hp"]},a={NONE:{"":1},SHORT:{"":1,da:10,h:100,k:1e3,M:1e6,G:1e9,T:1e12,P:1e15,E:1e18,Z:1e21,Y:1e24,d:.1,c:.01,m:.001,u:1e-6,n:1e-9,p:1e-12,f:1e-15,a:1e-18,z:1e-21,y:1e-24},LONG:{"":1,deca:10,hecto:100,kilo:1e3,mega:1e6,giga:1e9,tera:1e12,peta:1e15,exa:1e18,zetta:1e21,yotta:1e24,deci:.1,centi:.01,milli:.001,micro:1e-6,nano:1e-9,pico:1e-12,femto:1e-15,atto:1e-18,zepto:1e-21,yocto:1e-24},BINARY_SHORT_SI:{"":1,k:1e3,M:1e6,G:1e9,T:1e12,P:1e15,E:1e18,Z:1e21,Y:1e24},BINARY_SHORT_IEC:{"":1,Ki:1024,Mi:1048576,Gi:1073741824,Ti:1099511627776,Pi:0x4000000000000,Ei:Math.pow(1024,6),Zi:Math.pow(1024,7),Yi:Math.pow(1024,8)},BINARY_LONG_SI:{"":1,kilo:1e3,mega:1e6,giga:1e9,tera:1e12,peta:1e15,exa:1e18,zetta:1e21,yotta:1e24},BINARY_LONG_IEC:{"":1,kibi:1024,mebi:1048576,gibi:1073741824,tebi:1099511627776,pebi:0x4000000000000,exi:Math.pow(1024,6),zebi:Math.pow(1024,7),yobi:Math.pow(1024,8)},BTU:{"":1,MM:1e6},SHORT_LONG:{},BINARY_SHORT:{},BINARY_LONG:{}};a.SHORT_LONG=Object.assign({},a.SHORT,a.LONG),a.BINARY_SHORT=Object.assign({},a.BINARY_SHORT_SI,a.BINARY_SHORT_IEC),a.BINARY_LONG=Object.assign({},a.BINARY_LONG_SI,a.BINARY_LONG_IEC);const o={"":{quantity:"UNITLESS",value:1},m:{quantity:"LENGTH",prefixGroup:"SHORT",formatPrefixes:["n","u","m","c","","k"],value:1},meter:{prefixGroup:"LONG",formatPrefixes:["nano","micro","milli","centi","","kilo"],value:"1 m",aliases:["meters"]},inch:{value:"0.0254 meter",aliases:["inches","in"]},foot:{value:"12 inch",aliases:["ft","feet"]},yard:{value:"3 foot",aliases:["yd","yards"]},mile:{value:"5280 ft",aliases:["mi","miles"]},link:{value:"7.92 in",aliases:["li","links"]},rod:{value:"25 link",aliases:["rd","rods"]},chain:{value:"100 link",aliases:["ch","chains"]},angstrom:{value:"1e-10 m",aliases:["angstroms"]},mil:{value:"1e-3 inch"},sqin:{value:"1 in^2"},sqft:{value:"1 ft^2"},sqyd:{value:"1 yd^2"},sqmi:{value:"1 mi^2"},sqrd:{value:"1 rod^2"},sqch:{value:"1 chain^2"},sqmil:{value:"1 mil^2"},acre:{value:"10 chain^2"},hectare:{value:"1e4 m^2"},L:{prefixGroup:"SHORT",formatPrefixes:["n","u","m",""],value:"1e-3 m^3",aliases:["l","lt"]},litre:{prefixGroup:"LONG",formatPrefixes:["nano","micro","milli",""],value:"1 L",aliases:["liter","liters","litres"]},cuin:{value:"1 in^3"},cuft:{value:"1 ft^3"},cuyd:{value:"1 yd^3"},teaspoon:{value:"4.92892159375 mL",aliases:["teaspoons","tsp"]},tablespoon:{value:"3 teaspoon",aliases:["tablespoons","tbsp"]},drop:{value:"0.05 mL"},gtt:{value:"0.05 mL"},minim:{value:"0.0125 teaspoon",aliases:["minims"]},fluidounce:{value:"0.125 cups",aliases:["floz","fluidounces"]},fluiddram:{value:"0.125 floz",aliases:["fldr","fluiddrams"]},cc:{value:"1 cm^3"},cup:{value:"236.5882365 mL",aliases:["cp","cups"]},pint:{value:"2 cup",aliases:["pt","pints"]},quart:{value:"4 cup",aliases:["qt","quarts"]},gallon:{value:"16 cup",aliases:["gal","gallons"]},oilbarrel:{value:"42 gal",aliases:["obl","oilbarrels"]},g:{quantity:"MASS",prefixGroup:"SHORT",formatPrefixes:["n","u","m","","k"],value:.001,basePrefix:"k"},gram:{prefixGroup:"LONG",formatPrefixes:["nano","micro","milli","","kilo"],value:"1 g"},poundmass:{value:"0.45359237 kg",aliases:["lb","lbs","lbm","poundmasses"]},ton:{value:"2000 lbm"},tonne:{prefixGroup:"LONG",formatPrefixes:["","kilo","mega","giga"],value:"1000 kg"},t:{prefixGroup:"SHORT",value:"1 tonne"},grain:{value:"64.79891 mg",aliases:["gr"]},ounce:{value:"0.0625 lbm",aliases:["oz","ounces"]},dram:{value:"0.0625 oz",aliases:["dr"]},hundredweight:{value:"100 lbm",aliases:["cwt","hundredweights"]},stick:{value:"4 oz",aliases:["sticks"]},stone:{value:"14 lbm"},s:{quantity:"TIME",prefixGroup:"SHORT",formatPrefixes:["f","p","n","u","m",""],value:1,aliases:["sec"]},min:{value:"60 s",aliases:["minute","minutes"]},h:{value:"60 min",aliases:["hr","hrs","hour","hours"]},second:{prefixGroup:"LONG",formatPrefixes:["femto","pico","nano","micro","milli",""],value:"1 s",aliases:["seconds"]},day:{value:"24 hr",aliases:["days"]},week:{value:"7 day",aliases:["weeks"]},month:{value:"30.4375 day",aliases:["months"]},year:{value:"365.25 day",aliases:["years"]},decade:{value:"10 year",aliases:["decades"]},century:{value:"100 year",aliases:["centuries"]},millennium:{value:"1000 year",aliases:["millennia"]},hertz:{prefixGroup:"LONG",formatPrefixes:["","kilo","mega","giga","tera"],value:"1/s"},Hz:{prefixGroup:"SHORT",formatPrefixes:["","k","M","G","T"],value:"1 hertz"},rad:{quantity:"ANGLE",prefixGroup:"SHORT",formatPrefixes:["m",""],value:1},radian:{prefixGroup:"LONG",formatPrefixes:["milli",""],value:"1 rad",aliases:["radians"]},sr:{quantity:"SOLID_ANGLE",prefixGroup:"SHORT",formatPrefixes:["u","m",""],value:1},steradian:{prefixGroup:"LONG",formatPrefixes:["micro","milli",""],value:"1 sr",aliases:["steradians"]},deg:{value:[Math.PI/180,"rad"],aliases:["degree","degrees"]},grad:{prefixGroup:"SHORT",formatPrefixes:["c"],value:[Math.PI/200,"rad"]},gradian:{prefixGroup:"LONG",formatPrefixes:["centi",""],value:[Math.PI/200,"rad"],aliases:["gradians"]},cycle:{value:[2*Math.PI,"rad"],aliases:["cycles"]},arcmin:{value:"0.016666666666666666 deg",aliases:["arcminute","arcminutes"]},arcsec:{value:"0.016666666666666666 arcmin",aliases:["arcsecond","arcseconds"]},A:{quantity:"CURRENT",prefixGroup:"SHORT",formatPrefixes:["u","m","","k"],value:1},ampere:{prefixGroup:"LONG",formatPrefixes:["micro","milli","","kilo"],value:"1 A",aliases:["amperes"]},K:{quantity:"TEMPERATURE",prefixGroup:"SHORT",formatPrefixes:["n","u","m",""],value:1},kelvin:{prefixGroup:"LONG",formatPrefixes:["nano","micro","milli",""],value:"1 K"},degC:{value:"1 K",offset:273.15,aliases:["celsius"]},degR:{value:[1/1.8,"K"],aliases:["rankine","R"]},degF:{value:"1 R",offset:459.67,aliases:["fahrenheit"]},mol:{quantity:"AMOUNT_OF_SUBSTANCE",prefixGroup:"SHORT",formatPrefixes:["","k"],value:1},mole:{prefixGroup:"LONG",formatPrefixes:["","kilo"],value:"1 mol",aliases:["moles"]},cd:{quantity:"LUMINOUS_INTENSITY",value:1,prefixGroup:"SHORT",formatPrefixes:["","m"]},candela:{value:"1 cd",prefixGroup:"LONG",formatPrefixes:["","milli"]},lumen:{prefixGroup:"LONG",value:"1 cd sr",aliases:["lumens"]},lm:{prefixGroup:"SHORT",value:"1 lumen"},lux:{prefixGroup:"LONG",value:"1 cd/m^2"},lx:{prefixGroup:"SHORT",value:"1 lux"},N:{prefixGroup:"SHORT",formatPrefixes:["u","m","","k","M"],value:"1 kg m/s^2"},newton:{prefixGroup:"LONG",formatPrefixes:["micro","milli","","kilo","mega"],value:"1 N",aliases:["newtons"]},dyn:{prefixGroup:"SHORT",formatPrefixes:["m","k","M"],value:"1 g cm/s^2"},dyne:{prefixGroup:"LONG",formatPrefixes:["milli","kilo","mega"],value:"1 dyn"},lbf:{value:"4.4482216152605 N",aliases:["poundforce"]},kip:{value:"1000 lbf",aliases:["kips"]},J:{prefixGroup:"SHORT",formatPrefixes:["m","","k","M","G"],value:"1 N m"},joule:{prefixGroup:"LONG",formatPrefixes:["milli","","kilo","mega","giga"],value:"1 J",aliases:["joules"]},erg:{value:"1 dyn cm"},Wh:{prefixGroup:"SHORT",formatPrefixes:["k","M","G","T"],value:"1 W hr"},BTU:{prefixGroup:"BTU",formatPrefixes:["","MM"],value:"1055.05585262 J",aliases:["BTUs"]},eV:{prefixGroup:"SHORT",formatPrefixes:["u","m","","k","M","G"],value:"1.602176565e-19 J"},electronvolt:{prefixGroup:"LONG",formatPrefixes:["micro","milli","","kilo","mega","giga"],value:"1 eV",aliases:["electronvolts"]},W:{prefixGroup:"SHORT",formatPrefixes:["p","n","u","m","","k","M","G","T","P"],value:"1 J/s"},watt:{prefixGroup:"LONG",formatPrefixes:["pico","nano","micro","milli","","kilo","mega","tera","peta"],value:"1 W",aliases:["watts"]},hp:{value:"550 ft lbf / s"},VA:{prefixGroup:"SHORT",formatPrefixes:["","k"],value:"1 W"},Pa:{prefixGroup:"SHORT",formatPrefixes:["","k","M","G"],value:"1 N / m^2"},psi:{value:"1 lbf/in^2"},atm:{value:"101325 Pa"},bar:{prefixGroup:"SHORT_LONG",formatPrefixes:["m",""],value:"1e5 Pa"},torr:{prefixGroup:"LONG",formatPrefixes:["milli",""],value:"133.32236842105263 Pa"},Torr:{prefixGroup:"SHORT",formatPrefixes:["m",""],value:"1 torr"},mmHg:{value:"133.322387415 Pa",aliases:["mmhg"]},inH2O:{value:"249.082 Pa",aliases:["inh2o","inAq"]},C:{prefixGroup:"SHORT",formatPrefixes:["p","n","u","m",""],value:"1 A s"},coulomb:{prefixGroup:"LONG",formatPrefixes:["pico","nano","micro","milli",""],value:"1 C",aliases:["coulombs"]},V:{prefixGroup:"SHORT",formatPrefixes:["m","","k","M"],value:"1 W/A"},volt:{prefixGroup:"LONG",formatPrefixes:["milli","","kilo","mega"],value:"1 V",aliases:["volts"]},F:{prefixGroup:"SHORT",formatPrefixes:["p","n","u","m",""],value:"1 C/V"},farad:{prefixGroup:"LONG",formatPrefixes:["pico","nano","micro","milli",""],value:"1 F",aliases:["farads"]},ohm:{prefixGroup:"SHORT_LONG",formatPrefixes:["","k","M"],value:"1 V/A",aliases:["ohms"]},H:{prefixGroup:"SHORT",formatPrefixes:["u","m",""],value:"1 V s / A"},henry:{prefixGroup:"LONG",formatPrefixes:["micro","milli",""],value:"1 H",aliases:["henries"]},S:{prefixGroup:"SHORT",formatPrefixes:["u","m",""],value:"1 / ohm"},siemens:{prefixGroup:"LONG",formatPrefixes:["micro","milli",""],value:"1 S"},Wb:{prefixGroup:"SHORT",formatPrefixes:["n","u","m",""],value:"1 V s"},weber:{prefixGroup:"LONG",formatPrefixes:["nano","micro","milli",""],value:"1 Wb",aliases:["webers"]},T:{prefixGroup:"SHORT",formatPrefixes:["n","u","m",""],value:"1 N s / C m"},tesla:{prefixGroup:"LONG",formatPrefixes:["nano","micro","milli",""],value:"1 T",aliases:["teslas"]},b:{quantity:"BIT",prefixGroup:"BINARY_SHORT",value:1},bits:{prefixGroup:"BINARY_LONG",value:"1 b",aliases:["bit"]},B:{prefixGroup:"BINARY_SHORT",value:"8 b"},bytes:{prefixGroup:"BINARY_LONG",value:"1 B",aliases:["byte"]}};const l=Symbol("_IS_UNITMATH_DEFAULT_FUNCTION");let f={add:(e,t)=>e+t,sub:(e,t)=>e-t,mul:(e,t)=>e*t,div:(e,t)=>e/t,pow:(e,t)=>Math.pow(e,t),abs:e=>Math.abs(e),eq:(e,t)=>e===t||1e-15>Math.abs(e-t)/Math.abs(e+t),lt:(e,t)=>t>e,gt:(e,t)=>e>t,le:(e,t)=>t>=e,ge:(e,t)=>e>=t,round:e=>Math.round(e),trunc:e=>Math.trunc(e),conv:e=>"string"==typeof e?parseFloat(e):e,clone:e=>e};for(const e of Object.keys(f))f[e][l]=!0;const p=e=>""+e;p[l]=!0;const m=function e(f){const p=["all","none"];if((f={...f}).formatPrefixDefault&&!p.includes(f.formatPrefixDefault))throw Error(`Invalid option for formatPrefixDefault: '${f.formatPrefixDefault}'. Valid options are ${p.join(", ")}`);const m=["conv","clone","add","sub","mul","div","pow"];let c=!0,v=!1;for(const e of m)f.type?.[e][l]?c=!1:v=!0;if(v){if(!c)throw Error("You must supply all required custom type functions: "+m.join(", "));if(f.autoPrefix){const e=["lt","gt","le","ge","abs"];let t=!0;for(const i of e)f.type?.[i][l]&&(t=!1);if(!t)throw Error("The following custom type functions are required when prefix is true: "+e.join(", "))}}function y(e,t){let i=new h(e,t);return Object.freeze(i),i}Object.freeze(f);class h{type="Unit";value;unitList;dimension;fixed;constructor(e,t){let r;if(void 0===e&&void 0===t)r=S.parser(""),r.value=null;else if("string"==typeof e&&void 0===t)r=S.parser(e);else if(d(e))r=e;else if("string"==typeof t)r=S.parser(t),r.value=null==e?null:f.type.conv(e);else{if(void 0!==t)throw new TypeError("To construct a unit, you must supply a single string, two strings, a number and a string, or a custom type and a string.");r=S.parser(""),r.value=null==e?null:f.type.conv(e)}this.dimension=O(r.dimension),this.unitList=b(r.unitList),this.value=null==r.value?null:n(this.unitList,i(r.unitList,r.value,f.type),f.type),this.fixed=!1}clone(){let e=g(this);return Object.freeze(e),e}add(e,t){const r=function(e,t){if(null==e.value||null==t.value)throw Error(`Cannot add ${""+e} and ${""+t}: both units must have values`);if(!e.equalsQuantity(t))throw Error(`Cannot add ${""+e} and ${""+t}: dimensions do not match`);const r=g(e);return r.value=n(e.unitList,f.type.add(i(e.unitList,e.value,f.type),i(t.unitList,t.value,f.type)),f.type),r}(this,x(e,t));return Object.freeze(r),r}sub(e,t){const i=w(this,x(e,t));return Object.freeze(i),i}mul(e,t){const r=function(e,t){const r=g(e);for(let i of Object.keys({...e.dimension,...t.dimension}))r.dimension[i]=(e.dimension[i]||0)+(t.dimension[i]||0),1e-15>Math.abs(r.dimension[i])&&delete r.dimension[i];for(let e=0;t.unitList.length>e;e++){const i={...t.unitList[e]};r.unitList.push(i)}if(r.unitList=b(r.unitList),r.dimension=O(r.dimension),null!==e.value||null!==t.value){let s=f.type.conv(1);const u=i(e.unitList,null===e.value?s:e.value,f.type),a=i(t.unitList,null===t.value?s:t.value,f.type);r.value=n(r.unitList,f.type.mul(u,a),f.type)}else r.value=null;return r}(this,x(e,t));return Object.freeze(r),r}div(e,t){const r=function(e,t){const r=g(e);for(let i of Object.keys({...e.dimension,...t.dimension}))r.dimension[i]=(e.dimension[i]||0)-(t.dimension[i]||0),1e-15>Math.abs(r.dimension[i])&&delete r.dimension[i];for(let e=0;t.unitList.length>e;e++){const i={...t.unitList[e]};i.power=-i.power,r.unitList.push(i)}if(r.unitList=b(r.unitList),r.dimension=O(r.dimension),null!==e.value||null!==t.value){let s=f.type.conv(1);const u=i(e.unitList,null===e.value?s:e.value,f.type),a=i(t.unitList,null===t.value?s:t.value,f.type);r.value=n(r.unitList,f.type.div(u,a),f.type)}else r.value=null;return r}(this,x(e,t));return Object.freeze(r),r}pow(e){const t=G(this,e);return Object.freeze(t),t}sqrt(){const e=function(e){return G(e,.5)}(this);return Object.freeze(e),e}abs(){const e=function(e){const t=g(e);null!==t.value&&(t.value=n(t.unitList,f.type.abs(i(t.unitList,t.value,f.type)),f.type));return t}(this);return Object.freeze(e),e}split(e){let t=function(e,t){if(!f.type.conv[l]&&(f.type.round[l]||f.type.trunc[l]))throw Error("When using custom types, split requires a type.round and a type.trunc function");if(null===e.value)throw Error(`Cannot split ${""+e}: unit has no value`);let n=g(e);const r=[];for(let e=0;t.length>e&&(n=P(n,x(t[e])),e!==t.length-1);e++){const i=f.type.round(n.value);let s;s=f.type.eq(i,n.value)?i:f.type.trunc(n.value);const u=new h(s,""+t[e]);r.push(u),n=w(n,u)}let s=f.type.conv(0);for(let e=0;r.length>e;e++)s=f.type.add(s,i(r[e].unitList,r[e].value,f.type));f.type.eq(s,i(e.unitList,e.value,f.type))&&(n.value=f.type.conv(0));return r.push(n),r}(this,e);for(let e=0;t.length>e;e++)Object.freeze(t[e]);return t}to(e){let t;if(null==e)throw Error("to() requires a unit as a parameter");if("string"!=typeof e&&"Unit"!==e.type)throw new TypeError("Parameter must be a Unit or a string.");return t=P(this,e=x(e)),Object.freeze(t),t}toBaseUnits(){let e=function(e){const t=g(e),r=[];for(let e of Object.keys(t.dimension))if(Math.abs(t.dimension[e]||0)>1e-12)for(let i of Object.keys(S.defs.units))if(S.defs.units[i].quantity===e){r.push({unit:S.defs.units[i],prefix:S.defs.units[i].basePrefix||"",power:t.dimension[e]});break}t.unitList=r,null!==e.value&&(t.value=n(t.unitList,i(e.unitList,e.value,f.type),f.type));return t}(this);return Object.freeze(e),e}getComplexity(){return function(e){let t=e.length,i=e.filter((e=>1e-14>e.power)),n=e.filter((e=>e.power>1e-14));t+=2*n.filter((e=>Math.abs(e.power-1)>1e-14)).length;let r=i.length>0&&n.length>0?-1:1;t+=2*i.filter((e=>0>e.power&&Math.abs(e.power*r-1)>1e-14)).length,i.length>0&&n.length>0&&(t+=1);return t}(this.unitList)}setValue(e){let t=N(this,e);return Object.freeze(t),t}getValue(){return this.value}getNormalizedValue(){return null===this.value?null:i(this.unitList,this.value,f.type)}setNormalizedValue(e){let t=N(this,n(this.unitList,e,f.type));return Object.freeze(t),t}getInferredSystem(){let e=null,t={};for(let e of this.unitList)for(let i of Object.keys(S.defs.systems))for(let n of S.defs.systems[i]){`${n.unitList[0].prefix}${n.unitList[0].unit.name}`===`${e.prefix}${e.unit.name}`?t[i]=(t[i]||0)+1:e.unit.name===n.unitList[0].unit.name&&(t[i]=(t[i]||0)+.5)}let i=Object.keys(t);if(i.length>0){let n=i[0],r=t[i[0]];for(let e=1;i.length>e;e++)t[i[e]]>r&&(n=i[e],r=t[i[e]]);e=n}return e}applyBestPrefix(e){return T(this,k(e))}simplify(e){let t=k(e),r=t.system;if("auto"===r){let e=this.getInferredSystem();e&&(r=e)}let u=S.defs.systems[r]||[];const a=[];let o,l=[];for(let e of u)this.equalsQuantity(e)&&l.push(e);l.length>0&&(o=l[0]);for(let e of this.unitList)for(let t of l){if(1===t.unitList.length&&`${t.unitList[0].prefix}${t.unitList[0].unit.name}`===`${e.prefix}${e.unit.name}`){o=t;break}}if(!o)for(let e of this.unitList)if(this.equalsQuantity(e.unit.name)){o=new h(e.unit.name);break}let p=!0;if(o)a.push(...o.unitList);else for(let e of Object.keys(this.dimension))if(Math.abs(this.dimension[e]||0)>1e-12){let t=!1;for(let i of u)if(s(i.unitList)&&1===i.dimension[e]){a.push({unit:i.unitList[0].unit,prefix:i.unitList[0].prefix,power:this.dimension[e]}),t=!0;break}if(!t)for(const i of Object.values(S.defs.units))if(i.quantity===e){a.push({unit:i,prefix:i.basePrefix||"",power:this.dimension[e]}),t=!0;break}t||(p=!1)}let m=g(this);return p&&(m.unitList=a,m.value=null!==this.value?n(m.unitList,i(this.unitList,this.value,f.type),f.type):null),t.autoPrefix?T(m,t):(Object.freeze(m),m)}getUnits(){let e=g(this);return e.value=null,Object.freeze(e),e}isCompound(){return r(this.unitList)}isBase(){return s(this.unitList)}equalsQuantity(e){e=x(e);for(let t of Object.keys({...this.dimension,...e.dimension}))if(Math.abs((this.dimension[t]||0)-(e.dimension[t]||0))>1e-12)return!1;return!0}equals(e){if(!f.type.conv[l]&&f.type.eq[l])throw Error("When using custom types, equals requires a type.eq function");if(e=x(e),null===this.value!=(null===e.value))return!1;let{value1:t,value2:i}=L(this,e,!1);return this.equalsQuantity(e)&&f.type.eq(t,i)}compare(e){if(!f.type.conv[l]&&(f.type.gt[l]||f.type.lt[l]))throw Error("When using custom types, compare requires a type.gt and a type.lt function");e=x(e);let{value1:t,value2:i}=L(this,e,!0);return"number"==typeof t&&isNaN(t)?1:"number"==typeof i&&isNaN(i)||f.type.lt(t,i)?-1:f.type.gt(t,i)?1:0}lessThan(e){if(!f.type.conv[l]&&f.type.lt[l])throw Error("When using custom types, lessThan requires a type.lt function");e=x(e);let{value1:t,value2:i}=L(this,e,!0);return f.type.lt(t,i)}lessThanOrEqual(e){if(!f.type.conv[l]&&f.type.le[l])throw Error("When using custom types, lessThanOrEqual requires a type.le function");e=x(e);let{value1:t,value2:i}=L(this,e,!0);return f.type.le(t,i)}greaterThan(e){if(!f.type.conv[l]&&f.type.gt[l])throw Error("When using custom types, greaterThan requires a type.gt function");e=x(e);let{value1:t,value2:i}=L(this,e,!0);return f.type.gt(t,i)}greaterThanOrEqual(e){if(!f.type.conv[l]&&f.type.ge[l])throw Error("When using custom types, greaterThanOrEqual requires a type.ge function");e=x(e);let{value1:t,value2:i}=L(this,e,!0);return f.type.ge(t,i)}toString(e){let t=this.clone(),i=k(e),n="";"number"==typeof t.value&&i.formatter[l]&&i.precision>0?n+=+t.value.toPrecision(i.precision):null!==t.value&&(n+=i.formatter(t.value));const r=function(e,t){let i="",n="",r=0,s=0;for(let t=0;e.unitList.length>t;t++)e.unitList[t].power>0?(r++,i+=" "+e.unitList[t].prefix+e.unitList[t].unit.name,Math.abs(e.unitList[t].power-1)>1e-15&&(i+="^"+e.unitList[t].power)):0>e.unitList[t].power&&s++;if(s>0)for(let t=0;e.unitList.length>t;t++)0>e.unitList[t].power&&(r>0?(n+=" "+e.unitList[t].prefix+e.unitList[t].unit.name,Math.abs(e.unitList[t].power+1)>1e-15&&(n+="^"+-e.unitList[t].power)):(n+=" "+e.unitList[t].prefix+e.unitList[t].unit.name,n+="^"+e.unitList[t].power));i=i.substr(1),n=n.substr(1),t.parentheses&&(r>1&&s>0&&(i="("+i+")"),s>1&&r>0&&(n="("+n+")"));let u=i;r>0&&s>0&&(u+=" / ");return u+=n,u}(t,i);return r.length>0&&n.length>0&&(n+=" "),n+=r,n}}function d(e){return"Unit"===e?.type&&!e.clone}function x(e,t){if(i=e,"Unit"===i?.type&&i.clone)return e;if(d(e)){return new h(e)}return"string"==typeof e?y(e):y(e,t);var i}function g(e){const t=new h;t.value=null===e.value?null:f.type.clone(e.value),t.dimension={...e.dimension},t.unitList=[];for(let i=0;e.unitList.length>i;i++)t.unitList[i]={},t.unitList[i]={...e.unitList[i]};return t}function b(e){let t=e.map((e=>Object.assign({},e)));if(t.length>=2){let e={};for(let i=0;t.length>i;i++)if(e.hasOwnProperty(t[i].unit.name)){e[t[i].unit.name].power+=t[i].power,t.splice(i,1),i--}else e[t[i].unit.name]=t[i];for(let e=0;t.length>e;e++)1e-15>Math.abs(t[e].power)&&(t.splice(e,1),e--)}return t}function O(e){let t={...e};for(let e of Object.keys(t))1e-15>Math.abs(t[e])&&delete t[e];return t}function L(e,t,n){if(n&&!e.equalsQuantity(t))throw Error(`Cannot compare units ${e} and ${t}; dimensions do not match`);let r,s;if(null===e.value&&null===t.value)r=i(e.unitList,f.type.conv(1),f.type),s=i(t.unitList,f.type.conv(1),f.type);else{if(null===e.value||null===t.value)throw Error(`Cannot compare units ${e} and ${t}; one has a value and the other does not`);r=i(e.unitList,e.value,f.type),s=i(t.unitList,t.value,f.type)}return{value1:r,value2:s}}function w(e,t){if(null==e.value||null==t.value)throw Error(`Cannot subtract ${""+e} and ${""+t}: both units must have values`);if(!e.equalsQuantity(t))throw Error(`Cannot subtract ${""+e} and ${""+t}: dimensions do not match`);const r=g(e);return r.value=n(e.unitList,f.type.sub(i(e.unitList,e.value,f.type),i(t.unitList,t.value,f.type)),f.type),r}function G(e,t){const i=g(e);for(let n of Object.keys(i.dimension))i.dimension[n]=e.dimension[n]*t;for(let e=0;i.unitList.length>e;e++)i.unitList[e].power=i.unitList[e].power*t;return i.value=null!==i.value?f.type.pow(i.value,f.type.conv(t)):null,i}function P(e,t){let r;const s=null===e.value?f.type.conv(1):e.value;if(!e.equalsQuantity(t))throw new TypeError(`Cannot convert ${""+e} to ${t}: dimensions do not match`);if(null!==t.value)throw Error(`Cannot convert ${""+e}: target unit must be valueless`);return r=g(t),r.value=n(r.unitList,i(e.unitList,s,f.type),f.type),r}function T(e,t){let r=g(e),s=r.unitList[0];if(1!==e.unitList.length)return e;if(null===e.value)return e;if(Math.abs(s.power-Math.round(s.power))>=1e-14)return e;if(1e-14>Math.abs(s.power))return e;if(f.type.lt(f.type.abs(e.value),f.type.conv(1e-50)))return e;if(f.type.le(f.type.abs(e.value),t.prefixMax)&&f.type.ge(f.type.abs(e.value),t.prefixMin))return e;let u=f.type.abs(e.value);function a(i){let n=f.type.abs(function(t){return f.type.div(e.value,f.type.pow(f.type.div(f.type.conv(s.unit.prefixGroup[t]),f.type.conv(s.unit.prefixGroup[s.prefix])),f.type.conv(s.power)))}(i));return f.type.lt(n,t.prefixMin)?f.type.div(f.type.conv(t.prefixMin),n):f.type.gt(n,t.prefixMax)?f.type.div(n,f.type.conv(t.prefixMax)):f.type.le(n,u)?f.type.sub(f.type.conv(1),f.type.div(n,u)):f.type.sub(f.type.conv(1),f.type.div(u,n))}let o=s.prefix,l=a(o),p=s.unit.formatPrefixes??("all"===t.formatPrefixDefault?Object.keys(s.unit.prefixGroup):void 0);if(!p)return e;for(let e=0;p.length>e;e++){let t=p[e],i=a(t);f.type.lt(i,l)&&(l=i,o=t)}return s.prefix=o,r.value=n(r.unitList,i(e.unitList,e.value,f.type),f.type),Object.freeze(r),r}function N(e,t){let i=g(e);return i.value=null==t?null:f.type.conv(t),i}function k(e){let t={...f};return"object"==typeof e&&(t=Object.assign(t,e)),t}let S=function(e){const{skipBuiltIns:n}=e.definitions;let r;if(n)r={...e.definitions.systems};else{r={...u};for(let t of Object.keys(e.definitions.systems))r[t]=r.hasOwnProperty(t)?[...e.definitions.systems[t],...r[t]]:[...e.definitions.systems[t]]}const s={systems:r,prefixGroups:{...n?{}:a,...e.definitions.prefixGroups},units:{...n?{}:o,...e.definitions.units}},l={units:{},prefixGroups:{...s.prefixGroups},systems:{}},f=t(e,m);for(;;){let t=0,n=[],r=[];for(const u of Object.keys(s.units)){if(l.units.hasOwnProperty(u))continue;const a=s.units[u];if(!a)continue;if("string"!=typeof a&&a.prefixGroup&&!l.prefixGroups.hasOwnProperty(a.prefixGroup))throw Error(`Unknown prefixes '${a.prefixGroup}' for unit '${u}'`);let o,m,c,v=!1;if("string"!=typeof(p=a)&&void 0!==p.quantity)o=e.type.conv(a.value),m={[a.quantity]:1},c=a.quantity;else{let t;try{if(!a.hasOwnProperty("value"))throw new TypeError(`Unit definition for '${u}' must be an object with a value property where the value is a string or a two-element array.`);if(a&&"string"==typeof a.value)t=f(a.value);else{if(!Array.isArray(a.value)||2!==a.value.length)throw new TypeError(`Unit definition for '${u}' must be an object with a value property where the value is a string or a two-element array.`);t=f(a.value[1]),t.value=e.type.conv(a.value[0])}if(null==t.value)throw Error(`Parsing value for '${u}' resulted in invalid value: ${t.value}`);o=i(t.unitList,t.value,e.type),m=Object.freeze(t.dimension)}catch(e){if(!(e instanceof Error&&/Unit.*not found/.test(""+e)))throw e;n.push(u),r.push(""+e),v=!0}}if(!v){let i=[u];a.aliases&&i.push(...a.aliases),i.forEach((i=>{if(l.units.hasOwnProperty(i))throw Error(`Alias '${i}' would override an existing unit`);if(!/^[a-zA-Z][a-zA-Z0-9]*$/.test(i)&&""!==i)throw new SyntaxError(`Unit name contains non-alphanumeric characters or begins with a number: '${i}'`);const n={name:i,value:o,offset:e.type.conv(a.offset?a.offset:0),dimension:m,prefixGroup:a.prefixGroup&&l.prefixGroups[a.prefixGroup]||{"":1},formatPrefixes:a.formatPrefixes,basePrefix:a.basePrefix};c&&(n.quantity=c),Object.freeze(n),l.units[i]=n,t++}))}}if(0===n.length)break;if(0===t)throw Error(`Could not create the following units: ${n.join(", ")}. Reasons follow: ${r.join(" ")}`)}var p;if("auto"!==e.system&&!s.systems.hasOwnProperty(e.system))throw Error(`Unknown unit system ${e.system}. Available systems are: auto, ${Object.keys(s.systems).join(", ")} `);for(let e of Object.keys(s.systems)){let t=s.systems[e];l.systems[e]=[];for(let i=0;t.length>i;i++){let n=f(t[i]);n.type="Unit",Object.freeze(n),l.systems[e][i]=n}}for(let e of Object.keys(l.units)){const t=l.units[e];if(t.formatPrefixes)for(let e=0;t.formatPrefixes.length>e;e++){let i=t.formatPrefixes[e];if(!t.prefixGroup.hasOwnProperty(i))throw Error(`In unit ${t.name}, common prefix ${i} was not found among the allowable prefixes`)}}function m(e){if("string"!=typeof e)throw new TypeError(`parameter must be a string (${e} given)`);if(l.units.hasOwnProperty(e))return{unit:l.units[e],prefix:""};for(const t of Object.keys(l.units))if(e.substring(e.length-t.length,e.length)===t){const i=l.units[t],n=e.substring(0,e.length-t.length);if(i.prefixGroup.hasOwnProperty(n))return{unit:i,prefix:n}}return null}return Object.freeze(l.prefixGroups),Object.freeze(l.systems),Object.freeze(l.units),{originalDefinitions:s,defs:l,exists:function(e){return null!==m(e)},findUnit:m,parser:f}}(f);return y.config=function(t){let i=Object.assign({},f,t);return i.definitions=Object.assign({},f.definitions,t.definitions),i.type=Object.assign({},f.type,t.type),e(i)},y.getConfig=function(){return f},y.definitions=function(){return S.originalDefinitions},y.add=function(e,t){return x(e).add(t)},y.sub=function(e,t){return x(e).sub(t)},y.mul=function(e,t){return x(e).mul(t)},y.div=function(e,t){return x(e).div(t)},y.pow=function(e,t){return x(e).pow(t)},y.sqrt=function(e){return x(e).sqrt()},y.abs=function(e){return x(e).abs()},y.to=function(e,t){return x(e).to(t)},y.toBaseUnits=function(e){return x(e).toBaseUnits()},y.exists=S.exists,y._unitStore=S,Object.freeze(y),y}({parentheses:!1,precision:15,autoPrefix:!0,prefixMin:.1,prefixMax:1e3,formatPrefixDefault:"none",system:"auto",formatter:p,definitions:{skipBuiltIns:!1,units:{},prefixGroups:{},systems:{}},type:f});export{m as default}; 2 | -------------------------------------------------------------------------------- /docs/js/demo.js: -------------------------------------------------------------------------------- 1 | import _unit from './UnitMath.js' 2 | 3 | const unit = _unit.config({ precision: 8 }) 4 | 5 | const model = { 6 | operand1: null, 7 | operand2: document.getElementById('operand2').value, 8 | operator: null, 9 | lastExpression: null, 10 | hasError: false, 11 | } 12 | 13 | const infixOperatorMap = { 14 | add: '+', 15 | sub: '-', 16 | mul: '*', 17 | div: '/', 18 | pow: '^', 19 | to: 'to', 20 | } 21 | 22 | // Add event listeners if the infix operators are clicked 23 | const infixOperators = ['add', 'sub', 'mul', 'div', 'pow', 'to'] 24 | infixOperators.forEach((op) => { 25 | document.getElementById(op).addEventListener('click', () => { 26 | if (model.operator === null && model.operand2 !== '') { 27 | setOperator(op) 28 | } 29 | document.getElementById('operand2').focus() 30 | }) 31 | }) 32 | 33 | // Update model when the input changes 34 | document.getElementById('operand2').addEventListener('input', (event) => { 35 | 36 | // Don't update the model if there's an error 37 | if (model.hasError) return 38 | 39 | model.operand2 = event.target.value 40 | 41 | model.lastExpression = null 42 | 43 | // If the user typed an infix operator, remove that operator from the input and call setOperator 44 | if (model.operand2.endsWith('+ ')) { 45 | model.operand2 = model.operand2.slice(0, -2) 46 | setOperator('add') 47 | } 48 | if (model.operand2.endsWith('- ')) { 49 | model.operand2 = model.operand2.slice(0, -2) 50 | setOperator('sub') 51 | } 52 | if (model.operand2.endsWith('* ')) { 53 | model.operand2 = model.operand2.slice(0, -2) 54 | setOperator('mul') 55 | } 56 | if (model.operand2.endsWith('/ ')) { 57 | model.operand2 = model.operand2.slice(0, -2) 58 | setOperator('div') 59 | } 60 | if (model.operand2.endsWith('^ ')) { 61 | model.operand2 = model.operand2.slice(0, -2) 62 | setOperator('pow') 63 | } 64 | if (model.operand2.endsWith(' to ')) { 65 | model.operand2 = model.operand2.slice(0, -4) 66 | setOperator('to') 67 | } 68 | render() 69 | }) 70 | 71 | // Clear the model when the clear button is clicked 72 | document.getElementById('clear').addEventListener('click', () => { 73 | clearModel() 74 | }) 75 | 76 | // Perform the calculation when the equals button is clicked 77 | document.getElementById('equals').addEventListener('click', () => { 78 | performCalculation() 79 | }) 80 | 81 | document.getElementById('operand2').addEventListener('keydown', (event) => { 82 | if (event.key === 'Enter') { 83 | // Perform the calculation when the enter key is pressed 84 | performCalculation() 85 | } 86 | else if (event.key === 'Escape') { 87 | // Clear the model when the escape key is pressed 88 | clearModel() 89 | } 90 | }) 91 | 92 | function clearModel() { 93 | model.operand1 = null 94 | model.operand2 = '' 95 | model.operator = null 96 | model.lastExpression = null 97 | model.hasError = false 98 | render() 99 | document.getElementById('operand2').focus() 100 | } 101 | 102 | function performCalculation() { 103 | if (model.hasError) return 104 | 105 | if (model.operator !== null) { 106 | model.lastExpression = model.operand1 + ' ' + infixOperatorMap[model.operator] + ' ' + model.operand2 107 | try { 108 | model.operand2 = unit(model.operand1)[model.operator](model.operand2).simplify().toString() 109 | } catch (ex) { 110 | model.operand2 = ex.message 111 | model.hasError = true 112 | } 113 | model.operand1 = null 114 | model.operator = null 115 | render() 116 | document.getElementById('operand2').focus() 117 | } 118 | } 119 | 120 | function setOperator(op) { 121 | if (model.operand1 === null) { 122 | model.operator = op 123 | model.operand1 = model.operand2 124 | model.operand2 = '' 125 | render() 126 | } 127 | } 128 | 129 | function render() { 130 | if (model.operand1) { 131 | document.getElementById('operand1').innerText = unit(model.operand1).toString() 132 | document.getElementById('operand1').style.display = 'inline' 133 | } else { 134 | document.getElementById('operand1').style.display = 'none' 135 | } 136 | 137 | if (model.operator) { 138 | document.getElementById('operator').innerText = infixOperatorMap[model.operator] ?? '' 139 | document.getElementById('operator').style.display = 'inline' 140 | } else { 141 | document.getElementById('operator').style.display = 'none' 142 | } 143 | 144 | if (model.lastExpression) { 145 | document.getElementById('lastExpression').innerText = model.lastExpression 146 | document.getElementById('lastExpression').style.display = 'inline' 147 | } else { 148 | document.getElementById('lastExpression').style.display = 'none' 149 | } 150 | 151 | document.getElementById('operand2').value = model.operand2 152 | console.log(model) 153 | } 154 | 155 | render() 156 | 157 | document.getElementById('operand2').focus() 158 | -------------------------------------------------------------------------------- /examples/decimal.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Example program 3 | * Extends UnitMath with the decimal.js type 4 | */ 5 | const Decimal = require('decimal.js').set({ precision: 64 }) 6 | 7 | const unit = require('../index.js').config({ 8 | type: { 9 | clone: Decimal, 10 | conv: Decimal, 11 | add: (a, b) => a.add(b), 12 | sub: (a, b) => a.sub(b), 13 | mul: (a, b) => a.mul(b), 14 | div: (a, b) => a.div(b), 15 | pow: (a, b) => a.pow(b), 16 | lt: (a, b) => a.lt(b), 17 | le: (a, b) => a.lte(b), 18 | gt: (a, b) => a.gt(b), 19 | ge: (a, b) => a.gte(b), 20 | abs: (a) => a.abs(), 21 | eq: (a, b) => a.eq(b) 22 | } 23 | }) 24 | 25 | console.log(Decimal(1).dividedBy(3).toString()) 26 | console.log(unit('1 m').div('3 s').toString()) 27 | console.log(unit('10000 kg m').div('3 s^2').toString()) 28 | console.log(Decimal(1).dividedBy(3) instanceof Decimal) 29 | 30 | console.log(unit('-1m^2').equals('1m^2')) 31 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | import UnitMath from './src/Unit.js' 2 | export default UnitMath 3 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node' 4 | } 5 | -------------------------------------------------------------------------------- /migrating-to-v1.md: -------------------------------------------------------------------------------- 1 | # Migrating to v1 2 | 3 | Most of the changes in v1 were the result of efforts to make the API simpler and more predictable. 4 | 5 | ## Simplifying vs. Outputting 6 | 7 | `toString` no longer simplifies units. You must now explicitly call `simplify` for the unit to be simplified. 8 | 9 | In v0.8.7, units were automatically simplified when formatting as a string. When and how to simplify a unit is very subjective, and UnitMath cannot anticipate all needs. Further, certain use cases such as converting to a specific unit are broken if the unit is automatically simplified. This necessitates additional state in the unit object to prevent it from being simplified. All these issues can be avoided if the user is responsible for calling `simplify` when they want the unit to be simplified. 10 | 11 | v0.8.7: 12 | 13 | ```js 14 | unit('4 ft').mul('3 in').toString() // '12 ft^2' 15 | ``` 16 | 17 | v1: 18 | 19 | ```js 20 | unit('4 ft').mul('3 in').toString() // '12 ft in' 21 | unit('4 ft').mul('3 in').simplify().toString() // '1 ft^2' 22 | ``` 23 | 24 | Because of this, the `simplify` and `simplifyThreshold` options were removed. 25 | 26 | ## Unit Systems 27 | 28 | Each system defined in `definitions.systems` is now just a string array of units assigned to that system. To further enforce this difference, `unitSystems` was renamed to `systems`: 29 | 30 | v0.8.7: 31 | 32 | ```js 33 | definitions: { 34 | unitSystems: { 35 | si: { 36 | AMOUNT_OF_SUBSTANCE: 'mol', 37 | CAPACITANCE: 'F', 38 | CURRENT: 'A', 39 | MASS: 'kg', 40 | ... 41 | } 42 | } 43 | } 44 | ``` 45 | 46 | v1: 47 | 48 | ```js 49 | definitions: { 50 | systems: { 51 | si: ['mol', 'F', 'A', 'kg', ...] 52 | } 53 | } 54 | ``` 55 | 56 | With this simpler method of defining systems, the options `definitons.quantities`, `definitions.baseQuantities`, and `autoAddToSystem` were no longer needed, and were removed. 57 | 58 | ## Other Changes 59 | 60 | Customer formatters no longer accept additional user arguments. (see https://github.com/ericman314/UnitMath/issues/47) 61 | 62 | Renamed `definitions.prefixes` to `definitions.prefixGroups`. 63 | 64 | 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unitmath", 3 | "version": "1.1.1", 4 | "description": "JavaScript library for unit conversion and arithmetic", 5 | "main": "index.js", 6 | "module": "./es/UnitMath.js", 7 | "exports": { 8 | ".": { 9 | "import": "./es/UnitMath.js", 10 | "require": "./dist/UnitMath.js" 11 | } 12 | }, 13 | "files": [ 14 | "dist", 15 | "lib", 16 | "es", 17 | "src" 18 | ], 19 | "types": "./es/UnitMath.d.ts", 20 | "scripts": { 21 | "build": "rollup -c && cp es/* docs/js/.", 22 | "clean": "rm -rf dist es", 23 | "toc": "markdown-toc -i README.md", 24 | "prepublishOnly": "npm test", 25 | "test": "npm run lint && npm run test:src", 26 | "test:src": "jest --all", 27 | "test:bundle": "npm run build && jest --testMatch '**/test/**/*.test-bundle.js'", 28 | "test:compiled": "npm run build && jest --testMatch '**/test/**/*.test-compiled.js'", 29 | "lint": "standard --env=jest --env=worker --verbose", 30 | "lint:fix": "standard --env=jest --env=worker --fix --verbose", 31 | "coverage": "jest --coverage", 32 | "typecheck": "tsc --noEmit" 33 | }, 34 | "repository": { 35 | "type": "git", 36 | "url": "git+https://github.com/ericman314/unitmath.git" 37 | }, 38 | "keywords": [ 39 | "unit", 40 | "conversion", 41 | "arithmetic", 42 | "math", 43 | "engineering", 44 | "science" 45 | ], 46 | "author": "Eric Mansfield", 47 | "license": "Apache-2.0", 48 | "bugs": { 49 | "url": "https://github.com/ericman314/unitmath/issues" 50 | }, 51 | "homepage": "https://github.com/ericman314/unitmath#readme", 52 | "devDependencies": { 53 | "@babel/cli": "^7.20.7", 54 | "@babel/core": "^7.20.12", 55 | "@babel/preset-env": "^7.20.2", 56 | "@rollup/plugin-babel": "^6.0.3", 57 | "@rollup/plugin-commonjs": "24.0.1", 58 | "@rollup/plugin-node-resolve": "15.0.1", 59 | "@rollup/plugin-terser": "^0.4.0", 60 | "@rollup/plugin-typescript": "^11.0.0", 61 | "@types/jest": "^29.4.0", 62 | "babel-jest": "^29.4.3", 63 | "core-js": "3.28.0", 64 | "decimal.js": "^10.4.3", 65 | "esm": "^3.2.25", 66 | "jest": "^29.4.3", 67 | "markdown-toc": "^1.2.0", 68 | "rollup": "^3.17.0", 69 | "rollup-plugin-dts": "^5.2.0", 70 | "standard": "^17.0.0", 71 | "ts-jest": "^29.0.5", 72 | "typescript": "^4.9.5" 73 | }, 74 | "standard": { 75 | "ignore": [ 76 | "*.test-compiled.js", 77 | "*.test-bundle.js", 78 | "types/*", 79 | "docs/*" 80 | ] 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | import babel from '@rollup/plugin-babel' 2 | import typescript from '@rollup/plugin-typescript' 3 | import commonjs from '@rollup/plugin-commonjs' 4 | import terser from '@rollup/plugin-terser' 5 | import dts from 'rollup-plugin-dts' 6 | import { defineConfig } from 'rollup' 7 | 8 | const terserOptions = { 9 | compress: { 10 | pure_getters: true, 11 | unsafe: true, 12 | unsafe_comps: true 13 | } 14 | } 15 | 16 | const babelOptions = { 17 | presets: [ 18 | ['@babel/preset-env', { 19 | useBuiltIns: 'usage', 20 | debug: true, 21 | corejs: 3 22 | }] 23 | ], 24 | ignore: [ 25 | 'node_modules' 26 | ] 27 | } 28 | 29 | const name = 'UnitMath' 30 | const input = 'src/Unit.ts' 31 | 32 | const config = defineConfig([ 33 | // UMD build 34 | { 35 | input, 36 | output: { 37 | name, 38 | file: 'dist/UnitMath.js', 39 | format: 'umd' 40 | }, 41 | plugins: [ 42 | typescript(), 43 | babel(babelOptions), 44 | commonjs() 45 | ] 46 | }, 47 | // minified UMD build 48 | { 49 | input, 50 | output: { 51 | file: 'dist/UnitMath.min.js', 52 | format: 'umd', 53 | indent: false, 54 | name 55 | }, 56 | plugins: [ 57 | typescript(), 58 | babel(babelOptions), 59 | commonjs(), 60 | terser(terserOptions) 61 | ] 62 | }, 63 | // minified UMD build 64 | { 65 | input, 66 | output: { 67 | file: 'dist/UnitMath.min2.js', 68 | format: 'umd', 69 | indent: false, 70 | name 71 | }, 72 | plugins: [ 73 | typescript(), 74 | terser(terserOptions) 75 | ] 76 | }, 77 | // es build 78 | { 79 | input, 80 | output: { 81 | file: 'es/UnitMath.js', 82 | format: 'es' 83 | }, 84 | plugins: [ 85 | typescript() 86 | ] 87 | }, 88 | // minified es build 89 | { 90 | input, 91 | output: { 92 | file: 'es/UnitMath.min.js', 93 | format: 'es', 94 | indent: false 95 | }, 96 | plugins: [ 97 | typescript(), 98 | terser(terserOptions) 99 | ] 100 | }, 101 | // d.ts build (outputs individual files) 102 | { 103 | input, 104 | output: { 105 | dir: 'types', 106 | format: 'es' 107 | }, 108 | plugins: [ 109 | typescript({ 110 | compilerOptions: { 111 | declaration: true, 112 | declarationDir: 'types', 113 | emitDeclarationOnly: true 114 | } 115 | }) 116 | ] 117 | }, 118 | // d.ts bundle (outputs a single file) 119 | { 120 | input: 'types/Unit.d.ts', 121 | output: [{ file: 'es/UnitMath.d.ts', format: 'es' }], 122 | plugins: [dts()] 123 | } 124 | ]) 125 | 126 | export default config 127 | -------------------------------------------------------------------------------- /src/BuiltIns.ts: -------------------------------------------------------------------------------- 1 | // A base quantity is a physical quantity in a subset of a given system of quantities that is chosen by convention, where no quantity in the set can be expressed in terms of the others. 2 | // export const baseQuantities = ['MASS', 'LENGTH', 'TIME', 'CURRENT', 'TEMPERATURE', 'LUMINOUS_INTENSITY', 'AMOUNT_OF_SUBSTANCE', 'ANGLE', 'BIT', 'SOLID_ANGLE'] 3 | 4 | import { UnitProps } from "./types" 5 | 6 | // A derived quantity is a quantity in a system of quantities that is a defined in terms of the base quantities of that system. 7 | // export const quantities = { 8 | // UNITLESS: '', 9 | // ABSEMENT: 'LENGTH TIME', 10 | // ACCELERATION: 'LENGTH TIME^-2', 11 | // ANGULAR_ACCELERATION: 'TIME^-2 ANGLE', 12 | // ANGULAR_MOMENTUM: 'MASS LENGTH^2 TIME^-1 ANGLE', 13 | // ANGULAR_VELOCITY: 'TIME^-1 ANGLE', 14 | // AREA: 'LENGTH^2', 15 | // AREA_DENSITY: 'MASS LENGTH^-2', 16 | // BIT_RATE: 'TIME^-1 BIT', 17 | // CAPACITANCE: 'MASS^-1 LENGTH^-2 TIME^4 CURRENT^2', 18 | // CURRENT_DENSITY: 'LENGTH^-2 CURRENT', 19 | // DYNAMIC_VISCOSITY: 'MASS LENGTH^-1 TIME^-1', 20 | // ELECTRIC_CHARGE: 'TIME CURRENT', 21 | // ELECTRIC_CHARGE_DENSITY: 'LENGTH^-3 TIME CURRENT', 22 | // ELECTRIC_DISPLACEMENT: 'LENGTH^-2 TIME CURRENT', 23 | // ELECTRIC_FIELD_STRENGTH: 'MASS LENGTH TIME^-3 CURRENT^-1', 24 | // ELECTRICAL_CONDUCTANCE: 'MASS^-1 LENGTH^-2 TIME^3 CURRENT^2', 25 | // ELECTRICAL_CONDUCTIVITY: 'MASS^-1 LENGTH^-3 TIME^3 CURRENT^2', 26 | // ELECTRIC_POTENTIAL: 'MASS LENGTH^2 TIME^-3 CURRENT^-1', 27 | // RESISTANCE: 'MASS LENGTH^2 TIME^-3 CURRENT^-2', 28 | // ELECTRICAL_RESISTIVITY: 'MASS LENGTH^3 TIME^-3 CURRENT^-2', 29 | // ENERGY: 'MASS LENGTH^2 TIME^-2', 30 | // ENTROPY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1', 31 | // FORCE: 'MASS LENGTH TIME^-2', 32 | // FREQUENCY: 'TIME^-1', 33 | // HEAT_CAPACITY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1', 34 | // HEAT_FLUX_DENSITY: 'MASS TIME^-3', 35 | // ILLUMINANCE: 'LENGTH^-2 LUMINOUS_INTENSITY', 36 | // IMPEDANCE: 'MASS LENGTH^2 TIME^-3 CURRENT^-2', 37 | // IMPULSE: 'MASS LENGTH TIME^-1', 38 | // INDUCTANCE: 'MASS LENGTH^2 TIME^-2 CURRENT^-2', 39 | // IRRADIANCE: 'MASS TIME^-3', 40 | // JERK: 'LENGTH TIME^-3', 41 | // KINEMATIC_VISCOSITY: 'LENGTH^2 TIME^-1', 42 | // LINEAR_DENSITY: 'MASS LENGTH^-1', 43 | // LUMINOUS_FLUX: 'LUMINOUS_INTENSITY SOLID_ANGLE', 44 | // MAGNETIC_FIELD_STRENGTH: 'LENGTH^-1 CURRENT', 45 | // MAGNETIC_FLUX: 'MASS LENGTH^2 TIME^-2 CURRENT^-1', 46 | // MAGNETIC_FLUX_DENSITY: 'MASS TIME^-2 CURRENT^-1', 47 | // MOLAR_CONCENTRATION: 'LENGTH^-3 AMOUNT_OF_SUBSTANCE', 48 | // MOLAR_ENERGY: 'MASS LENGTH^2 TIME^-2 AMOUNT_OF_SUBSTANCE^-1', 49 | // MOLAR_ENTROPY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1 AMOUNT_OF_SUBSTANCE^-1', 50 | // MOLAR_HEAT_CAPACITY: 'MASS LENGTH^2 TIME^-2 TEMPERATURE^-1 AMOUNT_OF_SUBSTANCE^-1', 51 | // MOMENT_OF_INERTIA: 'MASS LENGTH^2', 52 | // MOMENTUM: 'MASS LENGTH TIME^-1', 53 | // PERMEABILITY: 'MASS LENGTH TIME^-2 CURRENT^-2', 54 | // PERMITTIVITY: 'MASS^-1 LENGTH^-3 TIME^4 CURRENT^2 ', 55 | // POWER: 'MASS LENGTH^2 TIME^-3', 56 | // PRESSURE: 'MASS LENGTH^-1 TIME^-2', 57 | // RELUCTANCE: 'MASS^-1 LENGTH^-2 TIME^2 CURRENT^2', 58 | // SPECIFIC_ENERGY: 'LENGTH^2 TIME^-2', 59 | // SPECIFIC_HEAT_CAPACITY: 'LENGTH^2 TIME^-2 TEMPERATURE^-1', 60 | // SPECIFIC_VOLUME: 'MASS^-1 LENGTH^3', 61 | // SPIN: 'MASS LENGTH^2 TIME^-1', 62 | // SURFACE_TENSION: 'MASS TIME^-2', 63 | // TEMPERATURE_GRADIENT: 'LENGTH^-1 TEMPERATURE', 64 | // THERMAL_CONDUCTIVITY: 'MASS LENGTH TIME^-3 TEMPERATURE^-1', 65 | // TORQUE: 'MASS LENGTH^2 TIME^-2', // TODO: Should this have a radian in it somewhere? 66 | // VELOCITY: 'LENGTH TIME^-1', 67 | // VOLUME: 'LENGTH^3', 68 | // VOLUMETRIC_FLOW_RATE: 'LENGTH^3 TIME^-1' 69 | // } 70 | 71 | // // A unit system is a set of units that are by convention used with the unit system. 72 | // // Units listed here will be treated as belonging to the specified system. 73 | // // TODO: We need a better way to add all of the units which possibly might show up and need to be parsed. 74 | export const systems = { 75 | si: ['m', 'meter', 's', 'A', 'kg', 'K', 'mol', 'rad', 'b', 'F', 'C', 'S', 'V', 'J', 'N', 'Hz', 'ohm', 'H', 'cd', 'lm', 'lx', 'Wb', 'T', 'W', 'Pa', 'ohm', 'sr'], 76 | cgs: ['cm', 's', 'A', 'g', 'K', 'mol', 'rad', 'b', 'F', 'C', 'S', 'V', 'erg', 'dyn', 'Hz', 'ohm', 'H', 'cd', 'lm', 'lx', 'Wb', 'T', 'Pa', 'ohm', 'sr'], 77 | us: ['ft', 'mi', 'mile', 'in', 'inch', 's', 'A', 'lbm', 'degF', 'mol', 'rad', 'b', 'F', 'C', 'S', 'V', 'BTU', 'lbf', 'Hz', 'ohm', 'H', 'cd', 'lm', 'lx', 'Wb', 'T', 'psi', 'ohm', 'sr', 'hp'] 78 | } as const 79 | 80 | // Units may or may not use one of the prefix groups (SHORT, LONG, etc). 81 | export const prefixes = { 82 | NONE: { 83 | '': 1 84 | }, 85 | SHORT: { 86 | '': 1, 87 | 88 | 'da': 1e1, 89 | 'h': 1e2, 90 | 'k': 1e3, 91 | 'M': 1e6, 92 | 'G': 1e9, 93 | 'T': 1e12, 94 | 'P': 1e15, 95 | 'E': 1e18, 96 | 'Z': 1e21, 97 | 'Y': 1e24, 98 | 99 | 'd': 1e-1, 100 | 'c': 1e-2, 101 | 'm': 1e-3, 102 | 'u': 1e-6, 103 | 'n': 1e-9, 104 | 'p': 1e-12, 105 | 'f': 1e-15, 106 | 'a': 1e-18, 107 | 'z': 1e-21, 108 | 'y': 1e-24 109 | }, 110 | LONG: { 111 | '': 1, 112 | 113 | 'deca': 1e1, 114 | 'hecto': 1e2, 115 | 'kilo': 1e3, 116 | 'mega': 1e6, 117 | 'giga': 1e9, 118 | 'tera': 1e12, 119 | 'peta': 1e15, 120 | 'exa': 1e18, 121 | 'zetta': 1e21, 122 | 'yotta': 1e24, 123 | 124 | 'deci': 1e-1, 125 | 'centi': 1e-2, 126 | 'milli': 1e-3, 127 | 'micro': 1e-6, 128 | 'nano': 1e-9, 129 | 'pico': 1e-12, 130 | 'femto': 1e-15, 131 | 'atto': 1e-18, 132 | 'zepto': 1e-21, 133 | 'yocto': 1e-24 134 | }, 135 | BINARY_SHORT_SI: { 136 | '': 1, 137 | 'k': 1e3, 138 | 'M': 1e6, 139 | 'G': 1e9, 140 | 'T': 1e12, 141 | 'P': 1e15, 142 | 'E': 1e18, 143 | 'Z': 1e21, 144 | 'Y': 1e24 145 | }, 146 | BINARY_SHORT_IEC: { 147 | '': 1, 148 | 'Ki': 1024, 149 | 'Mi': Math.pow(1024, 2), 150 | 'Gi': Math.pow(1024, 3), 151 | 'Ti': Math.pow(1024, 4), 152 | 'Pi': Math.pow(1024, 5), 153 | 'Ei': Math.pow(1024, 6), 154 | 'Zi': Math.pow(1024, 7), 155 | 'Yi': Math.pow(1024, 8) 156 | }, 157 | BINARY_LONG_SI: { 158 | '': 1, 159 | 'kilo': 1e3, 160 | 'mega': 1e6, 161 | 'giga': 1e9, 162 | 'tera': 1e12, 163 | 'peta': 1e15, 164 | 'exa': 1e18, 165 | 'zetta': 1e21, 166 | 'yotta': 1e24 167 | }, 168 | BINARY_LONG_IEC: { 169 | '': 1, 170 | 'kibi': 1024, 171 | 'mebi': Math.pow(1024, 2), 172 | 'gibi': Math.pow(1024, 3), 173 | 'tebi': Math.pow(1024, 4), 174 | 'pebi': Math.pow(1024, 5), 175 | 'exi': Math.pow(1024, 6), 176 | 'zebi': Math.pow(1024, 7), 177 | 'yobi': Math.pow(1024, 8) 178 | }, 179 | BTU: { 180 | '': 1, 181 | 'MM': 1e6 182 | }, 183 | SHORT_LONG: {} as { [s: string]: number }, 184 | BINARY_SHORT: {} as { [s: string]: number }, 185 | BINARY_LONG: {} as { [s: string]: number } 186 | } 187 | 188 | // Additional prefix groups 189 | prefixes.SHORT_LONG = Object.assign({}, prefixes.SHORT, prefixes.LONG) 190 | prefixes.BINARY_SHORT = Object.assign({}, prefixes.BINARY_SHORT_SI, prefixes.BINARY_SHORT_IEC) 191 | prefixes.BINARY_LONG = Object.assign({}, prefixes.BINARY_LONG_SI, prefixes.BINARY_LONG_IEC) 192 | 193 | // Units are a set measure of a particular quantity. Below, each key of UNITS is a different unit. Each unit may be 194 | // defined using a base quantity, such as LENGTH, or it may be defined in terms of other units. The unit may also 195 | // include `prefixes`, which specify which prefix group will be used for parsing the unit, and `commonPrefixes`, which 196 | // specifies which prefixes will be used when formatting that unit. 197 | export const units: Record = { 198 | '': { 199 | quantity: 'UNITLESS', 200 | value: 1 201 | }, 202 | 203 | // length 204 | m: { 205 | quantity: 'LENGTH', 206 | prefixGroup: 'SHORT', 207 | formatPrefixes: ['n', 'u', 'm', 'c', '', 'k'], 208 | value: 1 209 | }, 210 | meter: { 211 | prefixGroup: 'LONG', 212 | formatPrefixes: ['nano', 'micro', 'milli', 'centi', '', 'kilo'], 213 | value: '1 m', 214 | aliases: ['meters'] 215 | }, 216 | inch: { 217 | value: '0.0254 meter', 218 | aliases: ['inches', 'in'] 219 | }, 220 | foot: { 221 | value: '12 inch', 222 | aliases: ['ft', 'feet'] 223 | }, 224 | yard: { 225 | value: '3 foot', 226 | aliases: ['yd', 'yards'] 227 | }, 228 | mile: { 229 | value: '5280 ft', 230 | aliases: ['mi', 'miles'] 231 | }, 232 | link: { 233 | value: '7.92 in', 234 | aliases: ['li', 'links'] 235 | }, 236 | rod: { 237 | value: '25 link', 238 | aliases: ['rd', 'rods'] 239 | }, 240 | chain: { 241 | value: '100 link', 242 | aliases: ['ch', 'chains'] 243 | }, 244 | 245 | angstrom: { 246 | value: '1e-10 m', 247 | aliases: ['angstroms'] 248 | }, 249 | 250 | mil: { 251 | value: '1e-3 inch' 252 | }, 253 | 254 | // Area 255 | sqin: { value: '1 in^2' }, 256 | sqft: { value: '1 ft^2' }, 257 | sqyd: { value: '1 yd^2' }, 258 | sqmi: { value: '1 mi^2' }, 259 | sqrd: { value: '1 rod^2' }, 260 | sqch: { value: '1 chain^2' }, 261 | sqmil: { value: '1 mil^2' }, 262 | acre: { value: '10 chain^2' }, 263 | hectare: { value: '1e4 m^2' }, 264 | 265 | // Volume 266 | L: { 267 | prefixGroup: 'SHORT', 268 | formatPrefixes: ['n', 'u', 'm', ''], 269 | value: '1e-3 m^3', 270 | aliases: ['l', 'lt'] 271 | }, // litre 272 | litre: { 273 | prefixGroup: 'LONG', 274 | formatPrefixes: ['nano', 'micro', 'milli', ''], 275 | value: '1 L', 276 | aliases: ['liter', 'liters', 'litres'] 277 | }, 278 | cuin: { value: '1 in^3' }, 279 | cuft: { value: '1 ft^3' }, 280 | cuyd: { value: '1 yd^3' }, 281 | teaspoon: { 282 | value: '4.92892159375 mL', 283 | aliases: ['teaspoons', 'tsp'] 284 | }, 285 | tablespoon: { 286 | value: '3 teaspoon', 287 | aliases: ['tablespoons', 'tbsp'] 288 | }, 289 | drop: { value: '0.05 mL' }, 290 | gtt: { value: '0.05 mL' }, 291 | 292 | // Liquid volume 293 | minim: { 294 | value: '0.0125 teaspoon', 295 | aliases: ['minims'] 296 | }, 297 | fluidounce: { 298 | value: '0.125 cups', 299 | aliases: ['floz', 'fluidounces'] 300 | }, 301 | fluiddram: { 302 | value: '0.125 floz', 303 | aliases: ['fldr', 'fluiddrams'] 304 | }, 305 | cc: { value: '1 cm^3' }, 306 | cup: { 307 | value: '236.5882365 mL', 308 | aliases: ['cp', 'cups'] 309 | }, 310 | pint: { 311 | value: '2 cup', 312 | aliases: ['pt', 'pints'] 313 | }, 314 | quart: { 315 | value: '4 cup', 316 | aliases: ['qt', 'quarts'] 317 | }, 318 | gallon: { 319 | value: '16 cup', 320 | aliases: ['gal', 'gallons'] 321 | }, 322 | oilbarrel: { 323 | value: '42 gal', 324 | aliases: ['obl', 'oilbarrels'] 325 | }, 326 | 327 | // Mass 328 | g: { 329 | quantity: 'MASS', 330 | prefixGroup: 'SHORT', 331 | formatPrefixes: ['n', 'u', 'm', '', 'k'], 332 | value: 0.001, 333 | basePrefix: 'k' // Treat as if 'kg' is the base unit, not 'g' 334 | }, 335 | gram: { 336 | prefixGroup: 'LONG', 337 | formatPrefixes: ['nano', 'micro', 'milli', '', 'kilo'], 338 | value: '1 g' 339 | }, 340 | 341 | poundmass: { 342 | value: '0.45359237 kg', 343 | aliases: ['lb', 'lbs', 'lbm', 'poundmasses'] 344 | }, 345 | ton: { value: '2000 lbm' }, 346 | tonne: { 347 | prefixGroup: 'LONG', 348 | formatPrefixes: ['', 'kilo', 'mega', 'giga'], 349 | value: '1000 kg' 350 | }, 351 | t: { 352 | prefixGroup: 'SHORT', 353 | // kt could be confused with knot (speed), 354 | value: '1 tonne' 355 | }, 356 | grain: { 357 | value: '64.79891 mg', 358 | aliases: ['gr'] 359 | }, 360 | ounce: { 361 | value: '0.0625 lbm', 362 | aliases: ['oz', 'ounces'] 363 | }, 364 | dram: { 365 | value: '0.0625 oz', 366 | aliases: ['dr'] 367 | }, 368 | hundredweight: { 369 | value: '100 lbm', 370 | aliases: ['cwt', 'hundredweights'] 371 | }, 372 | stick: { 373 | value: '4 oz', 374 | aliases: ['sticks'] 375 | }, 376 | stone: { value: '14 lbm' }, 377 | 378 | // Time 379 | s: { 380 | quantity: 'TIME', 381 | prefixGroup: 'SHORT', 382 | formatPrefixes: ['f', 'p', 'n', 'u', 'm', ''], 383 | value: 1, 384 | aliases: ['sec'] 385 | }, 386 | min: { 387 | value: '60 s', 388 | aliases: ['minute', 'minutes'] 389 | }, 390 | h: { 391 | value: '60 min', 392 | aliases: ['hr', 'hrs', 'hour', 'hours'] 393 | }, 394 | second: { 395 | prefixGroup: 'LONG', 396 | formatPrefixes: ['femto', 'pico', 'nano', 'micro', 'milli', ''], 397 | value: '1 s', 398 | aliases: ['seconds'] 399 | }, 400 | day: { 401 | value: '24 hr', 402 | aliases: ['days'] 403 | }, 404 | week: { 405 | value: '7 day', 406 | aliases: ['weeks'] 407 | }, 408 | month: { 409 | value: '30.4375 day', // 1/12th of Julian year 410 | aliases: ['months'] 411 | }, 412 | year: { 413 | value: '365.25 day', // Julian year 414 | aliases: ['years'] 415 | }, 416 | decade: { 417 | value: '10 year', // Julian decade 418 | aliases: ['decades'] 419 | }, 420 | century: { 421 | value: '100 year', // Julian century 422 | aliases: ['centuries'] 423 | }, 424 | millennium: { 425 | value: '1000 year', // Julian millennium 426 | aliases: ['millennia'] 427 | }, 428 | 429 | // Frequency 430 | hertz: { 431 | prefixGroup: 'LONG', 432 | formatPrefixes: ['', 'kilo', 'mega', 'giga', 'tera'], 433 | value: '1/s' 434 | }, 435 | Hz: { 436 | prefixGroup: 'SHORT', 437 | formatPrefixes: ['', 'k', 'M', 'G', 'T'], 438 | value: '1 hertz' 439 | }, 440 | 441 | // Angle 442 | rad: { 443 | quantity: 'ANGLE', 444 | prefixGroup: 'SHORT', 445 | formatPrefixes: ['m', ''], 446 | value: 1 447 | }, 448 | radian: { 449 | prefixGroup: 'LONG', 450 | formatPrefixes: ['milli', ''], 451 | value: '1 rad', 452 | aliases: ['radians'] 453 | }, 454 | sr: { 455 | quantity: 'SOLID_ANGLE', 456 | prefixGroup: 'SHORT', 457 | formatPrefixes: ['u', 'm', ''], 458 | value: 1 459 | }, 460 | steradian: { 461 | prefixGroup: 'LONG', 462 | formatPrefixes: ['micro', 'milli', ''], 463 | value: '1 sr', 464 | aliases: ['steradians'] 465 | }, 466 | deg: { 467 | value: [Math.PI / 180, 'rad'], 468 | aliases: ['degree', 'degrees'] 469 | }, 470 | grad: { 471 | prefixGroup: 'SHORT', 472 | formatPrefixes: ['c'], 473 | value: [Math.PI / 200, 'rad'] 474 | }, 475 | gradian: { 476 | prefixGroup: 'LONG', 477 | formatPrefixes: ['centi', ''], 478 | value: [Math.PI / 200, 'rad'], 479 | aliases: ['gradians'] 480 | }, 481 | cycle: { 482 | value: [2 * Math.PI, 'rad'], 483 | aliases: ['cycles'] 484 | }, 485 | arcmin: { 486 | value: '0.016666666666666666 deg', 487 | aliases: ['arcminute', 'arcminutes'] 488 | }, 489 | arcsec: { 490 | value: '0.016666666666666666 arcmin', 491 | aliases: ['arcsecond', 'arcseconds'] 492 | }, 493 | 494 | // Electric current 495 | A: { 496 | quantity: 'CURRENT', 497 | prefixGroup: 'SHORT', 498 | formatPrefixes: ['u', 'm', '', 'k'], 499 | value: 1 500 | }, 501 | ampere: { 502 | prefixGroup: 'LONG', 503 | formatPrefixes: ['micro', 'milli', '', 'kilo'], 504 | value: '1 A', 505 | aliases: ['amperes'] 506 | }, 507 | 508 | // Temperature 509 | // K(C) = °C + 273.15 510 | // K(F) = (°F + 459.67) / 1.8 511 | // K(R) = °R / 1.8 512 | K: { 513 | quantity: 'TEMPERATURE', 514 | prefixGroup: 'SHORT', 515 | formatPrefixes: ['n', 'u', 'm', ''], 516 | value: 1 517 | }, 518 | kelvin: { 519 | prefixGroup: 'LONG', 520 | formatPrefixes: ['nano', 'micro', 'milli', ''], 521 | value: '1 K' 522 | }, 523 | degC: { 524 | value: '1 K', 525 | offset: 273.15, 526 | aliases: ['celsius'] 527 | }, 528 | degR: { 529 | value: [1 / 1.8, 'K'], 530 | aliases: ['rankine', 'R'] 531 | }, 532 | degF: { 533 | value: '1 R', 534 | offset: 459.67, 535 | aliases: ['fahrenheit'] 536 | }, 537 | 538 | // amount of substance 539 | mol: { 540 | quantity: 'AMOUNT_OF_SUBSTANCE', 541 | prefixGroup: 'SHORT', 542 | formatPrefixes: ['', 'k'], 543 | value: 1 544 | }, 545 | mole: { 546 | prefixGroup: 'LONG', 547 | formatPrefixes: ['', 'kilo'], 548 | value: '1 mol', 549 | aliases: ['moles'] 550 | }, 551 | 552 | // luminous intensity 553 | cd: { 554 | quantity: 'LUMINOUS_INTENSITY', 555 | value: 1, 556 | prefixGroup: 'SHORT', 557 | formatPrefixes: ['', 'm'] 558 | }, 559 | candela: { 560 | value: '1 cd', 561 | prefixGroup: 'LONG', 562 | formatPrefixes: ['', 'milli'] 563 | }, 564 | 565 | // luminous flux 566 | lumen: { 567 | prefixGroup: 'LONG', 568 | value: '1 cd sr', 569 | aliases: ['lumens'] 570 | }, 571 | lm: { 572 | prefixGroup: 'SHORT', 573 | value: '1 lumen' 574 | }, 575 | 576 | // illuminance 577 | lux: { 578 | prefixGroup: 'LONG', 579 | value: '1 cd/m^2' 580 | }, 581 | lx: { 582 | prefixGroup: 'SHORT', 583 | value: '1 lux' 584 | }, 585 | 586 | // Force 587 | N: { 588 | prefixGroup: 'SHORT', 589 | formatPrefixes: ['u', 'm', '', 'k', 'M'], // These could be debatable 590 | value: '1 kg m/s^2' 591 | }, 592 | newton: { 593 | prefixGroup: 'LONG', 594 | formatPrefixes: ['micro', 'milli', '', 'kilo', 'mega'], 595 | value: '1 N', 596 | aliases: ['newtons'] 597 | }, 598 | dyn: { 599 | prefixGroup: 'SHORT', 600 | formatPrefixes: ['m', 'k', 'M'], 601 | value: '1 g cm/s^2' 602 | }, 603 | dyne: { 604 | prefixGroup: 'LONG', 605 | formatPrefixes: ['milli', 'kilo', 'mega'], 606 | value: '1 dyn' 607 | }, 608 | lbf: { 609 | value: '4.4482216152605 N', 610 | aliases: ['poundforce'] 611 | }, 612 | kip: { 613 | value: '1000 lbf', 614 | aliases: ['kips'] 615 | }, 616 | 617 | // Energy 618 | J: { 619 | prefixGroup: 'SHORT', 620 | formatPrefixes: ['m', '', 'k', 'M', 'G'], 621 | value: '1 N m' 622 | }, 623 | joule: { 624 | prefixGroup: 'LONG', 625 | formatPrefixes: ['milli', '', 'kilo', 'mega', 'giga'], 626 | value: '1 J', 627 | aliases: ['joules'] 628 | }, 629 | erg: { 630 | value: '1 dyn cm' 631 | }, 632 | Wh: { 633 | prefixGroup: 'SHORT', 634 | formatPrefixes: ['k', 'M', 'G', 'T'], 635 | value: '1 W hr' 636 | }, 637 | BTU: { 638 | prefixGroup: 'BTU', 639 | formatPrefixes: ['', 'MM'], 640 | value: '1055.05585262 J', 641 | aliases: ['BTUs'] 642 | }, 643 | eV: { 644 | prefixGroup: 'SHORT', 645 | formatPrefixes: ['u', 'm', '', 'k', 'M', 'G'], 646 | value: '1.602176565e-19 J' 647 | }, 648 | electronvolt: { 649 | prefixGroup: 'LONG', 650 | formatPrefixes: ['micro', 'milli', '', 'kilo', 'mega', 'giga'], 651 | value: '1 eV', 652 | aliases: ['electronvolts'] 653 | }, 654 | 655 | // Power 656 | W: { 657 | prefixGroup: 'SHORT', 658 | formatPrefixes: ['p', 'n', 'u', 'm', '', 'k', 'M', 'G', 'T', 'P'], 659 | value: '1 J/s' 660 | }, 661 | watt: { 662 | prefixGroup: 'LONG', 663 | formatPrefixes: ['pico', 'nano', 'micro', 'milli', '', 'kilo', 'mega', 'tera', 'peta'], 664 | value: '1 W', 665 | aliases: ['watts'] 666 | }, 667 | hp: { value: '550 ft lbf / s' }, 668 | 669 | // Electrical power units 670 | VA: { 671 | prefixGroup: 'SHORT', 672 | formatPrefixes: ['', 'k'], 673 | value: '1 W' 674 | }, 675 | 676 | // Pressure 677 | Pa: { 678 | prefixGroup: 'SHORT', 679 | formatPrefixes: ['', 'k', 'M', 'G'], // 'h' is sometimes used but not often 680 | value: '1 N / m^2' 681 | }, 682 | psi: { 683 | value: '1 lbf/in^2' 684 | // kpsi is sometimes used 685 | }, 686 | atm: { value: '101325 Pa' }, 687 | bar: { 688 | prefixGroup: 'SHORT_LONG', 689 | formatPrefixes: ['m', ''], 690 | value: '1e5 Pa' 691 | }, 692 | torr: { 693 | prefixGroup: 'LONG', 694 | formatPrefixes: ['milli', ''], 695 | value: '133.32236842105263 Pa' 696 | }, 697 | Torr: { 698 | prefixGroup: 'SHORT', 699 | formatPrefixes: ['m', ''], 700 | value: '1 torr' 701 | }, 702 | mmHg: { 703 | value: '133.322387415 Pa', 704 | aliases: ['mmhg'] 705 | }, 706 | inH2O: { 707 | value: '249.082 Pa', 708 | aliases: ['inh2o', 'inAq'] 709 | }, 710 | 711 | // Electric charge 712 | C: { 713 | prefixGroup: 'SHORT', 714 | formatPrefixes: ['p', 'n', 'u', 'm', ''], 715 | value: '1 A s' 716 | }, 717 | coulomb: { 718 | prefixGroup: 'LONG', 719 | formatPrefixes: ['pico', 'nano', 'micro', 'milli', ''], 720 | value: '1 C', 721 | aliases: ['coulombs'] 722 | }, 723 | 724 | // Electric potential 725 | V: { 726 | prefixGroup: 'SHORT', 727 | formatPrefixes: ['m', '', 'k', 'M'], 728 | value: '1 W/A' 729 | }, 730 | volt: { 731 | prefixGroup: 'LONG', 732 | formatPrefixes: ['milli', '', 'kilo', 'mega'], 733 | value: '1 V', 734 | aliases: ['volts'] 735 | }, 736 | // Electric capacitance 737 | F: { 738 | prefixGroup: 'SHORT', 739 | formatPrefixes: ['p', 'n', 'u', 'm', ''], 740 | value: '1 C/V' 741 | }, 742 | farad: { 743 | prefixGroup: 'LONG', 744 | formatPrefixes: ['pico', 'nano', 'micro', 'milli', ''], 745 | value: '1 F', 746 | aliases: ['farads'] 747 | }, 748 | 749 | // Electric resistance 750 | ohm: { 751 | prefixGroup: 'SHORT_LONG', // Both Mohm and megaohm are acceptable 752 | formatPrefixes: ['', 'k', 'M'], 753 | value: '1 V/A', 754 | aliases: ['ohms'] 755 | }, 756 | /* 757 | * Unicode breaks in browsers if charset is not specified 758 | * TODO: Allow with config option? 759 | Ω: { 760 | prefixes: 'SHORT', 761 | commonPrefixes: ['', 'k', 'M'], 762 | value: '1 ohm', 763 | }, 764 | */ 765 | // Electric inductance 766 | H: { 767 | prefixGroup: 'SHORT', 768 | formatPrefixes: ['u', 'm', ''], 769 | value: '1 V s / A' 770 | }, 771 | henry: { 772 | prefixGroup: 'LONG', 773 | formatPrefixes: ['micro', 'milli', ''], // Just guessing here 774 | value: '1 H', 775 | aliases: ['henries'] 776 | }, 777 | // Electric conductance 778 | S: { 779 | prefixGroup: 'SHORT', 780 | formatPrefixes: ['u', 'm', ''], 781 | value: '1 / ohm' 782 | }, 783 | siemens: { 784 | prefixGroup: 'LONG', 785 | formatPrefixes: ['micro', 'milli', ''], 786 | value: '1 S' 787 | }, 788 | // Magnetic flux 789 | Wb: { 790 | prefixGroup: 'SHORT', 791 | formatPrefixes: ['n', 'u', 'm', ''], 792 | value: '1 V s' 793 | }, 794 | weber: { 795 | prefixGroup: 'LONG', 796 | formatPrefixes: ['nano', 'micro', 'milli', ''], 797 | value: '1 Wb', 798 | aliases: ['webers'] 799 | }, 800 | // Magnetic flux density 801 | T: { 802 | prefixGroup: 'SHORT', 803 | formatPrefixes: ['n', 'u', 'm', ''], 804 | value: '1 N s / C m' 805 | }, 806 | tesla: { 807 | prefixGroup: 'LONG', 808 | formatPrefixes: ['nano', 'micro', 'milli', ''], 809 | value: '1 T', 810 | aliases: ['teslas'] 811 | }, 812 | 813 | // Binary 814 | // TODO: Figure out how to do SI vs. IEC while formatting 815 | b: { 816 | quantity: 'BIT', 817 | prefixGroup: 'BINARY_SHORT', 818 | value: 1 819 | }, 820 | bits: { 821 | prefixGroup: 'BINARY_LONG', 822 | value: '1 b', 823 | aliases: ['bit'] 824 | }, 825 | B: { 826 | prefixGroup: 'BINARY_SHORT', 827 | value: '8 b' 828 | }, 829 | bytes: { 830 | prefixGroup: 'BINARY_LONG', 831 | value: '1 B', 832 | aliases: ['byte'] 833 | } 834 | } 835 | -------------------------------------------------------------------------------- /src/Parser.ts: -------------------------------------------------------------------------------- 1 | import { FindUnitFn, RequiredOptions, ParsedUnit } from "./types" 2 | 3 | const ignoredCharacters = ' \t()*' 4 | 5 | /** 6 | * Returns a new Parser. 7 | */ 8 | export function createParser(options: RequiredOptions, findUnit: FindUnitFn) { 9 | // private variables and functions for the Unit parser 10 | let text: string, index: number, c: string 11 | 12 | function skipIgnored() { 13 | while (c && ignoredCharacters.includes(c)) { 14 | next() 15 | } 16 | } 17 | 18 | function isDigitDot(c: string) { 19 | return ((c >= '0' && c <= '9') || c === '.') 20 | } 21 | 22 | function isDigit(c: string) { 23 | return ((c >= '0' && c <= '9')) 24 | } 25 | 26 | function next () { 27 | index++ 28 | c = text.charAt(index) 29 | } 30 | 31 | function revert(oldIndex: number) { 32 | index = oldIndex 33 | c = text.charAt(index) 34 | } 35 | 36 | function parseNonFinite() { 37 | const nonFiniteStrings = ['NaN', 'Infinity', '-Infinity'] 38 | for (let nonFiniteString of nonFiniteStrings) { 39 | if (text.substr(index, nonFiniteString.length) === nonFiniteString) { 40 | index += nonFiniteString.length 41 | c = text.charAt(index) 42 | return nonFiniteString 43 | } 44 | } 45 | return null 46 | } 47 | 48 | function parseNumber () { 49 | let number = '' 50 | let oldIndex 51 | oldIndex = index 52 | 53 | if (c === '+') { 54 | next() 55 | } else if (c === '-') { 56 | number += c 57 | next() 58 | } 59 | 60 | if (!isDigitDot(c)) { 61 | // a + or - must be followed by a digit 62 | revert(oldIndex) 63 | return null 64 | } 65 | 66 | // get number, can have a single dot 67 | if (c === '.') { 68 | number += c 69 | next() 70 | if (!isDigit(c)) { 71 | // this is no legal number, it is just a dot 72 | revert(oldIndex) 73 | return null 74 | } 75 | } else { 76 | while (isDigit(c)) { 77 | number += c 78 | next() 79 | } 80 | if (c === '.') { 81 | number += c 82 | next() 83 | } 84 | } 85 | while (isDigit(c)) { 86 | number += c 87 | next() 88 | } 89 | 90 | // check for exponential notation like "2.3e-4" or "1.23e50" 91 | if (c === 'E' || c === 'e') { 92 | // The grammar branches here. This could either be part of an exponent or the start of a unit that begins with the letter e, such as "4exabytes" 93 | 94 | let tentativeNumber = '' 95 | const tentativeIndex = index 96 | 97 | tentativeNumber += c 98 | next() 99 | 100 | // @ts-ignore: Typescript does not realize that c has changed 101 | if (c === '+' || c === '-') { 102 | tentativeNumber += c 103 | next() 104 | } 105 | 106 | // Scientific notation MUST be followed by an exponent (otherwise we assume it is not scientific notation) 107 | if (!isDigit(c)) { 108 | // The e or E must belong to something else, so return the number without the e or E. 109 | revert(tentativeIndex) 110 | return number 111 | } 112 | 113 | // We can now safely say that this is scientific notation. 114 | number = number + tentativeNumber 115 | while (isDigit(c)) { 116 | number += c 117 | next() 118 | } 119 | } 120 | 121 | return number 122 | } 123 | 124 | function parseUnit () { 125 | let unitName = '' 126 | 127 | // Alphanumeric characters only; matches [a-zA-Z0-9] 128 | let code = text.charCodeAt(index) 129 | while ((code >= 48 && code <= 57) || 130 | (code >= 65 && code <= 90) || 131 | (code >= 97 && code <= 122)) { 132 | unitName += c 133 | next() 134 | code = text.charCodeAt(index) 135 | } 136 | 137 | // Must begin with [a-zA-Z] 138 | code = unitName.charCodeAt(0) 139 | if ((code >= 65 && code <= 90) || 140 | (code >= 97 && code <= 122)) { 141 | return unitName 142 | } else { 143 | return null 144 | } 145 | } 146 | 147 | function parseCharacter(toFind: string) { 148 | if (c === toFind) { 149 | next() 150 | return toFind 151 | } else { 152 | return null 153 | } 154 | } 155 | 156 | /** 157 | * Parse a string and return the numeric value (or null) and an array of units with their powers. 158 | * 159 | * Throws an exception if the provided string does not contain a valid unit or 160 | * cannot be parsed. 161 | * @memberof Unit 162 | * @param {string} str A string like "5.2 inch", "4e2 cm/s^2" 163 | * @return {Object} { value, unitArray } 164 | */ 165 | function parse(str: string): ParsedUnit { 166 | // console.log(`parse("${str}")`) 167 | 168 | text = str 169 | index = -1 170 | c = '' 171 | 172 | if (typeof text !== 'string') { 173 | throw new TypeError('Invalid argument in parse, string expected') 174 | } 175 | 176 | const unit: ParsedUnit = { 177 | type: 'Unit', 178 | value: null, 179 | unitList: [], 180 | dimension: {} 181 | } 182 | 183 | 184 | let powerMultiplierCurrent = 1 185 | let expectingUnit = false 186 | 187 | // A unit should follow this pattern: 188 | // [number|[-]Infinity|NaN] ...[ [*/] unit[^number] ] 189 | // unit[^number] ... [ [*/] unit[^number] ] 190 | 191 | // Rules: 192 | // number is any floating point number. 193 | // unit is any alphanumeric string beginning with an alpha. Units with names like e3 should be avoided because they look like the exponent of a floating point number! 194 | // The string may optionally begin with a number. 195 | // Each unit may optionally be followed by ^number. 196 | // Whitespace or a forward slash is recommended between consecutive units, although the following technically is parseable: 197 | // 2m^2kg/s^2 198 | // it is not good form. If a unit starts with e, then it could be confused as a floating point number: 199 | // 4erg 200 | 201 | next() 202 | skipIgnored() 203 | 204 | // Optional number or non-finite string at the start of the string 205 | const valueStr = parseNonFinite() || parseNumber() 206 | // console.log(`valueStr = "${valueStr}"`) 207 | 208 | if (valueStr) { 209 | unit.value = options.type.conv(valueStr) 210 | 211 | skipIgnored() // Whitespace is not required here 212 | 213 | // handle multiplication or division right after the value, like '1/s' 214 | if (parseCharacter('/')) { 215 | powerMultiplierCurrent = -1 216 | expectingUnit = true 217 | } 218 | } 219 | 220 | while (true) { 221 | skipIgnored() 222 | 223 | // Parentheses are not allowed 224 | // if (c === '(' || c === ')') { 225 | // throw new SyntaxError(`Unexpected "${c}" in "${text}" at index ${index}`) 226 | // } 227 | 228 | // Is there something here? 229 | let uStr 230 | if (c) { 231 | const oldC = c 232 | uStr = parseUnit() 233 | if (uStr === null) { 234 | throw new SyntaxError('Unexpected "' + oldC + '" in "' + text + '" at index ' + index.toString()) 235 | } 236 | } else { 237 | // End of input. 238 | break 239 | } 240 | 241 | // Verify the unit exists and get the prefix (if any) 242 | const found = findUnit(uStr) 243 | if (found === null) { 244 | // Unit not found. 245 | throw new SyntaxError('Unit "' + uStr + '" not found.') 246 | } 247 | 248 | let power = powerMultiplierCurrent 249 | // Is there a "^ number"? 250 | skipIgnored() 251 | if (parseCharacter('^')) { 252 | skipIgnored() 253 | const p = parseNumber() 254 | if (p === null) { 255 | // No valid number found for the power! 256 | throw new SyntaxError('In "' + str + '", "^" must be followed by a floating-point number') 257 | } 258 | power *= +p 259 | } 260 | 261 | // Add the unit to the list 262 | unit.unitList.push({ 263 | unit: found.unit, 264 | prefix: found.prefix, 265 | power: power 266 | }) 267 | 268 | for (let dim of Object.keys(found.unit.dimension)) { 269 | unit.dimension[dim] = (unit.dimension[dim] || 0) + (found.unit.dimension[dim] || 0) * power 270 | } 271 | 272 | skipIgnored() 273 | 274 | // "/" means we are expecting something to come next. 275 | // Is there a forward slash? If so, set powerMultiplierCurrent to -1. All remaining units will be in the denominator. 276 | expectingUnit = false 277 | 278 | if (parseCharacter('/')) { 279 | if (powerMultiplierCurrent === -1) { 280 | throw new SyntaxError(`Unexpected additional "/" in "${text}" at index ${index}`) 281 | } 282 | powerMultiplierCurrent = -1 283 | expectingUnit = true 284 | } 285 | } 286 | 287 | // Is there a trailing slash? 288 | if (expectingUnit) { 289 | throw new SyntaxError('Trailing characters: "' + str + '"') 290 | } 291 | 292 | return unit 293 | } 294 | 295 | return parse 296 | } 297 | -------------------------------------------------------------------------------- /src/UnitStore.ts: -------------------------------------------------------------------------------- 1 | import { createParser } from './Parser' 2 | import { normalize } from './utils' 3 | import * as builtIns from './BuiltIns' 4 | import { RequiredOptions, ParsedUnit, Definitions, DefinitionsExtended, UnitProps, UnitPropsExtended, UnitStore, UnitSystems, UnitPropsWithQuantity } from './types'; 5 | 6 | /** 7 | * Creates a new unit store. 8 | */ 9 | export function createUnitStore(options: RequiredOptions): UnitStore { 10 | /* Units are defined by these objects: 11 | * defs.prefixes 12 | * defs.units 13 | */ 14 | 15 | // TODO: Should we deep freeze the built-ins to prevent modification of the built-in units? 16 | 17 | const { skipBuiltIns } = options.definitions 18 | 19 | // Merge the built-in units with the user's definitions 20 | 21 | let systems: UnitSystems 22 | 23 | if (skipBuiltIns) { 24 | systems = { ...options.definitions.systems } 25 | } else { 26 | systems = { ...builtIns.systems } as any 27 | 28 | // Prepend the user's units onto the built-in ones, so that the user's will be chosen first 29 | for (let system of Object.keys(options.definitions.systems)) { 30 | if (systems.hasOwnProperty(system)) { 31 | systems[system] = [...options.definitions.systems[system], ...systems[system]] 32 | } else { 33 | systems[system] = [...options.definitions.systems[system]] 34 | } 35 | } 36 | } 37 | 38 | const originalDefinitions: Definitions = { 39 | systems, 40 | prefixGroups: { 41 | ...(skipBuiltIns ? {} : builtIns.prefixes), 42 | ...options.definitions.prefixGroups 43 | }, 44 | units: { 45 | ...(skipBuiltIns ? {} : builtIns.units), 46 | ...options.definitions.units 47 | } 48 | } 49 | 50 | // These will contain copies we can mutate without affecting the originals 51 | const defs: DefinitionsExtended = { 52 | units: {}, 53 | prefixGroups: { ...originalDefinitions.prefixGroups }, 54 | systems: {} 55 | } 56 | // for (let system of Object.keys(originalDefinitions.systems)) { 57 | // defs.systems[system] = originalDefinitions.systems[system].slice() 58 | // } 59 | 60 | /* All of the prefixes, units, and systems have now been defined. 61 | * 62 | * We will perform the following processing steps to prepare the UnitStore for use: 63 | * 64 | * - For each QUANTITY, parse its value and replace it with a dimension array, where each index of the array 65 | * corresponds to the base quantity's index in defs.baseQuantities, and the value of each element is the power 66 | * (exponent) of that base in the dimension. 67 | * 68 | * - Initialize the parser with an empty set of units. 69 | * 70 | * - Loop through the units. If the unit has a `quantity` property, initialize that unit with the quantity's 71 | * dimension, and the given value property. If the unit does not, then parse the unit's value property (which is 72 | * either a string or an two-element array) using the parser, and create the dimension and value from the resulting 73 | * Unit. Create the unit with the name, dimension, value, offset, prefixes, and commonPrefixes properties. Convert 74 | * the prefixes from a string to the associated object from the defs.prefixes object. 75 | * 76 | * - Some units will fail to be parsed if the defs.units object keys are not enumerated in the optimal order. Repeat 77 | * the loop until all units have been converted. 78 | * 79 | * - Verify that each unit's commonPrefixes are contained in prefixes. 80 | * 81 | * - Loop through the defs.systems and convert the strings into valueless units. 82 | * 83 | * - Add the unit system names to each unit (as an array) for reverse lookup. 84 | * 85 | * - Clone units that have aliases. Shallow copies are acceptable since the resulting defs.units object will be 86 | * deep-immutable. 87 | * 88 | */ 89 | 90 | // Create a parser configured for these options, and also supply it with the findUnit function 91 | const parser = createParser(options, findUnit) 92 | 93 | // Loop through the units. If the unit has a `quantity` property, initialize that unit with the quantity's dimension, 94 | // and the given value property. If the unit does not, then parse the unit's value property (which is either a string 95 | // or a two-element array) using the parser, and create the dimension and value from the resulting Unit. Create the 96 | // unit with the name, dimension, value, offset, prefixes, and commonPrefixes properties. Convert the prefixes from a 97 | // string to the associated object from the defs.prefixes object. 98 | 99 | while (true) { 100 | let unitsAdded = 0 101 | let unitsSkipped = [] 102 | let reasonsSkipped = [] 103 | 104 | for (const unitDefKey of Object.keys(originalDefinitions.units)) { 105 | if (defs.units.hasOwnProperty(unitDefKey)) continue 106 | 107 | const unitDef = originalDefinitions.units[unitDefKey] 108 | if (!unitDef) continue 109 | 110 | // const unitDef = unitDef 111 | 112 | // uses unknown set of prefixes? 113 | if (typeof unitDef !== 'string' && unitDef.prefixGroup && !defs.prefixGroups.hasOwnProperty(unitDef.prefixGroup)) { 114 | throw new Error(`Unknown prefixes '${unitDef.prefixGroup}' for unit '${unitDefKey}'`) 115 | } 116 | 117 | let unitValue: T 118 | let unitDimension: { [s: string]: number } 119 | let unitQuantity: UnitPropsWithQuantity['quantity'] | undefined 120 | 121 | let skipThisUnit = false 122 | if (isUnitPropsWithQuantity(unitDef)) { 123 | // Defining the unit based on a quantity. 124 | unitValue = options.type.conv(unitDef.value) 125 | unitDimension = { [unitDef.quantity]: 1 } 126 | unitQuantity = unitDef.quantity 127 | } else { 128 | // Defining the unit based on other units. 129 | let parsed: ParsedUnit 130 | try { 131 | if (unitDef.hasOwnProperty('value')) { 132 | if (unitDef && typeof unitDef.value === 'string') { 133 | parsed = parser(unitDef.value) 134 | } else if (Array.isArray(unitDef.value) && unitDef.value.length === 2) { 135 | parsed = parser(unitDef.value[1]) 136 | parsed.value = options.type.conv(unitDef.value[0]) 137 | } else { 138 | throw new TypeError(`Unit definition for '${unitDefKey}' must be an object with a value property where the value is a string or a two-element array.`) 139 | } 140 | } else { 141 | throw new TypeError(`Unit definition for '${unitDefKey}' must be an object with a value property where the value is a string or a two-element array.`) 142 | } 143 | if (parsed.value == null) { 144 | throw new Error(`Parsing value for '${unitDefKey}' resulted in invalid value: ${parsed.value}`) 145 | } 146 | unitValue = normalize(parsed.unitList, parsed.value, options.type) 147 | unitDimension = Object.freeze(parsed.dimension) 148 | } catch (ex) { 149 | if (ex instanceof Error && /Unit.*not found/.test(ex.toString())) { 150 | unitsSkipped.push(unitDefKey) 151 | reasonsSkipped.push(ex.toString()) 152 | skipThisUnit = true 153 | } else { 154 | throw ex 155 | } 156 | } 157 | } 158 | 159 | if (!skipThisUnit) { 160 | // Add this units and its aliases (they are all the same except for the name) 161 | let unitAndAliases = [unitDefKey] 162 | if (unitDef.aliases) { 163 | unitAndAliases.push(...unitDef.aliases) 164 | } 165 | unitAndAliases.forEach(newUnitName => { 166 | if (defs.units.hasOwnProperty(newUnitName)) { 167 | throw new Error(`Alias '${newUnitName}' would override an existing unit`) 168 | } 169 | if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(newUnitName) && newUnitName !== '') { 170 | throw new SyntaxError(`Unit name contains non-alphanumeric characters or begins with a number: '${newUnitName}'`) 171 | } 172 | const newUnit: UnitPropsExtended = { 173 | name: newUnitName, 174 | value: unitValue, 175 | offset: options.type.conv(unitDef.offset ? unitDef.offset : 0), 176 | dimension: unitDimension, 177 | prefixGroup: (unitDef.prefixGroup && defs.prefixGroups[unitDef.prefixGroup]) || { '': 1 }, 178 | formatPrefixes: unitDef.formatPrefixes, 179 | basePrefix: unitDef.basePrefix 180 | // systems: [] 181 | } 182 | if (unitQuantity) newUnit.quantity = unitQuantity 183 | Object.freeze(newUnit) 184 | defs.units[newUnitName] = newUnit 185 | unitsAdded++ 186 | }) 187 | } 188 | } 189 | 190 | // console.log(`Added ${unitsAdded} units and skipped: ${unitsSkipped.join(', ')}`) 191 | if (unitsSkipped.length === 0) break 192 | else if (unitsAdded === 0) { 193 | throw new Error(`Could not create the following units: ${unitsSkipped.join(', ')}. Reasons follow: ${reasonsSkipped.join(' ')}`) 194 | } 195 | } 196 | 197 | // Check to make sure config options has selected a unit system that exists. 198 | if (options.system !== 'auto') { 199 | if (!originalDefinitions.systems.hasOwnProperty(options.system)) { 200 | throw new Error(`Unknown unit system ${options.system}. Available systems are: auto, ${Object.keys(originalDefinitions.systems).join(', ')} `) 201 | } 202 | } 203 | 204 | // Replace unit system strings with valueless units 205 | for (let system of Object.keys(originalDefinitions.systems)) { 206 | let sys = originalDefinitions.systems[system] 207 | defs.systems[system] = [] 208 | for (let i = 0; i < sys.length; i++) { 209 | // Important! The unit below is not a real unit, but for now it is-close enough 210 | let unit = parser(sys[i]) 211 | unit.type = 'Unit' 212 | Object.freeze(unit) 213 | defs.systems[system][i] = unit 214 | } 215 | } 216 | 217 | // Final setup for units 218 | for (let key of Object.keys(defs.units)) { 219 | const unit = defs.units[key] 220 | // Check that each commonPrefix is in prefixes 221 | if (unit.formatPrefixes) { 222 | for (let i = 0; i < unit.formatPrefixes.length; i++) { 223 | let s = unit.formatPrefixes[i] 224 | if (!unit.prefixGroup.hasOwnProperty(s)) { 225 | throw new Error(`In unit ${unit.name}, common prefix ${s} was not found among the allowable prefixes`) 226 | } 227 | } 228 | } 229 | } 230 | 231 | /** 232 | * Tests whether the given string exists as a known unit. The unit may have a prefix. 233 | * @param {string} singleUnitString The name of the unit, with optional prefix. 234 | */ 235 | function exists(singleUnitString: string) { 236 | return findUnit(singleUnitString) !== null 237 | } 238 | 239 | /** 240 | * Find a unit from a string 241 | * @param {string} unitString A string like 'cm' or 'inch' 242 | * @returns {Object | null} result When found, an object with fields unit and 243 | * prefix is returned. Else, null is returned. 244 | * @private 245 | */ 246 | function findUnit(unitString: string): { unit: UnitPropsExtended, prefix: string } | null { 247 | if (typeof unitString !== 'string') { 248 | throw new TypeError(`parameter must be a string (${unitString} given)`) 249 | } 250 | // First, match units names exactly. For example, a user could define 'mm' as 10^-4 m, which is silly, but then we would want 'mm' to match the user-defined unit. 251 | if (defs.units.hasOwnProperty(unitString)) { 252 | const unit = defs.units[unitString] 253 | return { 254 | unit, 255 | prefix: '' 256 | } 257 | } 258 | 259 | for (const name of Object.keys(defs.units)) { 260 | if (unitString.substring(unitString.length - name.length, unitString.length) === name) { 261 | const unit = defs.units[name] 262 | const prefixLen = (unitString.length - name.length) 263 | const prefix = unitString.substring(0, prefixLen) 264 | if (unit.prefixGroup.hasOwnProperty(prefix)) { 265 | // store unit, prefix, and value 266 | // console.log(`findUnit(${unitString}): { unit.name: ${unit.name}, prefix: ${prefix} }`) 267 | return { 268 | unit, 269 | prefix 270 | } 271 | } 272 | } 273 | } 274 | 275 | return null 276 | } 277 | 278 | Object.freeze(defs.prefixGroups) 279 | Object.freeze(defs.systems) 280 | Object.freeze(defs.units) 281 | 282 | return { originalDefinitions, defs, exists, findUnit, parser } 283 | 284 | } 285 | 286 | function isUnitPropsWithQuantity(unit: UnitProps): unit is UnitPropsWithQuantity { 287 | return typeof unit !== 'string' && unit.quantity !== undefined 288 | } 289 | 290 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export const symIsDefaultFun = Symbol('_IS_UNITMATH_DEFAULT_FUNCTION') 3 | 4 | export interface TypeArithmetics { 5 | conv: { 6 | (a: any): T 7 | [symIsDefaultFun]?: boolean 8 | } 9 | clone: { 10 | (a: T): T 11 | [symIsDefaultFun]?: boolean 12 | } 13 | abs: { 14 | (a: T): T 15 | [symIsDefaultFun]?: boolean 16 | } 17 | add: { 18 | (a: T, b: T): T 19 | [symIsDefaultFun]?: boolean 20 | } 21 | sub: { 22 | (a: T, b: T): T 23 | [symIsDefaultFun]?: boolean 24 | } 25 | mul: { 26 | (a: T, b: T): T 27 | [symIsDefaultFun]?: boolean 28 | } 29 | div: { 30 | (a: T, b: T): T 31 | [symIsDefaultFun]?: boolean 32 | } 33 | pow: { 34 | (a: T, b: T): T 35 | [symIsDefaultFun]?: boolean 36 | } 37 | eq: { 38 | (a: T, b: T): boolean 39 | [symIsDefaultFun]?: boolean 40 | } 41 | lt: { 42 | (a: T, b: T): boolean 43 | [symIsDefaultFun]?: boolean 44 | } 45 | le: { 46 | (a: T, b: T): boolean 47 | [symIsDefaultFun]?: boolean 48 | } 49 | ge: { 50 | (a: T, b: T): boolean 51 | [symIsDefaultFun]?: boolean 52 | } 53 | gt: { 54 | (a: T, b: T): boolean 55 | [symIsDefaultFun]?: boolean 56 | } 57 | 58 | round: { 59 | (a: T): T 60 | [symIsDefaultFun]?: boolean 61 | } 62 | trunc: { 63 | (a: T): T 64 | [symIsDefaultFun]?: boolean 65 | } 66 | } 67 | 68 | export interface AtomicUnit { 69 | unit: UnitPropsExtended, 70 | prefix: string, 71 | power: number 72 | } 73 | 74 | export interface FormatOptions { 75 | precision?: number 76 | parentheses?: boolean 77 | formatter?: { 78 | (a: T): string 79 | [symIsDefaultFun]?: boolean 80 | } 81 | } 82 | 83 | export interface PrefixOptions { 84 | autoPrefix?: boolean 85 | prefixMin?: T 86 | prefixMax?: T 87 | formatPrefixDefault?: 'all' | 'none' 88 | } 89 | 90 | export interface SimplifyOptions extends PrefixOptions { 91 | // simplifyThreshold?: number 92 | system?: string 93 | } 94 | 95 | export type RequiredOptions = 96 | & Required> 97 | & Required> 98 | & { 99 | type: TypeArithmetics 100 | definitions: Definitions & { skipBuiltIns?: boolean } 101 | } 102 | 103 | export type Options = 104 | & FormatOptions 105 | & SimplifyOptions 106 | & { 107 | type?: Partial> 108 | definitions?: Partial & { skipBuiltIns?: boolean } 109 | } 110 | 111 | 112 | export interface PrefixGroups { 113 | [prefixGroup: string]: Record 114 | } 115 | 116 | export interface UnitSystems { 117 | [system: string]: string[] 118 | } 119 | 120 | 121 | interface UnitPropsCommons { 122 | prefixGroup?: string 123 | basePrefix?: string 124 | formatPrefixes?: string[] 125 | aliases?: string[] 126 | offset?: number 127 | } 128 | 129 | export interface UnitPropsWithQuantity 130 | extends UnitPropsCommons { 131 | quantity: string 132 | value: number 133 | } 134 | 135 | export interface UnitPropsStringValue 136 | extends UnitPropsCommons { 137 | quantity?: undefined 138 | value: string 139 | } 140 | 141 | export interface UnitPropsTupleValue 142 | extends UnitPropsCommons { 143 | quantity?: undefined 144 | value: [number, string] 145 | } 146 | 147 | export type UnitProps = UnitPropsStringValue | UnitPropsWithQuantity | UnitPropsTupleValue 148 | 149 | 150 | export interface UnitPropsExtended { 151 | name: string 152 | quantity?: string 153 | value: T 154 | dimension: Record, 155 | prefixGroup: Record 156 | basePrefix?: string 157 | formatPrefixes?: string[] 158 | aliases?: string[] 159 | offset: T 160 | } 161 | 162 | export interface Definitions { 163 | prefixGroups: PrefixGroups, 164 | systems: UnitSystems, 165 | units: Record 166 | } 167 | 168 | export interface NullableDefinitions 169 | extends Omit { 170 | units: Record 171 | } 172 | 173 | export interface DefinitionsExtended { 174 | prefixGroups: PrefixGroups, 175 | systems: Record[]>, 176 | units: Record> 177 | // quantities?: Record 178 | // baseQuantities?: string[] 179 | } 180 | 181 | 182 | // TODO: Rename so Unit the type does not conflict with Unit the class? Is this interface even necessary? I don't know 183 | export interface Unit { 184 | readonly type: 'Unit' 185 | 186 | value: T | null 187 | unitList: AtomicUnit[] 188 | dimension: Record 189 | 190 | /** whether the prefix and the units are fixed */ 191 | // fixed: boolean 192 | 193 | // new (): Unit 194 | // new (str: string): Unit 195 | // new (value: V, unit: string): Unit 196 | 197 | 198 | /** 199 | * create a copy of this unit 200 | */ 201 | clone(): Unit 202 | 203 | /** 204 | * Adds two units. Both units' dimensions must be equal. 205 | * @param {Unit|string|T} other The unit to add to this one. If a string is supplied, it will be converted to a unit. 206 | * @returns {Unit} The result of adding this and the other unit. 207 | */ 208 | add(other: Unit | string | T): Unit 209 | add(value: T, unit: string): Unit 210 | 211 | 212 | /** 213 | * Subtracts two units. Both units' dimensions must be equal. 214 | * @param {Unit|string|T} other The unit to subtract from this one. If a string is supplied, it will be converted to a unit. 215 | * @returns {Unit} The result of subtract this and the other unit. 216 | */ 217 | sub(other: Unit | string | T): Unit 218 | sub(value: T, unit: string): Unit 219 | 220 | /** 221 | * Multiplies two units. 222 | * @param {Unit|string|T} other The unit to multiply to this one. 223 | * @returns {Unit} The result of multiplying this and the other unit. 224 | */ 225 | mul(other: Unit | string | T): Unit 226 | mul(value: T, unit: string): Unit 227 | 228 | /** 229 | * Divides two units. 230 | * @param {Unit|string|T} other The unit to divide this unit by. 231 | * @returns {Unit} The result of dividing this by the other unit. 232 | */ 233 | div(other: Unit | string | T): Unit 234 | div(value: T, unit: string): Unit 235 | 236 | 237 | /** 238 | * Calculate the power of a unit 239 | * @memberof Unit 240 | * @param {number|custom} p 241 | * @returns {Unit} The result: this^p 242 | */ 243 | pow(p: number): Unit 244 | 245 | /** 246 | * Takes the square root of a unit. 247 | * @memberof Unit 248 | * @returns {Unit} The square root of this unit. 249 | */ 250 | sqrt(): Unit 251 | 252 | /** 253 | * Returns the absolute value of this unit. 254 | * @memberOf Unit 255 | * @returns {Unit} The absolute value of this unit. 256 | */ 257 | abs(): Unit 258 | 259 | /** 260 | * Returns an array of units whose sum is equal to this unit, where each unit in the array is taken from the supplied string array. 261 | * @param {string[]} units A string array of units to split this unit into. 262 | * @returns {Unit[]} An array of units 263 | */ 264 | split(units: (string | Unit)[]): Unit[] 265 | 266 | /** 267 | * Convert the unit to a specific unit. 268 | * @memberof Unit 269 | * @param {string | Unit} valuelessUnit A unit without value. Can have prefix, like "cm". 270 | * @returns {Unit} Returns a clone of the unit converted to the specified unit. 271 | */ 272 | to(valuelessUnit: string | Unit): Unit 273 | 274 | /** 275 | * Fix the units and prevent them from being automatically simplified. 276 | * @memberof Unit 277 | * @returns {Unit} Returns a clone of the unit with a fixed prefix and unit. 278 | */ 279 | // fixUnits(): Unit 280 | 281 | /** 282 | * Convert the unit to base units. 283 | * @memberof Unit 284 | * @returns {Unit} Returns a clone of the unit in the base units. 285 | */ 286 | toBaseUnits(): Unit 287 | 288 | /** 289 | Get the complexity of this unit, or in other words, the number of symbols used to format the unit. 290 | @memberof Unit 291 | @returns {number} The complexity or number of symbols used to format the unit. 292 | */ 293 | getComplexity(): number 294 | 295 | /** 296 | * Returns a new unit with the given value. 297 | * @param {number | string | custom} value 298 | * @returns A new unit with the given value. 299 | */ 300 | setValue(value?: string | T | null): Unit 301 | 302 | /** 303 | * Returns this unit's value. 304 | * @returns The value of this unit. 305 | */ 306 | getValue(): T | null 307 | 308 | /** 309 | * Returns this unit's normalized value, which is the value it would have if it were to be converted to SI base units (or whatever base units are defined) 310 | * @returns The notmalized value of the unit. 311 | */ 312 | getNormalizedValue(): T | null 313 | 314 | /** 315 | * Returns a new unit with the given normalized value. 316 | * @param {number | string | custom} normalizedValue 317 | * @returns A new unit with the given normalized value. 318 | */ 319 | setNormalizedValue(normalizedValue: string | T): Unit 320 | 321 | /** 322 | * Simplify this Unit's unit list and return a new Unit with the simplified list. 323 | * The returned Unit will contain a list of the "best" units for formatting. 324 | * @returns {Unit} A simplified unit if possible, or the original unit if it could not be simplified. 325 | */ 326 | simplify(options?: SimplifyOptions & PrefixOptions): Unit 327 | 328 | /** 329 | * Choose the best prefix for the Unit. 330 | * @returns {Unit} A new unit that contains the "best" prefix, or, if no better prefix was found, returns the same unit unchanged. 331 | */ 332 | applyBestPrefix(prefixOptions?: PrefixOptions): Unit 333 | 334 | /** 335 | * Returns this unit without a value. 336 | * @memberof Unit 337 | * @returns {Unit} A new unit formed by removing the value from this unit. 338 | */ 339 | getUnits(): Unit 340 | 341 | /** 342 | * Examines this unit's unitList to determine the most likely system this unit is currently expressed in. 343 | * @returns {string | null} The system this unit is most likely expressed in, or null if no likely system was recognized. 344 | */ 345 | getInferredSystem(): string | null 346 | 347 | /** 348 | * Returns whether the unit is compound (like m/s, cm^2) or not (kg, N, hogshead) 349 | * @memberof Unit 350 | * @returns True if the unit is compound 351 | */ 352 | isCompound(): boolean 353 | 354 | 355 | /** 356 | * Return whether the given array of unit pieces is a base unit with single dimension such as kg or feet, but not m/s or N or J. 357 | * @param unitList Array of unit pieces 358 | * @returns True if the unit is base 359 | */ 360 | isBase(): boolean 361 | 362 | /** 363 | * check if this unit matches the given quantity 364 | * @memberof Unit 365 | * @param {QUANTITY | string | undefined} quantity 366 | */ 367 | // hasQuantity(quantity: any): boolean 368 | 369 | /** 370 | * Check if this unit has a dimension equal to another unit 371 | * @param {Unit} other 372 | * @return {boolean} true if equal dimensions 373 | */ 374 | equalsQuantity(other: Unit): boolean 375 | 376 | /** 377 | * Returns a string array of all the quantities that match this unit. 378 | * @return {string[]} The matching quantities, or an empty array if there are no matching quantities. 379 | */ 380 | // getQuantities(): string[] 381 | 382 | /** 383 | * Check if this unit equals another unit 384 | * @memberof Unit 385 | * @param {Unit} other 386 | * @return {boolean} true if both units are equal 387 | */ 388 | equals(other: Unit | string | T): boolean 389 | 390 | /** 391 | * Compare this unit to another and return a value indicating whether this unit is less than, greater than, or equal to the other. 392 | * @param {Unit} other 393 | * @return {number} -1 if this unit is less than, 1 if this unit is greater than, and 0 if this unit is equal to the other unit. 394 | */ 395 | compare(other: Unit | string | T): -1 | 0 | 1 396 | 397 | /** 398 | * Compare this unit to another and return whether this unit is less than the other. 399 | * @param {Unit} other 400 | * @return {boolean} true if this unit is less than the other. 401 | */ 402 | lessThan(other: Unit | string | T): boolean 403 | 404 | /** 405 | * Compare this unit to another and return whether this unit is less than or equal to the other. 406 | * @param {Unit} other 407 | * @return {boolean} true if this unit is less than or equal the other. 408 | */ 409 | lessThanOrEqual(other: Unit | string | T): boolean 410 | 411 | /** 412 | * Compare this unit to another and return whether this unit is greater than the other. 413 | * @param {Unit} other 414 | * @return {boolean} true if this unit is greater than the other. 415 | */ 416 | greaterThan(other: Unit | string | T): boolean 417 | 418 | /** 419 | * Compare this unit to another and return whether this unit is greater than or equal to the other. 420 | * @param {Unit} other 421 | * @return {boolean} true if this unit is greater than or equal the other. 422 | */ 423 | greaterThanOrEqual(other: Unit | string | T): boolean 424 | 425 | /** 426 | * Get a string representation of the Unit, with optional formatting options. Alias of `format`. 427 | * @memberof Unit 428 | * @param {Object} [opts] Formatting options. 429 | * @return {string} 430 | */ 431 | toString(formatOptions?: FormatOptions, ...userArgs: any[]): string 432 | 433 | } 434 | 435 | export interface UnitFactory { 436 | (): Unit 437 | (str: string): Unit 438 | (value: number | T | string | null, unitString?: string): Unit 439 | config(newOptions: Options): UnitFactory // Creates a new unit factory with type U 440 | getConfig(): Options 441 | definitions(): Definitions 442 | add(a: Unit | string | T, b: Unit | string | T): Unit 443 | sub(a: Unit | string | T, b: Unit | string | T): Unit 444 | mul(a: Unit | string | T, b: Unit | string | T): Unit 445 | div(a: Unit | string | T, b: Unit | string | T): Unit 446 | pow(a: Unit | string | T, b: number): Unit 447 | sqrt(a: Unit | string | T): Unit 448 | abs(a: Unit | string | T): Unit 449 | to(a: Unit | string | T, valuelessUnit: Unit | string): Unit 450 | toBaseUnits(a: Unit | string | T): Unit 451 | exists(unit: string): boolean 452 | _unitStore: UnitStore 453 | } 454 | 455 | export interface UnitStore { 456 | parser(input: string): ParsedUnit 457 | originalDefinitions: Definitions 458 | defs: DefinitionsExtended 459 | exists(name: string): boolean 460 | findUnit(unitString: string): { unit: UnitPropsExtended, prefix: string } | null 461 | } 462 | 463 | // A stripped down version of a Unit 464 | export interface ParsedUnit { 465 | type: 'Unit' 466 | unitList: AtomicUnit[] 467 | dimension: Record, // TODO: Should this be T or number? 468 | value: T | null 469 | } 470 | 471 | export type FindUnitFn = (unitString: string) => { unit: UnitPropsExtended, prefix: string } | null 472 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { TypeArithmetics, AtomicUnit } from "./types" 2 | 3 | 4 | /** 5 | * Normalize a value, based on an array of unit pieces 6 | * @private 7 | */ 8 | export function normalize(unitList: AtomicUnit[], value: T, type: TypeArithmetics): T { 9 | let unitValue, unitOffset, unitPower, unitPrefixValue 10 | 11 | if (value === null || value === undefined || unitList.length === 0) { 12 | return value 13 | } else if (isCompound(unitList)) { 14 | // units is a compound unit, so do not apply offsets. 15 | // For example, with J kg^-1 degC^-1 you would NOT want to apply the offset. 16 | let result: T = value 17 | 18 | for (let i = 0; i < unitList.length; i++) { 19 | unitValue = type.conv(unitList[i].unit.value) 20 | unitPrefixValue = type.conv(unitList[i].unit.prefixGroup[unitList[i].prefix]) 21 | unitPower = type.conv(unitList[i].power) 22 | result = type.mul(result, type.pow(type.mul(unitValue, unitPrefixValue), unitPower)) 23 | } 24 | 25 | return result 26 | } else { 27 | // units is a single unit of power 1, like kg or degC 28 | unitValue = type.conv(unitList[0].unit.value) 29 | unitOffset = type.conv(unitList[0].unit.offset) 30 | unitPrefixValue = type.conv(unitList[0].unit.prefixGroup[unitList[0].prefix]) 31 | 32 | return type.mul(type.add(type.mul(value, unitPrefixValue), unitOffset), unitValue) 33 | // (value*unitPrefixValue+unitOffset)*unitValue 34 | } 35 | } 36 | 37 | /** 38 | * Denormalize a value, based on an array of atomic units 39 | * @param unitList Array of atomic units (as in, Unit.units) 40 | * @returns denormalized value 41 | * @private 42 | */ 43 | export function denormalize(unitList: AtomicUnit[], value: T, type: TypeArithmetics): T { 44 | let unitValue, unitOffset, unitPower, unitPrefixValue 45 | 46 | if (value === null || value === undefined || unitList.length === 0) { 47 | return value 48 | } else if (isCompound(unitList)) { 49 | // unit is a compound unit, so do not apply offsets. 50 | // For example, with J kg^-1 degC^-1 you would NOT want to apply the offset. 51 | let result: T = value 52 | 53 | for (let i = 0; i < unitList.length; i++) { 54 | unitValue = type.conv(unitList[i].unit.value) 55 | unitPrefixValue = type.conv(unitList[i].unit.prefixGroup[unitList[i].prefix]) 56 | unitPower = type.conv(unitList[i].power) 57 | result = type.div(result, type.pow(type.mul(unitValue, unitPrefixValue), unitPower)) 58 | } 59 | 60 | return result 61 | } else { 62 | // unit is a single unit of power 1, like kg or degC 63 | 64 | unitValue = type.conv(unitList[0].unit.value) 65 | unitPrefixValue = type.conv(unitList[0].unit.prefixGroup[unitList[0].prefix]) 66 | unitOffset = type.conv(unitList[0].unit.offset) 67 | 68 | return type.div(type.sub(type.div(value, unitValue), unitOffset), unitPrefixValue) 69 | // (value/unitValue-unitOffset)/unitPrefixValue 70 | } 71 | } 72 | 73 | /** 74 | * Return whether the given array of unit pieces is compound (contains multiple units, such as m/s, or cm^2, but not N) 75 | * @param unitList Array of unit pieces 76 | * @returns True if the unit is compound 77 | * @private 78 | */ 79 | export function isCompound(unitList: AtomicUnit[]): boolean { 80 | if (unitList.length === 0) { 81 | return false 82 | } 83 | return unitList.length > 1 || Math.abs(unitList[0].power - 1.0) > 1e-15 84 | } 85 | 86 | /** 87 | * Return whether the given array of unit pieces is a base unit with single dimension such as kg or feet, but not m/s or N or J. 88 | * @param unitList Array of unit pieces 89 | * @returns True if the unit is base 90 | */ 91 | export function isBase(unitList: AtomicUnit[]): boolean { 92 | return unitList.length === 1 93 | && Math.abs(unitList[0].power - 1.0) < 1e-15 94 | && Object.keys(unitList[0].unit.dimension).length === 1 95 | && unitList[0].unit.dimension[Object.keys(unitList[0].unit.dimension)[0]] === 1 96 | } -------------------------------------------------------------------------------- /test/UnitMath.test-bundle.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const path = require('path') 3 | const esmRequire = require('esm')(module) 4 | 5 | /* These test ensure that the bundling is successful. 6 | * They should be run without esm or babel and the 7 | * `npm run build` must be run just before these tests. 8 | */ 9 | 10 | const createTests = (req, directory) => { 11 | const Unit = req(path.join(directory, 'UnitMath')) 12 | const UnitMin = req(path.join(directory, 'UnitMath.min')) 13 | it('should be able to import bundle', () => { 14 | assert(typeof Unit.add === 'function') 15 | }) 16 | it('minified bundle should be idential to standard bundle', () => { 17 | assert.deepStrictEqual(new Set(Object.keys(UnitMin)), new Set(Object.keys(Unit))) 18 | assert.deepStrictEqual(new Set(Object.keys(UnitMin())), new Set(Object.keys(Unit()))) 19 | }) 20 | } 21 | 22 | describe('bundling', () => { 23 | describe('umd', () => createTests(require, '../dist')) 24 | describe('umd - compatibility', () => { 25 | it('unit math should work if Object.assign is not a function', () => { 26 | let oldObjectAssign = Object.assign 27 | delete Object.assign 28 | delete require.cache[require.resolve('../dist/UnitMath')] 29 | const LegacyUnit = require('../dist/UnitMath') 30 | assert.doesNotThrow(LegacyUnit.config({})) 31 | Object.defineProperty(Object, 'assign', { 32 | value: oldObjectAssign, 33 | writable: true, 34 | enumerable: false, 35 | configurable: true 36 | }) 37 | }) 38 | }) 39 | describe('esm', () => createTests(path => esmRequire(path).default, '../es')) 40 | }) 41 | -------------------------------------------------------------------------------- /test/approx.ts: -------------------------------------------------------------------------------- 1 | export { }; 2 | 3 | declare global { 4 | namespace jest { 5 | interface Matchers { 6 | toApproximatelyEqual: (expected: any) => CustomMatcherResult; 7 | } 8 | } 9 | } 10 | // const assert = require('assert') 11 | 12 | // const EPSILON = 0.0001 13 | 14 | /** 15 | * Test whether a value is a number 16 | * @param {*} value 17 | * @returns {boolean} 18 | */ 19 | // function isNumber (value) { 20 | // return (value instanceof Number || typeof value === 'number') 21 | // } 22 | 23 | /** 24 | * Test whether two values are approximately equal. Tests whether the difference 25 | * between the two numbers is smaller than a fraction of their max value. 26 | * @param {Number | BigNumber | Complex | Fraction} a 27 | * @param {Number | BigNumber | Complex | Fraction} b 28 | * @param {Number} [epsilon] 29 | */ 30 | // exports.equal = function equal (a, b, epsilon) { 31 | // if (epsilon === undefined) { 32 | // epsilon = EPSILON 33 | // } 34 | 35 | // if (isNumber(a) && isNumber(b)) { 36 | // if (a === b) { 37 | // // great, we're done :) 38 | // } else if (isNaN(a)) { 39 | // assert.strictEqual(a.toString(), b.toString()) 40 | // } else if (a === 0) { 41 | // assert.ok(Math.abs(b) < epsilon, (a + ' ~= ' + b)) 42 | // } else if (b === 0) { 43 | // assert.ok(Math.abs(a) < epsilon, (a + ' ~= ' + b)) 44 | // } else { 45 | // const diff = Math.abs(a - b) 46 | // const max = Math.max(a, b) 47 | // const maxDiff = Math.abs(max * epsilon) 48 | // assert.ok(diff <= maxDiff, (a + ' ~= ' + b)) 49 | // } 50 | // } else { 51 | // assert.strictEqual(a, b) 52 | // } 53 | // } 54 | 55 | /** 56 | * Test whether all values in two objects or arrays are approximately equal. 57 | * Will deep compare all values of Arrays and Objects element wise. 58 | * @param {*} a 59 | * @param {*} b 60 | */ 61 | // exports.deepEqual = function deepEqual (a, b) { 62 | // let prop, i, len 63 | 64 | // if (Array.isArray(a) && Array.isArray(b)) { 65 | // assert.strictEqual(a.length, b.length, a + ' ~= ' + b) 66 | // for (i = 0, len = a.length; i < len; i++) { 67 | // deepEqual(a[i], b[i]) 68 | // } 69 | // } else if (a instanceof Object && b instanceof Object) { 70 | // for (prop in a) { 71 | // if (a.hasOwnProperty(prop)) { 72 | // assert.ok(b.hasOwnProperty(prop), a[prop] + ' ~= ' + b[prop]) 73 | // deepEqual(a[prop], b[prop]) 74 | // } 75 | // } 76 | 77 | // for (prop in b) { 78 | // if (b.hasOwnProperty(prop)) { 79 | // assert.ok(a.hasOwnProperty(prop), a[prop] + ' ~= ' + b[prop]) 80 | // deepEqual(a[prop], b[prop]) 81 | // } 82 | // } 83 | // } else { 84 | // exports.equal(a, b) 85 | // } 86 | // } 87 | 88 | 89 | function deepApproxEqual(a: any, b: any) { 90 | if (typeof a !== typeof b) return false 91 | let type = typeof a 92 | 93 | if (a === b) return true 94 | 95 | if (type === 'object') { 96 | let keys = new Set(Object.keys(a).concat(Object.keys(b))) 97 | for (let key of keys) { 98 | if (!deepApproxEqual(a[key], b[key])) { 99 | return false 100 | } 101 | } 102 | return true 103 | } 104 | 105 | if (type === 'number') { 106 | if (Math.abs(a - b) < 1e-15 || Math.abs(a - b) / (Math.abs(a)) < 1e-14) return true 107 | else return false 108 | } 109 | 110 | return false 111 | } 112 | 113 | 114 | expect.extend({ 115 | toApproximatelyEqual(received: any, expected: any) { 116 | const pass = deepApproxEqual(received, expected) 117 | const options = { 118 | comment: 'Approximate deep equality', 119 | isNot: this.isNot, 120 | promise: this.promise, 121 | } 122 | const message = pass 123 | ? () => 124 | this.utils.matcherHint('toApproximatelyEqual', undefined, undefined, options) + 125 | '\n\n' + 126 | `Expected: not ${this.utils.printExpected(expected)}\n` + 127 | `Received: ${this.utils.printReceived(received)}` 128 | : () => { 129 | 130 | return ( 131 | this.utils.matcherHint('toApproximatelyEqual', undefined, undefined, options) + 132 | '\n\n' + 133 | `Expected: ${this.utils.printExpected(expected)}\n` + 134 | `Received: ${this.utils.printReceived(received)}` 135 | ) 136 | } 137 | return { pass, message } 138 | } 139 | }) 140 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": [ "src/**/*" ], 3 | "compilerOptions": { 4 | "allowUnreachableCode": false, 5 | "strict": true, 6 | "noFallthroughCasesInSwitch": true, 7 | "noImplicitReturns": true, 8 | "noImplicitThis": true, 9 | "noImplicitAny": true, 10 | "noUnusedLocals": true, 11 | "noUnusedParameters": true, 12 | "module": "ESNext", 13 | "target": "ESNext", 14 | "rootDir": "src/", 15 | "moduleResolution": "node" 16 | } 17 | } -------------------------------------------------------------------------------- /types/BuiltIns.d.ts: -------------------------------------------------------------------------------- 1 | import { UnitProps } from "./types"; 2 | export declare const systems: { 3 | readonly si: readonly ["m", "meter", "s", "A", "kg", "K", "mol", "rad", "b", "F", "C", "S", "V", "J", "N", "Hz", "ohm", "H", "cd", "lm", "lx", "Wb", "T", "W", "Pa", "ohm", "sr"]; 4 | readonly cgs: readonly ["cm", "s", "A", "g", "K", "mol", "rad", "b", "F", "C", "S", "V", "erg", "dyn", "Hz", "ohm", "H", "cd", "lm", "lx", "Wb", "T", "Pa", "ohm", "sr"]; 5 | readonly us: readonly ["ft", "mi", "mile", "in", "inch", "s", "A", "lbm", "degF", "mol", "rad", "b", "F", "C", "S", "V", "BTU", "lbf", "Hz", "ohm", "H", "cd", "lm", "lx", "Wb", "T", "psi", "ohm", "sr", "hp"]; 6 | }; 7 | export declare const prefixes: { 8 | NONE: { 9 | '': number; 10 | }; 11 | SHORT: { 12 | '': number; 13 | da: number; 14 | h: number; 15 | k: number; 16 | M: number; 17 | G: number; 18 | T: number; 19 | P: number; 20 | E: number; 21 | Z: number; 22 | Y: number; 23 | d: number; 24 | c: number; 25 | m: number; 26 | u: number; 27 | n: number; 28 | p: number; 29 | f: number; 30 | a: number; 31 | z: number; 32 | y: number; 33 | }; 34 | LONG: { 35 | '': number; 36 | deca: number; 37 | hecto: number; 38 | kilo: number; 39 | mega: number; 40 | giga: number; 41 | tera: number; 42 | peta: number; 43 | exa: number; 44 | zetta: number; 45 | yotta: number; 46 | deci: number; 47 | centi: number; 48 | milli: number; 49 | micro: number; 50 | nano: number; 51 | pico: number; 52 | femto: number; 53 | atto: number; 54 | zepto: number; 55 | yocto: number; 56 | }; 57 | BINARY_SHORT_SI: { 58 | '': number; 59 | k: number; 60 | M: number; 61 | G: number; 62 | T: number; 63 | P: number; 64 | E: number; 65 | Z: number; 66 | Y: number; 67 | }; 68 | BINARY_SHORT_IEC: { 69 | '': number; 70 | Ki: number; 71 | Mi: number; 72 | Gi: number; 73 | Ti: number; 74 | Pi: number; 75 | Ei: number; 76 | Zi: number; 77 | Yi: number; 78 | }; 79 | BINARY_LONG_SI: { 80 | '': number; 81 | kilo: number; 82 | mega: number; 83 | giga: number; 84 | tera: number; 85 | peta: number; 86 | exa: number; 87 | zetta: number; 88 | yotta: number; 89 | }; 90 | BINARY_LONG_IEC: { 91 | '': number; 92 | kibi: number; 93 | mebi: number; 94 | gibi: number; 95 | tebi: number; 96 | pebi: number; 97 | exi: number; 98 | zebi: number; 99 | yobi: number; 100 | }; 101 | BTU: { 102 | '': number; 103 | MM: number; 104 | }; 105 | SHORT_LONG: { 106 | [s: string]: number; 107 | }; 108 | BINARY_SHORT: { 109 | [s: string]: number; 110 | }; 111 | BINARY_LONG: { 112 | [s: string]: number; 113 | }; 114 | }; 115 | export declare const units: Record; 116 | -------------------------------------------------------------------------------- /types/Parser.d.ts: -------------------------------------------------------------------------------- 1 | import { FindUnitFn, RequiredOptions, ParsedUnit } from "./types"; 2 | /** 3 | * Returns a new Parser. 4 | */ 5 | export declare function createParser(options: RequiredOptions, findUnit: FindUnitFn): (str: string) => ParsedUnit; 6 | -------------------------------------------------------------------------------- /types/Unit.d.ts: -------------------------------------------------------------------------------- 1 | import { UnitFactory } from './types'; 2 | declare const firstUnit: UnitFactory; 3 | export default firstUnit; 4 | -------------------------------------------------------------------------------- /types/UnitStore.d.ts: -------------------------------------------------------------------------------- 1 | import { RequiredOptions, UnitStore } from './types'; 2 | /** 3 | * Creates a new unit store. 4 | */ 5 | export declare function createUnitStore(options: RequiredOptions): UnitStore; 6 | -------------------------------------------------------------------------------- /types/types.d.ts: -------------------------------------------------------------------------------- 1 | export declare const symIsDefaultFun: unique symbol; 2 | export interface TypeArithmetics { 3 | conv: { 4 | (a: any): T; 5 | [symIsDefaultFun]?: boolean; 6 | }; 7 | clone: { 8 | (a: T): T; 9 | [symIsDefaultFun]?: boolean; 10 | }; 11 | abs: { 12 | (a: T): T; 13 | [symIsDefaultFun]?: boolean; 14 | }; 15 | add: { 16 | (a: T, b: T): T; 17 | [symIsDefaultFun]?: boolean; 18 | }; 19 | sub: { 20 | (a: T, b: T): T; 21 | [symIsDefaultFun]?: boolean; 22 | }; 23 | mul: { 24 | (a: T, b: T): T; 25 | [symIsDefaultFun]?: boolean; 26 | }; 27 | div: { 28 | (a: T, b: T): T; 29 | [symIsDefaultFun]?: boolean; 30 | }; 31 | pow: { 32 | (a: T, b: T): T; 33 | [symIsDefaultFun]?: boolean; 34 | }; 35 | eq: { 36 | (a: T, b: T): boolean; 37 | [symIsDefaultFun]?: boolean; 38 | }; 39 | lt: { 40 | (a: T, b: T): boolean; 41 | [symIsDefaultFun]?: boolean; 42 | }; 43 | le: { 44 | (a: T, b: T): boolean; 45 | [symIsDefaultFun]?: boolean; 46 | }; 47 | ge: { 48 | (a: T, b: T): boolean; 49 | [symIsDefaultFun]?: boolean; 50 | }; 51 | gt: { 52 | (a: T, b: T): boolean; 53 | [symIsDefaultFun]?: boolean; 54 | }; 55 | round: { 56 | (a: T): T; 57 | [symIsDefaultFun]?: boolean; 58 | }; 59 | trunc: { 60 | (a: T): T; 61 | [symIsDefaultFun]?: boolean; 62 | }; 63 | } 64 | export interface AtomicUnit { 65 | unit: UnitPropsExtended; 66 | prefix: string; 67 | power: number; 68 | } 69 | export interface FormatOptions { 70 | precision?: number; 71 | parentheses?: boolean; 72 | formatter?: { 73 | (a: T): string; 74 | [symIsDefaultFun]?: boolean; 75 | }; 76 | } 77 | export interface PrefixOptions { 78 | autoPrefix?: boolean; 79 | prefixMin?: T; 80 | prefixMax?: T; 81 | formatPrefixDefault?: 'all' | 'none'; 82 | } 83 | export interface SimplifyOptions extends PrefixOptions { 84 | system?: string; 85 | } 86 | export type RequiredOptions = Required> & Required> & { 87 | type: TypeArithmetics; 88 | definitions: Definitions & { 89 | skipBuiltIns?: boolean; 90 | }; 91 | }; 92 | export type Options = FormatOptions & SimplifyOptions & { 93 | type?: Partial>; 94 | definitions?: Partial & { 95 | skipBuiltIns?: boolean; 96 | }; 97 | }; 98 | export interface PrefixGroups { 99 | [prefixGroup: string]: Record; 100 | } 101 | export interface UnitSystems { 102 | [system: string]: string[]; 103 | } 104 | interface UnitPropsCommons { 105 | prefixGroup?: string; 106 | basePrefix?: string; 107 | formatPrefixes?: string[]; 108 | aliases?: string[]; 109 | offset?: number; 110 | } 111 | export interface UnitPropsWithQuantity extends UnitPropsCommons { 112 | quantity: string; 113 | value: number; 114 | } 115 | export interface UnitPropsStringValue extends UnitPropsCommons { 116 | quantity?: undefined; 117 | value: string; 118 | } 119 | export interface UnitPropsTupleValue extends UnitPropsCommons { 120 | quantity?: undefined; 121 | value: [number, string]; 122 | } 123 | export type UnitProps = UnitPropsStringValue | UnitPropsWithQuantity | UnitPropsTupleValue; 124 | export interface UnitPropsExtended { 125 | name: string; 126 | quantity?: string; 127 | value: T; 128 | dimension: Record; 129 | prefixGroup: Record; 130 | basePrefix?: string; 131 | formatPrefixes?: string[]; 132 | aliases?: string[]; 133 | offset: T; 134 | } 135 | export interface Definitions { 136 | prefixGroups: PrefixGroups; 137 | systems: UnitSystems; 138 | units: Record; 139 | } 140 | export interface NullableDefinitions extends Omit { 141 | units: Record; 142 | } 143 | export interface DefinitionsExtended { 144 | prefixGroups: PrefixGroups; 145 | systems: Record[]>; 146 | units: Record>; 147 | } 148 | export interface Unit { 149 | readonly type: 'Unit'; 150 | value: T | null; 151 | unitList: AtomicUnit[]; 152 | dimension: Record; 153 | /** whether the prefix and the units are fixed */ 154 | /** 155 | * create a copy of this unit 156 | */ 157 | clone(): Unit; 158 | /** 159 | * Adds two units. Both units' dimensions must be equal. 160 | * @param {Unit|string|T} other The unit to add to this one. If a string is supplied, it will be converted to a unit. 161 | * @returns {Unit} The result of adding this and the other unit. 162 | */ 163 | add(other: Unit | string | T): Unit; 164 | add(value: T, unit: string): Unit; 165 | /** 166 | * Subtracts two units. Both units' dimensions must be equal. 167 | * @param {Unit|string|T} other The unit to subtract from this one. If a string is supplied, it will be converted to a unit. 168 | * @returns {Unit} The result of subtract this and the other unit. 169 | */ 170 | sub(other: Unit | string | T): Unit; 171 | sub(value: T, unit: string): Unit; 172 | /** 173 | * Multiplies two units. 174 | * @param {Unit|string|T} other The unit to multiply to this one. 175 | * @returns {Unit} The result of multiplying this and the other unit. 176 | */ 177 | mul(other: Unit | string | T): Unit; 178 | mul(value: T, unit: string): Unit; 179 | /** 180 | * Divides two units. 181 | * @param {Unit|string|T} other The unit to divide this unit by. 182 | * @returns {Unit} The result of dividing this by the other unit. 183 | */ 184 | div(other: Unit | string | T): Unit; 185 | div(value: T, unit: string): Unit; 186 | /** 187 | * Calculate the power of a unit 188 | * @memberof Unit 189 | * @param {number|custom} p 190 | * @returns {Unit} The result: this^p 191 | */ 192 | pow(p: number): Unit; 193 | /** 194 | * Takes the square root of a unit. 195 | * @memberof Unit 196 | * @returns {Unit} The square root of this unit. 197 | */ 198 | sqrt(): Unit; 199 | /** 200 | * Returns the absolute value of this unit. 201 | * @memberOf Unit 202 | * @returns {Unit} The absolute value of this unit. 203 | */ 204 | abs(): Unit; 205 | /** 206 | * Returns an array of units whose sum is equal to this unit, where each unit in the array is taken from the supplied string array. 207 | * @param {string[]} units A string array of units to split this unit into. 208 | * @returns {Unit[]} An array of units 209 | */ 210 | split(units: (string | Unit)[]): Unit[]; 211 | /** 212 | * Convert the unit to a specific unit. 213 | * @memberof Unit 214 | * @param {string | Unit} valuelessUnit A unit without value. Can have prefix, like "cm". 215 | * @returns {Unit} Returns a clone of the unit converted to the specified unit. 216 | */ 217 | to(valuelessUnit: string | Unit): Unit; 218 | /** 219 | * Fix the units and prevent them from being automatically simplified. 220 | * @memberof Unit 221 | * @returns {Unit} Returns a clone of the unit with a fixed prefix and unit. 222 | */ 223 | /** 224 | * Convert the unit to base units. 225 | * @memberof Unit 226 | * @returns {Unit} Returns a clone of the unit in the base units. 227 | */ 228 | toBaseUnits(): Unit; 229 | /** 230 | Get the complexity of this unit, or in other words, the number of symbols used to format the unit. 231 | @memberof Unit 232 | @returns {number} The complexity or number of symbols used to format the unit. 233 | */ 234 | getComplexity(): number; 235 | /** 236 | * Returns a new unit with the given value. 237 | * @param {number | string | custom} value 238 | * @returns A new unit with the given value. 239 | */ 240 | setValue(value?: string | T | null): Unit; 241 | /** 242 | * Returns this unit's value. 243 | * @returns The value of this unit. 244 | */ 245 | getValue(): T | null; 246 | /** 247 | * Returns this unit's normalized value, which is the value it would have if it were to be converted to SI base units (or whatever base units are defined) 248 | * @returns The notmalized value of the unit. 249 | */ 250 | getNormalizedValue(): T | null; 251 | /** 252 | * Returns a new unit with the given normalized value. 253 | * @param {number | string | custom} normalizedValue 254 | * @returns A new unit with the given normalized value. 255 | */ 256 | setNormalizedValue(normalizedValue: string | T): Unit; 257 | /** 258 | * Simplify this Unit's unit list and return a new Unit with the simplified list. 259 | * The returned Unit will contain a list of the "best" units for formatting. 260 | * @returns {Unit} A simplified unit if possible, or the original unit if it could not be simplified. 261 | */ 262 | simplify(options?: SimplifyOptions & PrefixOptions): Unit; 263 | /** 264 | * Choose the best prefix for the Unit. 265 | * @returns {Unit} A new unit that contains the "best" prefix, or, if no better prefix was found, returns the same unit unchanged. 266 | */ 267 | applyBestPrefix(prefixOptions?: PrefixOptions): Unit; 268 | /** 269 | * Returns this unit without a value. 270 | * @memberof Unit 271 | * @returns {Unit} A new unit formed by removing the value from this unit. 272 | */ 273 | getUnits(): Unit; 274 | /** 275 | * Examines this unit's unitList to determine the most likely system this unit is currently expressed in. 276 | * @returns {string | null} The system this unit is most likely expressed in, or null if no likely system was recognized. 277 | */ 278 | getInferredSystem(): string | null; 279 | /** 280 | * Returns whether the unit is compound (like m/s, cm^2) or not (kg, N, hogshead) 281 | * @memberof Unit 282 | * @returns True if the unit is compound 283 | */ 284 | isCompound(): boolean; 285 | /** 286 | * Return whether the given array of unit pieces is a base unit with single dimension such as kg or feet, but not m/s or N or J. 287 | * @param unitList Array of unit pieces 288 | * @returns True if the unit is base 289 | */ 290 | isBase(): boolean; 291 | /** 292 | * check if this unit matches the given quantity 293 | * @memberof Unit 294 | * @param {QUANTITY | string | undefined} quantity 295 | */ 296 | /** 297 | * Check if this unit has a dimension equal to another unit 298 | * @param {Unit} other 299 | * @return {boolean} true if equal dimensions 300 | */ 301 | equalsQuantity(other: Unit): boolean; 302 | /** 303 | * Returns a string array of all the quantities that match this unit. 304 | * @return {string[]} The matching quantities, or an empty array if there are no matching quantities. 305 | */ 306 | /** 307 | * Check if this unit equals another unit 308 | * @memberof Unit 309 | * @param {Unit} other 310 | * @return {boolean} true if both units are equal 311 | */ 312 | equals(other: Unit | string | T): boolean; 313 | /** 314 | * Compare this unit to another and return a value indicating whether this unit is less than, greater than, or equal to the other. 315 | * @param {Unit} other 316 | * @return {number} -1 if this unit is less than, 1 if this unit is greater than, and 0 if this unit is equal to the other unit. 317 | */ 318 | compare(other: Unit | string | T): -1 | 0 | 1; 319 | /** 320 | * Compare this unit to another and return whether this unit is less than the other. 321 | * @param {Unit} other 322 | * @return {boolean} true if this unit is less than the other. 323 | */ 324 | lessThan(other: Unit | string | T): boolean; 325 | /** 326 | * Compare this unit to another and return whether this unit is less than or equal to the other. 327 | * @param {Unit} other 328 | * @return {boolean} true if this unit is less than or equal the other. 329 | */ 330 | lessThanOrEqual(other: Unit | string | T): boolean; 331 | /** 332 | * Compare this unit to another and return whether this unit is greater than the other. 333 | * @param {Unit} other 334 | * @return {boolean} true if this unit is greater than the other. 335 | */ 336 | greaterThan(other: Unit | string | T): boolean; 337 | /** 338 | * Compare this unit to another and return whether this unit is greater than or equal to the other. 339 | * @param {Unit} other 340 | * @return {boolean} true if this unit is greater than or equal the other. 341 | */ 342 | greaterThanOrEqual(other: Unit | string | T): boolean; 343 | /** 344 | * Get a string representation of the Unit, with optional formatting options. Alias of `format`. 345 | * @memberof Unit 346 | * @param {Object} [opts] Formatting options. 347 | * @return {string} 348 | */ 349 | toString(formatOptions?: FormatOptions, ...userArgs: any[]): string; 350 | } 351 | export interface UnitFactory { 352 | (): Unit; 353 | (str: string): Unit; 354 | (value: number | T | string | null, unitString?: string): Unit; 355 | config(newOptions: Options): UnitFactory; 356 | getConfig(): Options; 357 | definitions(): Definitions; 358 | add(a: Unit | string | T, b: Unit | string | T): Unit; 359 | sub(a: Unit | string | T, b: Unit | string | T): Unit; 360 | mul(a: Unit | string | T, b: Unit | string | T): Unit; 361 | div(a: Unit | string | T, b: Unit | string | T): Unit; 362 | pow(a: Unit | string | T, b: number): Unit; 363 | sqrt(a: Unit | string | T): Unit; 364 | abs(a: Unit | string | T): Unit; 365 | to(a: Unit | string | T, valuelessUnit: Unit | string): Unit; 366 | toBaseUnits(a: Unit | string | T): Unit; 367 | exists(unit: string): boolean; 368 | _unitStore: UnitStore; 369 | } 370 | export interface UnitStore { 371 | parser(input: string): ParsedUnit; 372 | originalDefinitions: Definitions; 373 | defs: DefinitionsExtended; 374 | exists(name: string): boolean; 375 | findUnit(unitString: string): { 376 | unit: UnitPropsExtended; 377 | prefix: string; 378 | } | null; 379 | } 380 | export interface ParsedUnit { 381 | type: 'Unit'; 382 | unitList: AtomicUnit[]; 383 | dimension: Record; 384 | value: T | null; 385 | } 386 | export type FindUnitFn = (unitString: string) => { 387 | unit: UnitPropsExtended; 388 | prefix: string; 389 | } | null; 390 | export {}; 391 | -------------------------------------------------------------------------------- /types/utils.d.ts: -------------------------------------------------------------------------------- 1 | import { TypeArithmetics, AtomicUnit } from "./types"; 2 | /** 3 | * Normalize a value, based on an array of unit pieces 4 | * @private 5 | */ 6 | export declare function normalize(unitList: AtomicUnit[], value: T, type: TypeArithmetics): T; 7 | /** 8 | * Denormalize a value, based on an array of atomic units 9 | * @param unitList Array of atomic units (as in, Unit.units) 10 | * @returns denormalized value 11 | * @private 12 | */ 13 | export declare function denormalize(unitList: AtomicUnit[], value: T, type: TypeArithmetics): T; 14 | /** 15 | * Return whether the given array of unit pieces is compound (contains multiple units, such as m/s, or cm^2, but not N) 16 | * @param unitList Array of unit pieces 17 | * @returns True if the unit is compound 18 | * @private 19 | */ 20 | export declare function isCompound(unitList: AtomicUnit[]): boolean; 21 | /** 22 | * Return whether the given array of unit pieces is a base unit with single dimension such as kg or feet, but not m/s or N or J. 23 | * @param unitList Array of unit pieces 24 | * @returns True if the unit is base 25 | */ 26 | export declare function isBase(unitList: AtomicUnit[]): boolean; 27 | --------------------------------------------------------------------------------