UNPKG

pqm

Version:

Physical Quantities and Measures (PQM) is a Node and browser package for dealing with numbers with units

1,225 lines (1,183 loc) 45.5 kB
'use strict'; /** * Autogenerated module containing unit definitions, do not edit. * Instead, edit src/data/unitdb.json and then generate this file * using 'node script/genunitobj.mjs' */ const dimensionTypes = [ "mass", "length", "time", "temperature", "current", "substance", "luminosity", "information", ]; const prefixes = { "y": 1e-24, "z": 1e-21, "a": 1e-18, "f": 1e-15, "p": 1e-12, "n": 1e-9, "u": 1e-6, "m": 1e-3, "c": 1e-2, "d": 1e-1, "da": 1e+1, "h": 1e+2, "k": 1e+3, "M": 1e+6, "G": 1e+9, "T": 1e+12, "P": 1e+15, "E": 1e+18, "Z": 1e+21, "Y": 1e+24, "Ki": 1.024e+3, "Mi": 1.048576e+6, "Gi": 1.073741824e+9, "Ti": 1.099511627776e+12, "Pi": 1.125899906842624e+15, "Ei": 1.152921504606847e+18, "Zi": 1.1805916207174113e+21, "Yi": 1.2089258196146292e+24, }; const units = { "1" : {"s": 1e+0, "d": [0,0,0,0,0,0,0,0,]}, "%" : {"s": 1e-2, "d": [0,0,0,0,0,0,0,0,]}, "ppm" : {"s": 1e-6, "d": [0,0,0,0,0,0,0,0,]}, "ppb" : {"s": 1e-9, "d": [0,0,0,0,0,0,0,0,]}, "ppt" : {"s": 1e-12, "d": [0,0,0,0,0,0,0,0,]}, "g" : {"s": 1e-3, "d": [1,0,0,0,0,0,0,0,]}, "Da" : {"s": 1.6605390666e-27, "d": [1,0,0,0,0,0,0,0,]}, "u" : {"s": 1.6605390666e-27, "d": [1,0,0,0,0,0,0,0,]}, "AMU" : {"s": 1.6605390666e-27, "d": [1,0,0,0,0,0,0,0,]}, "grain" : {"s": 6.479891e-5, "d": [1,0,0,0,0,0,0,0,]}, "ozm" : {"s": 2.8349523125e-2, "d": [1,0,0,0,0,0,0,0,]}, "dram" : {"s": 1.7718451953125e-3, "d": [1,0,0,0,0,0,0,0,]}, "lbm" : {"s": 4.5359237e-1, "d": [1,0,0,0,0,0,0,0,]}, "stone" : {"s": 6.35029318e+0, "d": [1,0,0,0,0,0,0,0,]}, "sg" : {"s": 1.45939029372064e+1, "d": [1,0,0,0,0,0,0,0,]}, "slug" : {"s": 1.45939029372064e+1, "d": [1,0,0,0,0,0,0,0,]}, "cwt" : {"s": 4.5359237e+1, "d": [1,0,0,0,0,0,0,0,]}, "dwt" : {"s": 1.55517384e-3, "d": [1,0,0,0,0,0,0,0,]}, "uk_cwt" : {"s": 5.080234544e+1, "d": [1,0,0,0,0,0,0,0,]}, "ton" : {"s": 9.0718474e+2, "d": [1,0,0,0,0,0,0,0,]}, "uk_ton" : {"s": 1.0160469088e+3, "d": [1,0,0,0,0,0,0,0,]}, "metric_ton" : {"s": 1e+3, "d": [1,0,0,0,0,0,0,0,]}, "tonne" : {"s": 1e+3, "d": [1,0,0,0,0,0,0,0,]}, "carat" : {"s": 2e-4, "d": [1,0,0,0,0,0,0,0,]}, "assay_ton" : {"s": 2.9166666666666667e-2, "d": [1,0,0,0,0,0,0,0,]}, "denier" : {"s": 1.1111111111111112e-7, "d": [1,-1,0,0,0,0,0,0,]}, "tex" : {"s": 1e-6, "d": [1,-1,0,0,0,0,0,0,]}, "m" : {"s": 1e+0, "d": [0,1,0,0,0,0,0,0,]}, "ang" : {"s": 1e-10, "d": [0,1,0,0,0,0,0,0,]}, "picapt" : {"s": 3.52777777777778e-4, "d": [0,1,0,0,0,0,0,0,]}, "pica" : {"s": 4.23333333333333e-3, "d": [0,1,0,0,0,0,0,0,]}, "in" : {"s": 2.54e-2, "d": [0,1,0,0,0,0,0,0,]}, "mil" : {"s": 2.54e-5, "d": [0,1,0,0,0,0,0,0,]}, "ft" : {"s": 3.048e-1, "d": [0,1,0,0,0,0,0,0,]}, "yd" : {"s": 9.144e-1, "d": [0,1,0,0,0,0,0,0,]}, "ell" : {"s": 1.143e+0, "d": [0,1,0,0,0,0,0,0,]}, "mi" : {"s": 1.609344e+3, "d": [0,1,0,0,0,0,0,0,]}, "survey_mi" : {"s": 1.6093472186944373e+3, "d": [0,1,0,0,0,0,0,0,]}, "nmi" : {"s": 1.852e+3, "d": [0,1,0,0,0,0,0,0,]}, "Nmi" : {"s": 1.852e+3, "d": [0,1,0,0,0,0,0,0,]}, "league" : {"s": 5.556e+3, "d": [0,1,0,0,0,0,0,0,]}, "ly" : {"s": 9.4607304725808e+15, "d": [0,1,0,0,0,0,0,0,]}, "parsec" : {"s": 3.08567758128155e+16, "d": [0,1,0,0,0,0,0,0,]}, "survey_ft" : {"s": 3.048006096012192e-1, "d": [0,1,0,0,0,0,0,0,]}, "AU" : {"s": 1.495978707e+11, "d": [0,1,0,0,0,0,0,0,]}, "chain" : {"s": 2.0116840233680467e+1, "d": [0,1,0,0,0,0,0,0,]}, "link" : {"s": 2.0116840233680466e-1, "d": [0,1,0,0,0,0,0,0,]}, "rod" : {"s": 5.029210058420117e+0, "d": [0,1,0,0,0,0,0,0,]}, "furlong" : {"s": 2.0116840233680466e+2, "d": [0,1,0,0,0,0,0,0,]}, "fathom" : {"s": 1.8288e+0, "d": [0,1,0,0,0,0,0,0,]}, "us_fathom" : {"s": 1.828803657607315e+0, "d": [0,1,0,0,0,0,0,0,]}, "fermi" : {"s": 1e-15, "d": [0,1,0,0,0,0,0,0,]}, "datamile" : {"s": 1.8288e+3, "d": [0,1,0,0,0,0,0,0,]}, "kayser" : {"s": 1e+2, "d": [0,-1,0,0,0,0,0,0,]}, "s" : {"s": 1e+0, "d": [0,0,1,0,0,0,0,0,]}, "sec" : {"s": 1e+0, "d": [0,0,1,0,0,0,0,0,]}, "min" : {"s": 6e+1, "d": [0,0,1,0,0,0,0,0,]}, "hr" : {"s": 3.6e+3, "d": [0,0,1,0,0,0,0,0,]}, "day" : {"s": 8.64e+4, "d": [0,0,1,0,0,0,0,0,]}, "week" : {"s": 6.048e+5, "d": [0,0,1,0,0,0,0,0,]}, "fortnight" : {"s": 1.2096e+6, "d": [0,0,1,0,0,0,0,0,]}, "yr" : {"s": 3.1536e+7, "d": [0,0,1,0,0,0,0,0,]}, "shake" : {"s": 1e-8, "d": [0,0,1,0,0,0,0,0,]}, "K" : {"s": 1e+0, "d": [0,0,0,1,0,0,0,0,]}, "deltaC" : {"s": 1e+0, "d": [0,0,0,1,0,0,0,0,]}, "degF" : {"s": 5.555555555555556e-1, "d": [0,0,0,1,0,0,0,0,], "o": 2.553722222222222e+2}, "degC" : {"s": 1e+0, "d": [0,0,0,1,0,0,0,0,], "o": 2.7315e+2}, "Ra" : {"s": 5.555555555555556e-1, "d": [0,0,0,1,0,0,0,0,]}, "Rank" : {"s": 5.555555555555556e-1, "d": [0,0,0,1,0,0,0,0,]}, "deltaF" : {"s": 5.555555555555556e-1, "d": [0,0,0,1,0,0,0,0,]}, "Reau" : {"s": 1.25e+0, "d": [0,0,0,1,0,0,0,0,], "o": 2.7315e+2}, "deltaReau" : {"s": 1.25e+0, "d": [0,0,0,1,0,0,0,0,]}, "kph" : {"s": 2.777777777777778e-1, "d": [0,1,-1,0,0,0,0,0,]}, "mph" : {"s": 4.4704e-1, "d": [0,1,-1,0,0,0,0,0,]}, "fps" : {"s": 3.048e-1, "d": [0,1,-1,0,0,0,0,0,]}, "knot" : {"s": 5.14444444444444e-1, "d": [0,1,-1,0,0,0,0,0,]}, "admkn" : {"s": 5.14773333333333e-1, "d": [0,1,-1,0,0,0,0,0,]}, "c" : {"s": 2.99792458e+8, "d": [0,1,-1,0,0,0,0,0,]}, "grav" : {"s": 9.80665e+0, "d": [0,1,-2,0,0,0,0,0,]}, "galileo" : {"s": 1e-2, "d": [0,1,-2,0,0,0,0,0,]}, "Pa" : {"s": 1e+0, "d": [1,-1,-2,0,0,0,0,0,]}, "mHg" : {"s": 1.3332239e+5, "d": [1,-1,-2,0,0,0,0,0,]}, "mH2O" : {"s": 9.80665e+3, "d": [1,-1,-2,0,0,0,0,0,]}, "Torr" : {"s": 1.33322368421053e+2, "d": [1,-1,-2,0,0,0,0,0,]}, "psi" : {"s": 6.89475729316836e+3, "d": [1,-1,-2,0,0,0,0,0,]}, "atm" : {"s": 1.01325e+5, "d": [1,-1,-2,0,0,0,0,0,]}, "bar" : {"s": 1e+5, "d": [1,-1,-2,0,0,0,0,0,]}, "inHg" : {"s": 3.3863886666667e+3, "d": [1,-1,-2,0,0,0,0,0,]}, "inH2O" : {"s": 2.4908891e+2, "d": [1,-1,-2,0,0,0,0,0,]}, "ftHg" : {"s": 4.0636664e+4, "d": [1,-1,-2,0,0,0,0,0,]}, "ftH2O" : {"s": 2.98906692e+3, "d": [1,-1,-2,0,0,0,0,0,]}, "Ba" : {"s": 1e-1, "d": [1,-1,-2,0,0,0,0,0,]}, "Pa-g" : {"s": 1e+0, "d": [1,-1,-2,0,0,0,0,0,], "o": 1.01325e+5}, "bar-g" : {"s": 1e+5, "d": [1,-1,-2,0,0,0,0,0,], "o": 1.01325e+5}, "psi-g" : {"s": 6.89475729316836e+3, "d": [1,-1,-2,0,0,0,0,0,], "o": 1.01325e+5}, "N" : {"s": 1e+0, "d": [1,1,-2,0,0,0,0,0,]}, "dyn" : {"s": 1e-5, "d": [1,1,-2,0,0,0,0,0,]}, "gf" : {"s": 9.80665e-3, "d": [1,1,-2,0,0,0,0,0,]}, "pond" : {"s": 9.80665e-3, "d": [1,1,-2,0,0,0,0,0,]}, "lbf" : {"s": 4.4482216152605e+0, "d": [1,1,-2,0,0,0,0,0,]}, "ozf" : {"s": 2.78013850953781e-1, "d": [1,1,-2,0,0,0,0,0,]}, "pdl" : {"s": 1.38254954376e-1, "d": [1,1,-2,0,0,0,0,0,]}, "ton-force" : {"s": 8.896443230521e+3, "d": [1,1,-2,0,0,0,0,0,]}, "J" : {"s": 1e+0, "d": [1,2,-2,0,0,0,0,0,]}, "eV" : {"s": 1.602176487e-19, "d": [1,2,-2,0,0,0,0,0,]}, "erg" : {"s": 1e-7, "d": [1,2,-2,0,0,0,0,0,]}, "cal" : {"s": 4.1868e+0, "d": [1,2,-2,0,0,0,0,0,]}, "Cal" : {"s": 4.1868e+3, "d": [1,2,-2,0,0,0,0,0,]}, "BTU" : {"s": 1.05505585e+3, "d": [1,2,-2,0,0,0,0,0,]}, "thm" : {"s": 1.05505585e+8, "d": [1,2,-2,0,0,0,0,0,]}, "Wh" : {"s": 3.6e+3, "d": [1,2,-2,0,0,0,0,0,]}, "HPh" : {"s": 2.68451953769617e+6, "d": [1,2,-2,0,0,0,0,0,]}, "ft-lb" : {"s": 1.3558179483314e+0, "d": [1,2,-2,0,0,0,0,0,]}, "ft-lbf" : {"s": 1.3558179483314e+0, "d": [1,2,-2,0,0,0,0,0,]}, "RSI" : {"s": 1e+0, "d": [-1,0,3,1,0,0,0,0,]}, "RIP" : {"s": 1.7611018368230189e-1, "d": [-1,0,3,1,0,0,0,0,]}, "clo" : {"s": 1.55e-1, "d": [-1,0,3,1,0,0,0,0,]}, "tog" : {"s": 1e-1, "d": [-1,0,3,1,0,0,0,0,]}, "W" : {"s": 1e+0, "d": [1,2,-3,0,0,0,0,0,]}, "PS" : {"s": 7.3549875e+2, "d": [1,2,-3,0,0,0,0,0,]}, "HP" : {"s": 7.4569987158227e+2, "d": [1,2,-3,0,0,0,0,0,]}, "P" : {"s": 1e-1, "d": [1,-1,-1,0,0,0,0,0,]}, "rhe" : {"s": 1e+1, "d": [-1,1,1,0,0,0,0,0,]}, "St" : {"s": 1e-4, "d": [0,2,-1,0,0,0,0,0,]}, "L" : {"s": 1e-3, "d": [0,3,0,0,0,0,0,0,]}, "tsp" : {"s": 4.92892159375e-6, "d": [0,3,0,0,0,0,0,0,]}, "tspm" : {"s": 5e-6, "d": [0,3,0,0,0,0,0,0,]}, "tbs" : {"s": 1.478676478125e-5, "d": [0,3,0,0,0,0,0,0,]}, "fl_oz" : {"s": 2.95735295625e-5, "d": [0,3,0,0,0,0,0,0,]}, "uk_fl_oz" : {"s": 2.84130625e-5, "d": [0,3,0,0,0,0,0,0,]}, "cup" : {"s": 2.365882365e-4, "d": [0,3,0,0,0,0,0,0,]}, "pt" : {"s": 4.73176473e-4, "d": [0,3,0,0,0,0,0,0,]}, "uk_pt" : {"s": 5.6826125e-4, "d": [0,3,0,0,0,0,0,0,]}, "qt" : {"s": 9.46352946e-4, "d": [0,3,0,0,0,0,0,0,]}, "uk_qt" : {"s": 1.1365225e-3, "d": [0,3,0,0,0,0,0,0,]}, "gal" : {"s": 3.785411784e-3, "d": [0,3,0,0,0,0,0,0,]}, "uk_gal" : {"s": 4.54609e-3, "d": [0,3,0,0,0,0,0,0,]}, "bushel" : {"s": 3.523907016688e-2, "d": [0,3,0,0,0,0,0,0,]}, "bbl" : {"s": 1.58987294928e-1, "d": [0,3,0,0,0,0,0,0,]}, "oilbarrel" : {"s": 1.58987294928e-1, "d": [0,3,0,0,0,0,0,0,]}, "beerbarrel" : {"s": 1.17347765304e-1, "d": [0,3,0,0,0,0,0,0,]}, "uk_beerbarrel" : {"s": 1.6365924e-1, "d": [0,3,0,0,0,0,0,0,]}, "MTON" : {"s": 1.13267386368e+0, "d": [0,3,0,0,0,0,0,0,]}, "GRT" : {"s": 2.8316846592e+0, "d": [0,3,0,0,0,0,0,0,]}, "gill" : {"s": 1.1829411825e-4, "d": [0,3,0,0,0,0,0,0,]}, "uk_gill" : {"s": 1.420653125e-4, "d": [0,3,0,0,0,0,0,0,]}, "peck" : {"s": 8.80976754172e-3, "d": [0,3,0,0,0,0,0,0,]}, "dry_gal" : {"s": 4.40488377086e-3, "d": [0,3,0,0,0,0,0,0,]}, "dry_qt" : {"s": 1.101220942715e-3, "d": [0,3,0,0,0,0,0,0,]}, "dry_pt" : {"s": 5.506104713575e-4, "d": [0,3,0,0,0,0,0,0,]}, "stere" : {"s": 1e+0, "d": [0,3,0,0,0,0,0,0,]}, "ar" : {"s": 1e+2, "d": [0,2,0,0,0,0,0,0,]}, "morgen" : {"s": 2.5e+3, "d": [0,2,0,0,0,0,0,0,]}, "acre" : {"s": 4.04687260987425e+3, "d": [0,2,0,0,0,0,0,0,]}, "us_acre" : {"s": 4.04687260987425e+3, "d": [0,2,0,0,0,0,0,0,]}, "uk_acre" : {"s": 4.0468564224e+3, "d": [0,2,0,0,0,0,0,0,]}, "ha" : {"s": 1e+4, "d": [0,2,0,0,0,0,0,0,]}, "barn" : {"s": 1e-28, "d": [0,2,0,0,0,0,0,0,]}, "b" : {"s": 1e+0, "d": [0,0,0,0,0,0,0,1,]}, "bit" : {"s": 1e+0, "d": [0,0,0,0,0,0,0,1,]}, "B" : {"s": 8e+0, "d": [0,0,0,0,0,0,0,1,]}, "byte" : {"s": 8e+0, "d": [0,0,0,0,0,0,0,1,]}, "word" : {"s": 1.6e+1, "d": [0,0,0,0,0,0,0,1,]}, "dword" : {"s": 3.2e+1, "d": [0,0,0,0,0,0,0,1,]}, "baud" : {"s": 1e+0, "d": [0,0,-1,0,0,0,0,1,]}, "A" : {"s": 1e+0, "d": [0,0,0,0,1,0,0,0,]}, "C" : {"s": 1e+0, "d": [0,0,1,0,1,0,0,0,]}, "Ah" : {"s": 3.6e+3, "d": [0,0,1,0,1,0,0,0,]}, "e" : {"s": 1.602176634e-19, "d": [0,0,1,0,1,0,0,0,]}, "V" : {"s": 1e+0, "d": [1,2,-3,0,-1,0,0,0,]}, "ohm" : {"s": 1e+0, "d": [1,2,-3,0,-2,0,0,0,]}, "F" : {"s": 1e+0, "d": [-1,-2,4,0,2,0,0,0,]}, "H" : {"s": 1e+0, "d": [1,2,-2,0,-2,0,0,0,]}, "S" : {"s": 1e+0, "d": [-1,-2,3,0,2,0,0,0,]}, "mho" : {"s": 1e+0, "d": [-1,-2,3,0,2,0,0,0,]}, "Wb" : {"s": 1e+0, "d": [1,2,-2,0,-1,0,0,0,]}, "Mx" : {"s": 1e-8, "d": [1,2,-2,0,-1,0,0,0,]}, "T" : {"s": 1e+0, "d": [1,0,-2,0,-1,0,0,0,]}, "Gs" : {"s": 1e-4, "d": [1,0,-2,0,-1,0,0,0,]}, "gs" : {"s": 1e-4, "d": [1,0,-2,0,-1,0,0,0,]}, "Fr" : {"s": 3.3356409519815207e-10, "d": [0,0,1,0,1,0,0,0,]}, "Gi" : {"s": 7.957747e-1, "d": [0,0,0,0,1,0,0,0,]}, "Oe" : {"s": 7.957747154594767e+1, "d": [0,-1,0,0,1,0,0,0,]}, "mol" : {"s": 1e+0, "d": [0,0,0,0,0,1,0,0,]}, "molar" : {"s": 1e+3, "d": [0,-3,0,0,0,1,0,0,]}, "M" : {"s": 1e+3, "d": [0,-3,0,0,0,1,0,0,]}, "kat" : {"s": 1e+0, "d": [0,0,-1,0,0,1,0,0,]}, "U" : {"s": 1.6666666666666667e-8, "d": [0,0,-1,0,0,1,0,0,]}, "cd" : {"s": 1e+0, "d": [0,0,0,0,0,0,1,0,]}, "lm" : {"s": 1e+0, "d": [0,0,0,0,0,0,1,0,]}, "lx" : {"s": 1e+0, "d": [0,-2,0,0,0,0,1,0,]}, "footcandle" : {"s": 1.0763910416709722e+1, "d": [0,-2,0,0,0,0,1,0,]}, "footlambert" : {"s": 3.4262590996353905e+0, "d": [0,-2,0,0,0,0,1,0,]}, "lambert" : {"s": 3.183098861837907e+3, "d": [0,-2,0,0,0,0,1,0,]}, "phot" : {"s": 1e+4, "d": [0,-2,0,0,0,0,1,0,]}, "stilb" : {"s": 1e+4, "d": [0,-2,0,0,0,0,1,0,]}, "rad" : {"s": 1e+0, "d": [0,0,0,0,0,0,0,0,]}, "sr" : {"s": 1e+0, "d": [0,0,0,0,0,0,0,0,]}, "rev" : {"s": 6.283185307179586e+0, "d": [0,0,0,0,0,0,0,0,]}, "deg" : {"s": 1.7453292519943295e-2, "d": [0,0,0,0,0,0,0,0,]}, "arcmin" : {"s": 2.908882086657216e-4, "d": [0,0,0,0,0,0,0,0,]}, "arcsec" : {"s": 4.84813681109536e-6, "d": [0,0,0,0,0,0,0,0,]}, "rpm" : {"s": 1.0471975511965977e-1, "d": [0,0,-1,0,0,0,0,0,]}, "Hz" : {"s": 6.283185307179586e+0, "d": [0,0,-1,0,0,0,0,0,]}, "Bq" : {"s": 1e+0, "d": [0,0,-1,0,0,0,0,0,]}, "Gy" : {"s": 1e+0, "d": [0,2,-2,0,0,0,0,0,]}, "Sv" : {"s": 1e+0, "d": [0,2,-2,0,0,0,0,0,]}, "R" : {"s": 2.58e-4, "d": [-1,0,1,0,1,0,0,0,]}, "RAD" : {"s": 1e-2, "d": [0,2,-2,0,0,0,0,0,]}, "rem" : {"s": 1e-2, "d": [0,2,-2,0,0,0,0,0,]}, "Ci" : {"s": 3.7e+10, "d": [0,0,-1,0,0,0,0,0,]}, }; /******************************************************************************* * Library for dealing with physical quantities (numbers with units) in with * functions for parsing units from strings, converting, adding, subtracting, * multiplying and dividing by numbers with units attached. *******************************************************************************/ // Constants const numDimensionTypes = dimensionTypes.length; /** * Class representing a physical quantity, that can be used in various * forms of arithmetic such as addition and multiplication. * * @param {number|number[]} magnitude Relative magnitude from reference unit * @param {number[]} dimensions Base dimensions of the unit * @param {number} offsets Base offsets from nominal of the unit (temperature * scales only valid for units that do not have compound * dimensions */ function Quantity(magnitude, dimensions, offset) { // Fill in member values let magInputLength; if (magnitude instanceof Array) { magInputLength = magnitude.length; this.isScalar = false; } else { magInputLength = 1; magnitude = [magnitude]; this.isScalar = true; } this.magnitude = new Array(magInputLength); for (let ii=0; ii<magInputLength; ii++) { this.magnitude[ii] = magnitude[ii]; } //this.dimensions = new Array(numDimensionTypes); if (dimensions) { this.dimensions = dimensions; } else { this.dimensions = new Array(numDimensionTypes); for (let dimIdx=0; dimIdx<numDimensionTypes; dimIdx++) { this.dimensions[dimIdx] = 0; } } // Finally, set the offset if (offset) { this.offset = offset; } else { this.offset = 0; } } /** * Get the input value as a quantity, if the input is a number, array or * is already a quantity; * * */ Quantity.toQuantity = function(value) { if (value instanceof Quantity) { return value; } else { return new Quantity(value); } }; /** * Get the dimensionality of the quantity (total number of dimensions of * all types) * * @returns {number} Dimensionality of the physical quantity */ Quantity.prototype.dimensionality = function() { let total = 0; for (let ii=0; ii<numDimensionTypes; ii++) { if (this.dimensions[ii] != 0) { total += Math.abs(this.dimensions[ii]); } } return total; }; /** * Make a copy of the this unit's dimensions * * @returns {Array} Copy of this quantity's dimensions array */ Quantity.prototype.copyDimensions = function() { let dimCopy = new Array(numDimensionTypes); for (let ii=0; ii<numDimensionTypes; ii++) { dimCopy[ii] = this.dimensions[ii]; } return dimCopy; }; /** * Make a copy of this unit's magnitude * * @returns {Array} Copy of this quantity's magnitude array */ Quantity.prototype.copyMagnitude = function() { let magCopy = new Array(numDimensionTypes); for (let ii=0; ii<numDimensionTypes; ii++) { magCopy[ii] = this.magnitude[ii]; } return magCopy; }; /** * Make a copy of this physical quantity * * @returns {Quantity} Copy of the Quantity */ Quantity.prototype.copy = function() { return new Quantity(this.copyMagnitude(), this.copyDimensions(), this.offset); }; /** * Check to see if the units of a supplied physical quantity are the same as * this one * * @param {Quantity|number} other Other quantity to check for same dimensions * @return {boolean} True if same dimensions, false if not */ Quantity.prototype.sameDimensions = function(other) { // Convert to a quantity if a number is supplied as input other = Quantity.toQuantity(other); for (let ii=0; ii<numDimensionTypes; ii++) { if (this.dimensions[ii] != other.dimensions[ii]) { return false; } } return true; }; /** * Add physical quantities together * * @param {Quantity|number} other Value to add as a physical quantity, must * not have an offset. * * @return {Quantity} Added value */ Quantity.prototype.add = function(other) { // Check user input other = Quantity.toQuantity(other); if (!this.sameDimensions(other)) { throw "Cannot add units that are not alike"; } if (other.offset != 0) { throw ("A unit with a zero offset (such as degC or degF) cannot be " + "added to another unit"); } // Adding a value treats the second input value as a delta, in the case of // units with offsets let newMagnitude = arrayAdd( this.magnitude, other.magnitude, (this.isScalar && other.isScalar) ); return new Quantity(newMagnitude, this.copyDimensions(), this.offset); }; /** * Subtract physical quantities * * @param {Quantity} other Value to subtract * @return {Quantity} Result of the subtraction */ Quantity.prototype.sub = function(other) { // Check user input other = Quantity.toQuantity(other); if (!this.sameDimensions(other)) { throw "Cannot subtract units that are not alike"; } let newMagnitude = arraySub( arrayAdd(this.magnitude, [this.offset], false), arrayAdd(other.magnitude, [other.offset], false), false ); let newOffset = 0; if (other.offset != 0) { // Subtracting a unit with an zero offset, result should be a 'delta' // Unit with no offset newOffset = 0; } else { // Subtracting a unit with no zero offset, this unit's offset is // preserved newOffset = this.offset; } // Same as addition, treat the second unit as a delta if has an offset newMagnitude = arraySub( newMagnitude, [newOffset], (this.isScalar && other.isScalar) ); return new Quantity(newMagnitude, this.copyDimensions(), newOffset); }; /** * Multiply a physical quantity by a scalar or another physical quantity. * * @param {number|Quantity} other Value to multiply the physical quantity by * @return {Quantity} New Quantity object representing the new value */ Quantity.prototype.mul = function(other) { // Check user input other = Quantity.toQuantity(other); // Check if the offsets are compatible if (this.offset != 0 && other.offset != 0) { throw ("Cannot multiply two dimensions with a zero offset, if using " + "temperatures consider using 'deltaC' or 'deltaF' instead"); } if (this.offset !=0 && other.dimensionality() != 0) { throw ("Can only multiply quantities with an offset by a dimensionless " + "quantity"); } // Multiply the magnitude let newMagnitude = arrayMul( this.magnitude, other.magnitude, (this.isScalar && other.isScalar) ); let newDimensions = new Array(numDimensionTypes); for (let ii=0; ii<numDimensionTypes; ii++) { newDimensions[ii] = this.dimensions[ii] + other.dimensions[ii]; } return new Quantity(newMagnitude, newDimensions, this.offset); }; /** * Invert a physical quantity as 1/(old_quantity). this allows division by * using the multiplication function. this operation also loses offset * information. * * @return {Quantity} Inverted physical quantity */ Quantity.prototype.inv = function() { if (this.offset != 0) { throw ("Cannot invert dimensions with an offset, if using " + "temperatures consider using 'deltaC' or 'deltaF' instead"); } let newMagnitude = arrayDiv( [1.0], this.magnitude, this.isScalar ); let newDimensions = this.copyDimensions(); for (let ii=0; ii<numDimensionTypes; ii++) { newDimensions[ii] = -newDimensions[ii]; } return (new Quantity(newMagnitude, newDimensions)); }; /** * Division, same as the multiplication by the inverse. this operation loses * all offset information. * * @param {Quantity|number} other Value to divide by * @return {Quantity} New value that is the result of the division. */ Quantity.prototype.div = function(other) { // Check user input other = Quantity.toQuantity(other); if (this.offset != 0 && other.offset != 0) { throw ("Cannot divide dimensions with an offset, if using " + "temperatures consider using 'deltaC' or 'deltaF' instead"); } let inverseValue = other.inv(); return this.mul(inverseValue); }; /** * Raise the unit to the provided power * * @param {number} n Integer power to raise the physical quantity to * @returns {Quantity} Physical quantity raised to provided power */ Quantity.prototype.pow = function(n) { // Check user input if (!(typeof(n) == "number")) { throw "Input to pow must be a number"; } if (!Number.isInteger(n)) { throw "Quantities don't support dimensions with fractional powers"; } if (this.offset != 0 && n > 1) { throw "Cannot raise units with zero offsets to powers > 1"; } if (n == 0) { return new Quantity(1); } let newMagnitude = arrayPow( this.magnitude, [n], this.isScalar ); let newDimensions = this.copyDimensions(); for (let ii=0; ii<numDimensionTypes; ii++) { newDimensions[ii] *= n; } return new Quantity(newMagnitude, newDimensions, this.offset); }; /** * Get the Nth root of a quantity, equivalent to x^(1/N) * * @param {number} n Integer root to take of the quantity * @returns {Quantity} nth root of the calling quantity */ Quantity.prototype.root = function(n) { // Check user input if (!(typeof(n) == "number")) { throw "Input to root must be a number"; } if (!Number.isInteger(n) || (n < 1)) { throw "Root may only be a positive integer greater than or equal to 1"; } // Check that quantity does not have a negative magnitude if (this.magnitude < 0) { throw ("Root function not supported for magnitudes with negative " + "magnitudes"); } let newDimensions = this.copyDimensions(); for (let ii=0; ii<numDimensionTypes; ii++) { let update = newDimensions[ii] / n; if (!Number.isInteger(update)) { throw ("Root operation would result in a fractional dimensional " + "power. This is not supported"); } newDimensions[ii] = update; } let newMagnitude = arrayPow( this.magnitude, [1/n], this.isScalar ); // Return the new quantity return new Quantity(newMagnitude, newDimensions, this.offset); }; /** * Compare physical quantity to another and return their relative magnitudes * * @param {Quantity|number} other Other quantity to compare to * @param {Quantity|number} tolerance Maximum difference between the two * quantities that is still considered * equal. Can be provided as an absolute * quantity, or as a fraction of this * quantity. default=0 * @param {boolean} preventCollapse Providing this parameter will prevent this * function from collapsing an array into a * scalar if possible. * * @return {number} Number indicating the result of the comparison. * -1: other is greater than this quantity * 0: other is equal to this quantity within * the provided tolerance * 1: other is less than this quantity */ Quantity.prototype.compare = function(other, tolerance, preventCollapse) { // Convert to a quantity if a number is supplied as input other = Quantity.toQuantity(other); // Only quantities with the same units can be compared if (!this.sameDimensions(other)) { throw "Cannot compare quantities with different dimensions"; } // default value for tolerance if (typeof(tolerance) === "undefined") { tolerance = 0; } let absoluteTolerance = 0; // If the user input is a quantity, check that the units are compatible // and if they are the magnitude of the tolerance will be the absolute // tolerance if (typeof(tolerance) === "object") { if(!this.sameDimensions(tolerance)) { throw "tolerance dimensions are not compatible with this quantity"; } if (tolerance.offset != 0) { throw "Absolute tolerance in units with a zero offset is not allowed"; } absoluteTolerance = tolerance.magnitude; } else { if (this.offset != 0 && tolerance != 0) { throw "Fractional tolerances not allowed for quantities with a " + "zero offset. Use an absolute tolerance instead"; } absoluteTolerance = this.magnitude * tolerance; } let doCollapse; if (preventCollapse) { doCollapse = false; } else { doCollapse = (this.isScalar && other.isScalar); } // Do the comparison and return the result let thisMag = arrayAdd(this.magnitude, [this.offset]); let otherMag = arrayAdd(other.magnitude, [other.offset]); return arrayOp( thisMag, otherMag, doCollapse, function(a, b) { if (b - a < -absoluteTolerance) { return 1; } else if (b - a > absoluteTolerance) { return -1; } else { return 0; } } ); }; /** * Check for equality with another quantity * * @param {Quantity|number} other Other quantity to check for equality with * @param {Quantity|number} tolerance Maximum difference between the two * quantities that is still considered * equal. Can be provided as an absolute * quantity, or as a fraction of this * quantity. default=0 * * @return {boolean} Returns true if quantities are equal, false if not */ Quantity.prototype.eq = function(other, tolerance) { return arrayOp( this.compare(other, tolerance, true), [0], (this.isScalar && other.isScalar), function(a, b) { return a == b; } ); }; /** * Check if this quantity is less than another quantity * * @param {Quantity|number} other Other quantity to check against * @param {Quantity|number} tolerance Maximum difference between the two * quantities that is still considered * equal. Can be provided as an absolute * quantity, or as a fraction of this * quantity. default=0 * * @return {boolean} Returns true if the other quantity is less than this * quantity. */ Quantity.prototype.lt = function(other, tolerance) { return arrayOp( this.compare(other, tolerance, true), [0], (this.isScalar, other.isScalar), function(a, b) { return a < b; } ); }; /** * Check if this quantity is less than or equal to another quantity * * @param {Quantity|number} other Other quantity to check against * @param {Quantity|number} tolerance Maximum difference between the two * quantities that is still considered * equal. Can be provided as an absolute * quantity, or as a fraction of this * quantity. default=0 * * @return {boolean} Returns true if the other quantity is less than or equal * to this quantity. */ Quantity.prototype.lte = function(other, tolerance) { return arrayOp( this.compare(other, tolerance, true), [0], (this.isScalar && other.isScalar), function(a, b) { return a <= b; } ); }; /** * Check if this quantity is greater than another quantity * * @param {Quantity|number} other Other quantity to check against * @param {Quantity|number} tolerance Maximum difference between the two * quantities that is still considered * equal. Can be provided as an absolute * quantity, or as a fraction of this * quantity. default=0 * * @returns {boolean} Returns true if the other quantity is greater than * this quantity. */ Quantity.prototype.gt = function(other, tolerance) { return arrayOp( this.compare(other, tolerance, true), [0], (this.isScalar && other.isScalar), function(a, b) { return a > b; } ); }; /** * Check if this quantity is greater than or equal to another quantity * * @param {Quantity|number} other Other quantity to check against * @param {Quantity|number} tolerance Maximum difference between the two * quantities that is still considered * equal. Can be provided as an absolute * quantity, or as a fraction of this * quantity. default=0 * * @returns {boolean} Returns true if the other quantity is greater than or * equal to this quantity. */ Quantity.prototype.gte = function(other, tolerance) { return arrayOp( this.compare(other, tolerance, true), [0], (this.isScalar && other.isScalar), function(a, b) { return a >= b; } ); }; /** * Get the magnitude of the physical quantity with the supplied unit * * @param {string} unitString Unit to get the magnitude of the Quantity in * * @return {number} Magnitude of the quantity in the new unit */ Quantity.prototype.in = function(unitString) { let convertQuantity = quantity(1, unitString); // Check for consistent units if (!this.sameDimensions(convertQuantity)) { throw "Cannot convert units that are not alike"; } // Get the current magnitude without the offset let currentMagnitude = arrayAdd(this.magnitude, [this.offset], false); // Subtract off the offset of the new unit let newMagnitude = arraySub(currentMagnitude, [convertQuantity.offset], false); // Finally, divide by the magnitude of the new unit newMagnitude = arrayDiv(newMagnitude, convertQuantity.magnitude, this.isScalar); return newMagnitude; }; /** * Get the value of the quantity in terms of a compact combination of the * supplied unit list * * @param {string[]} unitList List of units to return the quantity in terms of * * @returns {[number, string]} Array with magnitude and the units the * magnitude is in terms of as a string. */ Quantity.prototype.with = function(unitList) { // Convert unitList to a dimension Array let unitArray = new Array(unitList.length); for (let ii = 0; ii<unitList.length; ii++) { let unitQuantity = getUnitQuantity(unitList[ii]); unitArray[ii] = unitQuantity.dimensions; } // Loop through each dimension and create a list of unit list indexes that // are the best match for the dimension let useUnits = new Array(); let useUnitsPower = new Array(); let remainder = this.dimensionality(); let remainderArray = this.dimensions.slice(); while (remainder > 0) { let bestIdx = -1; let bestInv = 0; let bestRemainder = remainder; let bestRemainderArray = new Array(dimensionTypes.length); for (let unitIdx=0; unitIdx<unitList.length; unitIdx++) { for (let isInv=-1; isInv<=1; isInv += 2) { let newRemainder = 0; let newRemainderArray = new Array(dimensionTypes.length); for (let dimIdx=0; dimIdx<dimensionTypes.length; dimIdx++) { newRemainderArray[dimIdx] = ( remainderArray[dimIdx] - (isInv * unitArray[unitIdx][dimIdx]) ); newRemainder += Math.abs(newRemainderArray[dimIdx]); } if (newRemainder < bestRemainder) { bestIdx = unitIdx; bestInv = isInv; bestRemainder = newRemainder; bestRemainderArray = newRemainderArray; } } } // Check to make sure that progress is being made towards remainder = 0 // if no more progress is being made then the provided units don't span // this unit, throw an error. if (bestRemainder >= remainder) { throw "Cannot represent this quantity with the supplied units"; } // Check if the new best unit already in the set of numerator or // denominator units. If it is, increase the power of that unit, if it // is not, then add it. let existingIdx = useUnits.indexOf(bestIdx); if (existingIdx == -1) { useUnits.push(bestIdx); useUnitsPower.push(bestInv); } else { useUnitsPower[existingIdx] += bestInv; } remainder = bestRemainder; remainderArray = bestRemainderArray; } // At this point the units to be used are in useUnits, clean // them up and create a unit system to return to the caller. let numerator = ""; let denominator = ""; for (let ii=0; ii<useUnits.length; ii++) { if (useUnitsPower[ii] > 0) { numerator += unitList[useUnits[ii]]; if (useUnitsPower[ii] > 1) { numerator += ("^" + useUnitsPower[ii] + " "); } else { numerator += " "; } } else { denominator += unitList[useUnits[ii]]; if (useUnitsPower[ii] < -1) { denominator += ("^" + -useUnitsPower[ii] + " "); } else { denominator += " "; } } } let fullUnits = ""; if (numerator.length == 0 && denominator.length == 0) { fullUnits = "1"; } else if (denominator.length == 0) { fullUnits = numerator.trim(); } else { if (numerator.length == 0) { numerator = "1 "; } fullUnits = (numerator + "/ " + denominator).trim(); } return [this.in(fullUnits), fullUnits]; }; /** * Look for the most compact SI representation for the quantity and return * it. This function is capable of representing any unit. * * @returns {[number, string]} Index 0: Magnitude of the quantity * Index 1: String representation of the units */ Quantity.prototype.inSI = function() { return this.with([ "[k]g", "m", "s", "K", "A", "mol", "cd", "bit", //Base Units "Hz", "N", "Pa", "J", "W", "C", "V", "F", "ohm", "S", "Wb", "T", "H", "lm", "lx", "Bq", "Gy" // Derived Units ]); }; /** * Look for the most compact CGS representation for the quantity and return * it. This function is not able to represent all quantities. * * @returns {[number, string]} Index 0: Magnitude of the quantity * Index 1: String representation of the units */ Quantity.prototype.inCGS = function() { return this.with([ "g", "[c]m", "s", "deltaC", "dyn", "erg", "Ba", "P", "St", "cd", "bit" ]); }; /** * Look for the most compact US Customary representation for the quantity * and return it. This function is not able to represent all quantities. * * @returns {[number, string]} Index 0: Magnitude of the quantity * Index 1: String representation of the units */ Quantity.prototype.inUS = function() { return this.with([ "lbm", "ft", "s", "Ra", "gal", "lbf", "BTU", "HP", "cd", "bit" ]); }; /** * Display the Quantity as a string * * @param {string} unitStr Optional unit string to use to represent the unit * in * * @returns {string} Quantity displayed as a string */ Quantity.prototype.toString = function(unitStr) { let outUnitStr; let outMag; if (unitStr) { outUnitStr = unitStr; outMag = this.in(unitStr); } else { let arrSI = this.inSI(); outUnitStr = arrSI[1]; outMag = arrSI[0]; } let outMagStr; if (outMag instanceof Array) { outMagStr = "[" + outMag.toString() + "]"; } else { outMagStr = outMag.toString(); } return outMagStr + " " + outUnitStr; }; /** * Do the provided function operation as a vector operation on provided arrays * arr1 and arr2 * * @param {number[]} arr1 First vector to combine using op * @param {number[]} arr2 Second vector to combine using op * @param {boolean} collapse If this is true and the return value is a length * one array. Return the 0 element, not the array * @param {function} op Function to combine the two arrays. Must have the * signature op(number, number) -> number */ function arrayOp(arr1, arr2, collapse, op) { let maxLength = Math.max(arr1.length, arr2.length); let output = new Array(maxLength); // Equal length vectors if (arr1.length == arr2.length) { for (let ii=0; ii<maxLength; ii++) { output[ii] = op(arr1[ii], arr2[ii]); } // arr1 is scalar and arr2 is an array } else if (arr1.length == 1) { for (let ii=0; ii<maxLength; ii++) { output[ii] = op(arr1[0], arr2[ii]); } // arr2 is scalar and arr1 is an array } else if (arr2.length == 1) { for (let ii=0; ii<maxLength; ii++) { output[ii] = op(arr1[ii], arr2[0]); } } else { throw ("Vector operations arguments must have the same length or at " + "least one must be scalar (length 1)"); } if (collapse && (output.length == 1)) { return output[0]; } return output; } // Add as an array function arrayAdd(a, b, collapse) { return arrayOp(a, b, collapse, function(c, d) { return c + d; }); } // Subtract as an array function arraySub(a, b, collapse) { return arrayOp(a, b, collapse, function(c, d) { return c - d; }); } // Multiply as an array function arrayMul(a, b, collapse) { return arrayOp(a, b, collapse, function(c, d) { return c * d; }); } // Divide as an array function arrayDiv(a, b, collapse) { return arrayOp(a, b, collapse, function(c, d) { return c / d; }); } // Power as an array // Divide as an array function arrayPow(a, b, collapse) { return arrayOp(a, b, collapse, function(c, d) { return Math.pow(c, d); }); } /** * Convert the provided unit string to a quantity or throw and error if * it does not exist. * * @param {string} unitName Name of the unit as a string. May have an * optional prefix or raised power (e.g. [c]m^2) * * @returns {Quantity} Quantity represented by the unit string */ function getUnitQuantity(unitName) { // The variable unit parts is [prefix, unit, exponent] let unitParts = ["", "", ""]; let appendIdx = 1; for (let ii=0; ii<unitName.length; ii++) { if (unitName[ii] == "[") { appendIdx = 0; continue; } else if (unitName[ii] == "]") { appendIdx = 1; continue; } else if (unitName[ii] == "^") { appendIdx = 2; continue; } unitParts[appendIdx] += unitName[ii]; } // Try to find the specified parts of the unit // Explicit Prefix let prefixValue; if (unitParts[0]) { prefixValue = prefixes[unitParts[0]]; if (!prefixValue) { throw unitParts[0] + " is not a valid prefix"; } } else { prefixValue = 1; } // Unit let unitSymbol; if (!unitParts[1]) { throw "Error parsing unit: \"" + unitName + "\""; } // Easiest case, unit exists and is ready to use if (units.hasOwnProperty(unitParts[1])) { unitSymbol = unitParts[1]; // Check to see if the unit is a length 1 prefix and unit combined } else if ( (prefixValue == 1) && units.hasOwnProperty(unitParts[1].slice(1)) && prefixes.hasOwnProperty(unitParts[1].slice(0,1))) { prefixValue = prefixes[unitParts[1].slice(0,1)]; unitSymbol = unitParts[1].slice(1); // Check to see if the unit is a length 2 prefix and unit combined } else if ( (prefixValue == 1) && units.hasOwnProperty(unitParts[1].slice(2)) && prefixes.hasOwnProperty([unitParts[1].slice(0,2)])) { prefixValue = prefixes[unitParts[1].slice(0,2)]; unitSymbol = unitParts[1].slice(2); } else { throw (unitParts[1] + " is not a valid unit"); } let unitStructure = units[unitSymbol]; let scale = unitStructure.s; let dims = new Array(numDimensionTypes); for (let ii=0; ii<numDimensionTypes; ii++) { dims[ii] = unitStructure.d[ii]; } let offset; if (unitStructure.hasOwnProperty("o")) { offset = unitStructure.o; } else { offset = 0; } let unitQuantity = new Quantity(scale, dims, offset); // Exponent // Get the power of the unit and invert it if in section 1 (si==1) let powerValue; if (unitParts[2]) { powerValue = parseInt(unitParts[2]); if (!powerValue) { throw power + " is not a valid unit power"; } } else { powerValue = 1; } // Put together the parts and return unitQuantity = unitQuantity.mul(prefixValue); unitQuantity = unitQuantity.pow(powerValue); return unitQuantity; } /** * Parse a string to get it's representation as a physical quantity. * * @param {Number} magnitude Magnitude of the quantity to return * @param {string} unitString String representation of the desired unit. This * can be compound (e.g. "ft lb / s"), can include * powers with "^" (e.g. "in^2 / s^2"), and can also * include standard prefixes using brackets "[]" * (e.g. "[k]g / [m]m"). It cannot include * parenthesis "()", any values the follow a "/" * will be inverted in the returned unit (e.g. * "1 / s m" == "s^-1 m^-1") * @return {Quantity} The unit of measurement as */ function quantity(magnitude, unitString) { let returnQuantity = new Quantity(1); if (unitString) { let sections = unitString.split("/"); if (sections.length > 2) { throw "Cannot parse unit with 2 or more '/' symbols"; } for (let si=0; si<sections.length; si++) { let unitSyms = sections[si].trim().split(/\s+/g); for (let ui=0; ui<unitSyms.length; ui++) { let unitQuantity = getUnitQuantity(unitSyms[ui]); if (si > 0) { unitQuantity = unitQuantity.inv(); } // Multiply normally if the unit does not have an offset or if the unit // is non-compound if (unitQuantity.offset == 0) { returnQuantity = returnQuantity.mul(unitQuantity); } else if (sections.length == 1 && unitSyms.length == 1) { returnQuantity = unitQuantity; } else { throw "Cannot create compound units from units with zero offsets"; } } } } return returnQuantity.mul(magnitude); } /** * Define an arbitrary unit symbol that can be used in calculations * * @param {string} symbol Symbol that is used to represent the unit * @param {number} magnitude Magnitude of the new unit relative to the * provided unitStr (default=1) * @param {string} unitStr String defining the base units to base the new * unit off of (default="1" non-dimensional scalar) * @param {number} offset Zero offset of the new unit, scaled by itself * e.g. degF has an offset of 459.67 not 255.37 * (default = 0) */ function define(symbol, magnitude, unitStr, offset) { // Check user input if (units.hasOwnProperty(symbol)) { throw "The unit " + symbol + " is already defined"; } if (typeof(magnitude) === "undefined") { magnitude = 1; } // Create new unit and it to the global list of units let newQuantity = quantity(magnitude, unitStr); let newDimensions = new Array(numDimensionTypes); for (let ii=0; ii<numDimensionTypes; ii++) { newDimensions[ii] = newQuantity.dimensions[ii]; } units[symbol] = { s: newQuantity.magnitude, d: newDimensions, }; if (offset) { // Offset must be scaled to nominal (SI) units from user input offset = (offset / newQuantity.magnitude); units[symbol].o = offset; } } var pqm = { quantity: quantity, define: define, }; module.exports = pqm;