@danielsimonjr/mathts-core 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -10
- package/dist/index.d.ts +362 -2
- package/dist/index.js +861 -44
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -757,10 +757,7 @@ var Fraction = class _Fraction {
|
|
|
757
757
|
*/
|
|
758
758
|
multiply(other) {
|
|
759
759
|
if (other instanceof _Fraction) {
|
|
760
|
-
return new _Fraction(
|
|
761
|
-
this.numerator * other.numerator,
|
|
762
|
-
this.denominator * other.denominator
|
|
763
|
-
);
|
|
760
|
+
return new _Fraction(this.numerator * other.numerator, this.denominator * other.denominator);
|
|
764
761
|
}
|
|
765
762
|
return this.multiply(_Fraction.fromNumber(Number(other.valueOf())));
|
|
766
763
|
}
|
|
@@ -772,10 +769,7 @@ var Fraction = class _Fraction {
|
|
|
772
769
|
if (other.numerator === 0n) {
|
|
773
770
|
throw new Error("Division by zero");
|
|
774
771
|
}
|
|
775
|
-
return new _Fraction(
|
|
776
|
-
this.numerator * other.denominator,
|
|
777
|
-
this.denominator * other.numerator
|
|
778
|
-
);
|
|
772
|
+
return new _Fraction(this.numerator * other.denominator, this.denominator * other.numerator);
|
|
779
773
|
}
|
|
780
774
|
return this.divide(_Fraction.fromNumber(Number(other.valueOf())));
|
|
781
775
|
}
|
|
@@ -789,10 +783,7 @@ var Fraction = class _Fraction {
|
|
|
789
783
|
* Absolute value: |a/b|
|
|
790
784
|
*/
|
|
791
785
|
abs() {
|
|
792
|
-
return new _Fraction(
|
|
793
|
-
this.numerator < 0n ? -this.numerator : this.numerator,
|
|
794
|
-
this.denominator
|
|
795
|
-
);
|
|
786
|
+
return new _Fraction(this.numerator < 0n ? -this.numerator : this.numerator, this.denominator);
|
|
796
787
|
}
|
|
797
788
|
/**
|
|
798
789
|
* Reciprocal: b/a
|
|
@@ -1019,10 +1010,7 @@ var Fraction = class _Fraction {
|
|
|
1019
1010
|
* Used in Stern-Brocot tree and Farey sequences
|
|
1020
1011
|
*/
|
|
1021
1012
|
mediant(other) {
|
|
1022
|
-
return new _Fraction(
|
|
1023
|
-
this.numerator + other.numerator,
|
|
1024
|
-
this.denominator + other.denominator
|
|
1025
|
-
);
|
|
1013
|
+
return new _Fraction(this.numerator + other.numerator, this.denominator + other.denominator);
|
|
1026
1014
|
}
|
|
1027
1015
|
};
|
|
1028
1016
|
var FRACTION_ZERO = new Fraction(0n, 1n);
|
|
@@ -1045,6 +1033,12 @@ var defaultConfig = {
|
|
|
1045
1033
|
var globalConfig = { ...defaultConfig };
|
|
1046
1034
|
var BigNumber = class _BigNumber {
|
|
1047
1035
|
type = "BigNumber";
|
|
1036
|
+
/**
|
|
1037
|
+
* Duck-typing marker for Decimal.js / mathjs formatter compatibility.
|
|
1038
|
+
* Allows external code that checks `obj.isBigNumber === true` to identify
|
|
1039
|
+
* this class without an `instanceof` check.
|
|
1040
|
+
*/
|
|
1041
|
+
isBigNumber = true;
|
|
1048
1042
|
// Internal representation: sign * coefficient * 10^exponent
|
|
1049
1043
|
_sign;
|
|
1050
1044
|
_coefficient;
|
|
@@ -1218,6 +1212,14 @@ var BigNumber = class _BigNumber {
|
|
|
1218
1212
|
const str = this.toString();
|
|
1219
1213
|
return parseFloat(str);
|
|
1220
1214
|
}
|
|
1215
|
+
/**
|
|
1216
|
+
* Convert to a JS number.
|
|
1217
|
+
* Alias for `valueOf()` — provided for Decimal.js / mathjs formatter
|
|
1218
|
+
* duck-typing compatibility.
|
|
1219
|
+
*/
|
|
1220
|
+
toNumber() {
|
|
1221
|
+
return this.valueOf();
|
|
1222
|
+
}
|
|
1221
1223
|
toString() {
|
|
1222
1224
|
if (this._isNaN) return "NaN";
|
|
1223
1225
|
if (this._isInfinite) return this._sign === 1 ? "Infinity" : "-Infinity";
|
|
@@ -1240,35 +1242,55 @@ var BigNumber = class _BigNumber {
|
|
|
1240
1242
|
};
|
|
1241
1243
|
}
|
|
1242
1244
|
/**
|
|
1243
|
-
* Convert to fixed-point notation
|
|
1245
|
+
* Convert to fixed-point notation.
|
|
1246
|
+
*
|
|
1247
|
+
* When called with no argument (or `undefined`) returns the full fixed-point
|
|
1248
|
+
* string representation without rounding — matching Decimal.js semantics where
|
|
1249
|
+
* `toFixed()` is equivalent to a lossless fixed-point stringify.
|
|
1250
|
+
* When called with a non-negative integer `decimalPlaces`, rounds and formats
|
|
1251
|
+
* to exactly that many digits after the decimal point.
|
|
1244
1252
|
*/
|
|
1245
|
-
toFixed(decimalPlaces
|
|
1253
|
+
toFixed(decimalPlaces) {
|
|
1254
|
+
if (decimalPlaces === void 0) {
|
|
1255
|
+
return this.toFixedInternal(void 0);
|
|
1256
|
+
}
|
|
1246
1257
|
return this.round(decimalPlaces).toFixedInternal(decimalPlaces);
|
|
1247
1258
|
}
|
|
1248
1259
|
toFixedInternal(decimalPlaces) {
|
|
1249
1260
|
if (this._isNaN) return "NaN";
|
|
1250
1261
|
if (this._isInfinite) return this._sign === 1 ? "Infinity" : "-Infinity";
|
|
1251
|
-
|
|
1262
|
+
const dp = decimalPlaces ?? -1;
|
|
1263
|
+
const allDigits = dp < 0;
|
|
1264
|
+
if (this._sign === 0) return allDigits ? "0" : dp > 0 ? "0." + "0".repeat(dp) : "0";
|
|
1252
1265
|
const sign = this._sign === -1 ? "-" : "";
|
|
1253
1266
|
const digits = this._coefficient.toString();
|
|
1254
1267
|
if (this._exponent >= 0) {
|
|
1255
1268
|
const intPart = digits + "0".repeat(this._exponent);
|
|
1256
|
-
return sign + intPart
|
|
1269
|
+
if (allDigits) return sign + intPart;
|
|
1270
|
+
return sign + intPart + (dp > 0 ? "." + "0".repeat(dp) : "");
|
|
1257
1271
|
} else if (-this._exponent <= digits.length) {
|
|
1258
1272
|
const insertPos = digits.length + this._exponent;
|
|
1259
1273
|
const intPart = digits.slice(0, insertPos) || "0";
|
|
1260
|
-
|
|
1261
|
-
if (
|
|
1262
|
-
decPart
|
|
1274
|
+
const decPart = digits.slice(insertPos);
|
|
1275
|
+
if (allDigits) {
|
|
1276
|
+
return sign + intPart + (decPart.length > 0 ? "." + decPart : "");
|
|
1277
|
+
}
|
|
1278
|
+
let paddedDec = decPart;
|
|
1279
|
+
if (paddedDec.length < dp) {
|
|
1280
|
+
paddedDec += "0".repeat(dp - paddedDec.length);
|
|
1263
1281
|
}
|
|
1264
|
-
return sign + intPart + (
|
|
1282
|
+
return sign + intPart + (dp > 0 ? "." + paddedDec.slice(0, dp) : "");
|
|
1265
1283
|
} else {
|
|
1266
1284
|
const leadingZeros = -this._exponent - digits.length;
|
|
1267
|
-
|
|
1268
|
-
if (
|
|
1269
|
-
|
|
1285
|
+
const decPart = "0".repeat(leadingZeros) + digits;
|
|
1286
|
+
if (allDigits) {
|
|
1287
|
+
return sign + "0." + decPart;
|
|
1270
1288
|
}
|
|
1271
|
-
|
|
1289
|
+
let paddedDec = decPart;
|
|
1290
|
+
if (paddedDec.length < dp) {
|
|
1291
|
+
paddedDec += "0".repeat(dp - paddedDec.length);
|
|
1292
|
+
}
|
|
1293
|
+
return sign + "0" + (dp > 0 ? "." + paddedDec.slice(0, dp) : "");
|
|
1272
1294
|
}
|
|
1273
1295
|
}
|
|
1274
1296
|
/**
|
|
@@ -1337,6 +1359,55 @@ var BigNumber = class _BigNumber {
|
|
|
1337
1359
|
}
|
|
1338
1360
|
return 0n;
|
|
1339
1361
|
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Binary string representation, e.g. `255 -> '0b11111111'`, `0.5 -> '0b0.1'`.
|
|
1364
|
+
* Mirrors Decimal.js `toBinary()`. Negatives carry a leading `-`.
|
|
1365
|
+
*/
|
|
1366
|
+
toBinary() {
|
|
1367
|
+
return this.toRadixString(2, "0b");
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Octal string representation, e.g. `8 -> '0o10'`.
|
|
1371
|
+
* Mirrors Decimal.js `toOctal()`.
|
|
1372
|
+
*/
|
|
1373
|
+
toOctal() {
|
|
1374
|
+
return this.toRadixString(8, "0o");
|
|
1375
|
+
}
|
|
1376
|
+
/**
|
|
1377
|
+
* Hexadecimal string representation, e.g. `255 -> '0xff'`, `10.5 -> '0xa.8'`.
|
|
1378
|
+
* Mirrors Decimal.js `toHexadecimal()`.
|
|
1379
|
+
*/
|
|
1380
|
+
toHexadecimal() {
|
|
1381
|
+
return this.toRadixString(16, "0x");
|
|
1382
|
+
}
|
|
1383
|
+
/**
|
|
1384
|
+
* Shared radix formatter for {@link toBinary}/{@link toOctal}/{@link toHexadecimal}.
|
|
1385
|
+
* Integers and terminating fractions are exact; a non-terminating fraction is
|
|
1386
|
+
* truncated at `maxFractionDigits` radix digits (a safety bound — the common
|
|
1387
|
+
* use is integer formatting).
|
|
1388
|
+
*/
|
|
1389
|
+
toRadixString(radix, prefix, maxFractionDigits = 1100) {
|
|
1390
|
+
if (this._isNaN) return "NaN";
|
|
1391
|
+
if (this._isInfinite) return (this._sign < 0 ? "-" : "") + "Infinity";
|
|
1392
|
+
if (this._sign === 0) return prefix + "0";
|
|
1393
|
+
const negative = this._sign < 0;
|
|
1394
|
+
const absVal = this.abs();
|
|
1395
|
+
const intPart = absVal.toBigInt();
|
|
1396
|
+
let out = prefix + intPart.toString(radix);
|
|
1397
|
+
let frac = absVal.subtract(_BigNumber.fromBigInt(intPart));
|
|
1398
|
+
if (!frac.isZero()) {
|
|
1399
|
+
const radixBN = _BigNumber.fromNumber(radix);
|
|
1400
|
+
let digits = "";
|
|
1401
|
+
for (let i = 0; i < maxFractionDigits && !frac.isZero(); i++) {
|
|
1402
|
+
frac = frac.multiply(radixBN);
|
|
1403
|
+
const digit = frac.toBigInt();
|
|
1404
|
+
digits += Number(digit).toString(radix);
|
|
1405
|
+
frac = frac.subtract(_BigNumber.fromBigInt(digit));
|
|
1406
|
+
}
|
|
1407
|
+
out += "." + digits;
|
|
1408
|
+
}
|
|
1409
|
+
return (negative ? "-" : "") + out;
|
|
1410
|
+
}
|
|
1340
1411
|
// ============================================================
|
|
1341
1412
|
// Arithmetic Operations
|
|
1342
1413
|
// ============================================================
|
|
@@ -1373,6 +1444,12 @@ var BigNumber = class _BigNumber {
|
|
|
1373
1444
|
const b = this.ensureBigNumber(other);
|
|
1374
1445
|
return this.add(b.negate());
|
|
1375
1446
|
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Alias for subtract — mathjs / Decimal.js compatibility.
|
|
1449
|
+
*/
|
|
1450
|
+
sub(other) {
|
|
1451
|
+
return this.subtract(other);
|
|
1452
|
+
}
|
|
1376
1453
|
/**
|
|
1377
1454
|
* Multiplication
|
|
1378
1455
|
*/
|
|
@@ -1390,6 +1467,12 @@ var BigNumber = class _BigNumber {
|
|
|
1390
1467
|
const exponent = this._exponent + b._exponent;
|
|
1391
1468
|
return this.normalize(sign, coefficient, exponent);
|
|
1392
1469
|
}
|
|
1470
|
+
/**
|
|
1471
|
+
* Alias for multiply — mathjs / Decimal.js compatibility.
|
|
1472
|
+
*/
|
|
1473
|
+
mul(other) {
|
|
1474
|
+
return this.multiply(other);
|
|
1475
|
+
}
|
|
1393
1476
|
/**
|
|
1394
1477
|
* Division
|
|
1395
1478
|
*/
|
|
@@ -1501,6 +1584,14 @@ var BigNumber = class _BigNumber {
|
|
|
1501
1584
|
compare(other) {
|
|
1502
1585
|
return this.compareTo(other);
|
|
1503
1586
|
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Returns true iff this > other.
|
|
1589
|
+
* Accepts the same argument types as `add`/`multiply` (BigNumber, number, or string).
|
|
1590
|
+
* Named `gt` for Decimal.js / mathjs formatter compatibility.
|
|
1591
|
+
*/
|
|
1592
|
+
gt(other) {
|
|
1593
|
+
return this.greaterThan(this.ensureBigNumber(other));
|
|
1594
|
+
}
|
|
1504
1595
|
// ============================================================
|
|
1505
1596
|
// Rounding Methods
|
|
1506
1597
|
// ============================================================
|
|
@@ -1579,6 +1670,36 @@ var BigNumber = class _BigNumber {
|
|
|
1579
1670
|
trunc() {
|
|
1580
1671
|
return this.round(0, "down");
|
|
1581
1672
|
}
|
|
1673
|
+
/**
|
|
1674
|
+
* Round to `n` significant decimal digits and return a new BigNumber.
|
|
1675
|
+
* Delegates to `roundToPrecision`, using the global rounding mode unless
|
|
1676
|
+
* an explicit `roundingMode` is supplied.
|
|
1677
|
+
*
|
|
1678
|
+
* Named `toSignificantDigits` for Decimal.js / mathjs formatter compatibility.
|
|
1679
|
+
* When `n` is undefined the value is returned unchanged (no rounding).
|
|
1680
|
+
*/
|
|
1681
|
+
toSignificantDigits(n, roundingMode) {
|
|
1682
|
+
if (n === void 0) return this.clone();
|
|
1683
|
+
return this.roundToPrecision(n, roundingMode ?? globalConfig.rounding);
|
|
1684
|
+
}
|
|
1685
|
+
/**
|
|
1686
|
+
* The decimal exponent of this number: `floor(log10(|x|))`.
|
|
1687
|
+
*
|
|
1688
|
+
* Matches Decimal.js semantics:
|
|
1689
|
+
* - `12345` → `4` (5 digits → the most-significant place is 10^4)
|
|
1690
|
+
* - `0.0123` → `-2` (first significant digit is at 10^-2)
|
|
1691
|
+
* - `100` → `2`
|
|
1692
|
+
* - `0.01` → `-2`
|
|
1693
|
+
* - `0` → `0` (by convention; `isZero()` can be used to distinguish)
|
|
1694
|
+
*
|
|
1695
|
+
* For special values (NaN, ±Infinity) returns 0 — callers check
|
|
1696
|
+
* `isFinite()` / `isNaN()` before using `.e`.
|
|
1697
|
+
*/
|
|
1698
|
+
get e() {
|
|
1699
|
+
if (this._isNaN || this._isInfinite || this._sign === 0) return 0;
|
|
1700
|
+
const d = this._coefficient.toString().length;
|
|
1701
|
+
return d - 1 + this._exponent;
|
|
1702
|
+
}
|
|
1582
1703
|
// ============================================================
|
|
1583
1704
|
// Modular Arithmetic
|
|
1584
1705
|
// ============================================================
|
|
@@ -1795,7 +1916,7 @@ var BigNumber = class _BigNumber {
|
|
|
1795
1916
|
if (val > 0.5) {
|
|
1796
1917
|
k = Math.ceil(Math.log2(val * 2));
|
|
1797
1918
|
}
|
|
1798
|
-
const divisor = _BigNumber.fromNumber(2
|
|
1919
|
+
const divisor = _BigNumber.fromNumber(2).pow(k);
|
|
1799
1920
|
const reduced = this.divide(divisor);
|
|
1800
1921
|
let sum = _BigNumber.fromNumber(1);
|
|
1801
1922
|
let term = _BigNumber.fromNumber(1);
|
|
@@ -1883,9 +2004,7 @@ var BigNumber = class _BigNumber {
|
|
|
1883
2004
|
for (let i = 0; i < 100; i++) {
|
|
1884
2005
|
const guessSquared = guess.multiply(guess);
|
|
1885
2006
|
const newGuess = guess.multiply(_BigNumber.fromNumber(2)).add(this.divide(guessSquared)).divide(three);
|
|
1886
|
-
if (guess.subtract(newGuess).abs().compareTo(
|
|
1887
|
-
guess.abs().multiply(_BigNumber.parse("1e-" + (precision + 5)))
|
|
1888
|
-
) <= 0) {
|
|
2007
|
+
if (guess.subtract(newGuess).abs().compareTo(guess.abs().multiply(_BigNumber.parse("1e-" + (precision + 5)))) <= 0) {
|
|
1889
2008
|
return newGuess.roundToPrecision(precision);
|
|
1890
2009
|
}
|
|
1891
2010
|
guess = newGuess;
|
|
@@ -1963,9 +2082,7 @@ var BigNumber = class _BigNumber {
|
|
|
1963
2082
|
const xSquared = this.multiply(this);
|
|
1964
2083
|
const maxIter = precision + 20;
|
|
1965
2084
|
for (let n = 1; n <= maxIter; n++) {
|
|
1966
|
-
term = term.multiply(xSquared).divide(
|
|
1967
|
-
_BigNumber.fromNumber(2 * n * (2 * n + 1))
|
|
1968
|
-
).negate();
|
|
2085
|
+
term = term.multiply(xSquared).divide(_BigNumber.fromNumber(2 * n * (2 * n + 1))).negate();
|
|
1969
2086
|
const newSum = sum.add(term);
|
|
1970
2087
|
if (sum.equals(newSum)) break;
|
|
1971
2088
|
sum = newSum;
|
|
@@ -1980,9 +2097,7 @@ var BigNumber = class _BigNumber {
|
|
|
1980
2097
|
const xSquared = this.multiply(this);
|
|
1981
2098
|
const maxIter = precision + 20;
|
|
1982
2099
|
for (let n = 1; n <= maxIter; n++) {
|
|
1983
|
-
term = term.multiply(xSquared).divide(
|
|
1984
|
-
_BigNumber.fromNumber((2 * n - 1) * (2 * n))
|
|
1985
|
-
).negate();
|
|
2100
|
+
term = term.multiply(xSquared).divide(_BigNumber.fromNumber((2 * n - 1) * (2 * n))).negate();
|
|
1986
2101
|
const newSum = sum.add(term);
|
|
1987
2102
|
if (sum.equals(newSum)) break;
|
|
1988
2103
|
sum = newSum;
|
|
@@ -2034,7 +2149,13 @@ var BigNumber = class _BigNumber {
|
|
|
2034
2149
|
return this._sign;
|
|
2035
2150
|
}
|
|
2036
2151
|
clone() {
|
|
2037
|
-
return new _BigNumber(
|
|
2152
|
+
return new _BigNumber(
|
|
2153
|
+
this._sign,
|
|
2154
|
+
this._coefficient,
|
|
2155
|
+
this._exponent,
|
|
2156
|
+
this._isNaN,
|
|
2157
|
+
this._isInfinite
|
|
2158
|
+
);
|
|
2038
2159
|
}
|
|
2039
2160
|
// ============================================================
|
|
2040
2161
|
// Private Helper Methods
|
|
@@ -2072,8 +2193,683 @@ var BIGNUMBER_NEG_ONE = BigNumber.fromNumber(-1);
|
|
|
2072
2193
|
var BIGNUMBER_TEN = BigNumber.fromNumber(10);
|
|
2073
2194
|
var BIGNUMBER_PI = BigNumber.parse("3.14159265358979323846264338327950288419716939937510");
|
|
2074
2195
|
var BIGNUMBER_E = BigNumber.parse("2.71828182845904523536028747135266249775724709369995");
|
|
2075
|
-
var BIGNUMBER_LN2 = BigNumber.parse(
|
|
2076
|
-
|
|
2196
|
+
var BIGNUMBER_LN2 = BigNumber.parse(
|
|
2197
|
+
"0.69314718055994530941723212145817656807550013436026"
|
|
2198
|
+
);
|
|
2199
|
+
var BIGNUMBER_LN10 = BigNumber.parse(
|
|
2200
|
+
"2.30258509299404568401799145468436420760110148862877"
|
|
2201
|
+
);
|
|
2202
|
+
|
|
2203
|
+
// src/types/unit-definitions.ts
|
|
2204
|
+
var DIMENSIONLESS = Object.freeze({
|
|
2205
|
+
length: 0,
|
|
2206
|
+
mass: 0,
|
|
2207
|
+
time: 0,
|
|
2208
|
+
current: 0,
|
|
2209
|
+
temperature: 0,
|
|
2210
|
+
amount: 0,
|
|
2211
|
+
luminosity: 0
|
|
2212
|
+
});
|
|
2213
|
+
function dim(partial) {
|
|
2214
|
+
return {
|
|
2215
|
+
length: partial.length ?? 0,
|
|
2216
|
+
mass: partial.mass ?? 0,
|
|
2217
|
+
time: partial.time ?? 0,
|
|
2218
|
+
current: partial.current ?? 0,
|
|
2219
|
+
temperature: partial.temperature ?? 0,
|
|
2220
|
+
amount: partial.amount ?? 0,
|
|
2221
|
+
luminosity: partial.luminosity ?? 0
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
var BASE_UNITS = {
|
|
2225
|
+
m: { multiplier: 1, dimensions: dim({ length: 1 }), prefixable: true },
|
|
2226
|
+
// The base unit of mass is *kilogram*, but the prefix-able symbol is "g".
|
|
2227
|
+
// Internally we treat 1 kg as the canonical value, so "g" -> 0.001 kg.
|
|
2228
|
+
kg: { multiplier: 1, dimensions: dim({ mass: 1 }) },
|
|
2229
|
+
s: { multiplier: 1, dimensions: dim({ time: 1 }), prefixable: true },
|
|
2230
|
+
A: { multiplier: 1, dimensions: dim({ current: 1 }), prefixable: true },
|
|
2231
|
+
K: { multiplier: 1, dimensions: dim({ temperature: 1 }), prefixable: true },
|
|
2232
|
+
mol: { multiplier: 1, dimensions: dim({ amount: 1 }), prefixable: true },
|
|
2233
|
+
cd: { multiplier: 1, dimensions: dim({ luminosity: 1 }), prefixable: true }
|
|
2234
|
+
};
|
|
2235
|
+
var DERIVED_UNITS = {
|
|
2236
|
+
// --- Length ---------------------------------------------------------------
|
|
2237
|
+
// Note: 'm' is a base unit (above), already prefixable.
|
|
2238
|
+
ft: { multiplier: 0.3048, dimensions: dim({ length: 1 }) },
|
|
2239
|
+
foot: { multiplier: 0.3048, dimensions: dim({ length: 1 }) },
|
|
2240
|
+
in: { multiplier: 0.0254, dimensions: dim({ length: 1 }) },
|
|
2241
|
+
inch: { multiplier: 0.0254, dimensions: dim({ length: 1 }) },
|
|
2242
|
+
yd: { multiplier: 0.9144, dimensions: dim({ length: 1 }) },
|
|
2243
|
+
yard: { multiplier: 0.9144, dimensions: dim({ length: 1 }) },
|
|
2244
|
+
mi: { multiplier: 1609.344, dimensions: dim({ length: 1 }) },
|
|
2245
|
+
mile: { multiplier: 1609.344, dimensions: dim({ length: 1 }) },
|
|
2246
|
+
// --- Mass -----------------------------------------------------------------
|
|
2247
|
+
// The "g" entry is gram (0.001 kg). It is prefixable, so kg/mg/µg work via
|
|
2248
|
+
// prefix application on this entry rather than the base "kg".
|
|
2249
|
+
g: { multiplier: 1e-3, dimensions: dim({ mass: 1 }), prefixable: true },
|
|
2250
|
+
lb: { multiplier: 0.45359237, dimensions: dim({ mass: 1 }) },
|
|
2251
|
+
lbm: { multiplier: 0.45359237, dimensions: dim({ mass: 1 }) },
|
|
2252
|
+
oz: { multiplier: 0.028349523125, dimensions: dim({ mass: 1 }) },
|
|
2253
|
+
ton: { multiplier: 907.18474, dimensions: dim({ mass: 1 }) },
|
|
2254
|
+
// US short ton
|
|
2255
|
+
tonne: { multiplier: 1e3, dimensions: dim({ mass: 1 }) },
|
|
2256
|
+
// metric ton
|
|
2257
|
+
// --- Time -----------------------------------------------------------------
|
|
2258
|
+
min: { multiplier: 60, dimensions: dim({ time: 1 }) },
|
|
2259
|
+
h: { multiplier: 3600, dimensions: dim({ time: 1 }) },
|
|
2260
|
+
hr: { multiplier: 3600, dimensions: dim({ time: 1 }) },
|
|
2261
|
+
day: { multiplier: 86400, dimensions: dim({ time: 1 }) },
|
|
2262
|
+
week: { multiplier: 604800, dimensions: dim({ time: 1 }) },
|
|
2263
|
+
year: { multiplier: 31557600, dimensions: dim({ time: 1 }) },
|
|
2264
|
+
// Julian year
|
|
2265
|
+
// --- Temperature ----------------------------------------------------------
|
|
2266
|
+
// K is the base.
|
|
2267
|
+
// °C → K is offset by 273.15 (no multiplicative factor change).
|
|
2268
|
+
// °F → K is more complex (offset & scale).
|
|
2269
|
+
degC: { multiplier: 1, offset: 273.15, dimensions: dim({ temperature: 1 }) },
|
|
2270
|
+
degF: { multiplier: 5 / 9, offset: 459.67 * (5 / 9), dimensions: dim({ temperature: 1 }) },
|
|
2271
|
+
degR: { multiplier: 5 / 9, dimensions: dim({ temperature: 1 }) },
|
|
2272
|
+
// Rankine
|
|
2273
|
+
// --- Plane angle (dimensionless, but often treated as units) --------------
|
|
2274
|
+
rad: { multiplier: 1, dimensions: dim({}), prefixable: true },
|
|
2275
|
+
deg: { multiplier: Math.PI / 180, dimensions: dim({}) },
|
|
2276
|
+
grad: { multiplier: Math.PI / 200, dimensions: dim({}) },
|
|
2277
|
+
// --- Force ----------------------------------------------------------------
|
|
2278
|
+
// N = kg·m·s^-2 = 1 kg·m·s^-2; in our base-units representation that's
|
|
2279
|
+
// value=1, dim {length: 1, mass: 1, time: -2}.
|
|
2280
|
+
N: { multiplier: 1, dimensions: dim({ length: 1, mass: 1, time: -2 }), prefixable: true },
|
|
2281
|
+
dyn: { multiplier: 1e-5, dimensions: dim({ length: 1, mass: 1, time: -2 }) },
|
|
2282
|
+
lbf: { multiplier: 4.4482216152605, dimensions: dim({ length: 1, mass: 1, time: -2 }) },
|
|
2283
|
+
// --- Energy ---------------------------------------------------------------
|
|
2284
|
+
J: { multiplier: 1, dimensions: dim({ length: 2, mass: 1, time: -2 }), prefixable: true },
|
|
2285
|
+
erg: { multiplier: 1e-7, dimensions: dim({ length: 2, mass: 1, time: -2 }) },
|
|
2286
|
+
cal: { multiplier: 4.184, dimensions: dim({ length: 2, mass: 1, time: -2 }) },
|
|
2287
|
+
eV: {
|
|
2288
|
+
multiplier: 1602176634e-28,
|
|
2289
|
+
dimensions: dim({ length: 2, mass: 1, time: -2 }),
|
|
2290
|
+
prefixable: true
|
|
2291
|
+
},
|
|
2292
|
+
BTU: { multiplier: 1055.05585262, dimensions: dim({ length: 2, mass: 1, time: -2 }) },
|
|
2293
|
+
// --- Power ----------------------------------------------------------------
|
|
2294
|
+
W: { multiplier: 1, dimensions: dim({ length: 2, mass: 1, time: -3 }), prefixable: true },
|
|
2295
|
+
hp: { multiplier: 745.6998715822702, dimensions: dim({ length: 2, mass: 1, time: -3 }) },
|
|
2296
|
+
// --- Pressure -------------------------------------------------------------
|
|
2297
|
+
Pa: { multiplier: 1, dimensions: dim({ length: -1, mass: 1, time: -2 }), prefixable: true },
|
|
2298
|
+
bar: { multiplier: 1e5, dimensions: dim({ length: -1, mass: 1, time: -2 }), prefixable: true },
|
|
2299
|
+
atm: { multiplier: 101325, dimensions: dim({ length: -1, mass: 1, time: -2 }) },
|
|
2300
|
+
psi: { multiplier: 6894.757293168361, dimensions: dim({ length: -1, mass: 1, time: -2 }) },
|
|
2301
|
+
torr: { multiplier: 133.322368421, dimensions: dim({ length: -1, mass: 1, time: -2 }) },
|
|
2302
|
+
mmHg: { multiplier: 133.322387415, dimensions: dim({ length: -1, mass: 1, time: -2 }) },
|
|
2303
|
+
// --- Electric -------------------------------------------------------------
|
|
2304
|
+
C: {
|
|
2305
|
+
multiplier: 1,
|
|
2306
|
+
dimensions: dim({ time: 1, current: 1 }),
|
|
2307
|
+
prefixable: true
|
|
2308
|
+
},
|
|
2309
|
+
V: {
|
|
2310
|
+
multiplier: 1,
|
|
2311
|
+
dimensions: dim({ length: 2, mass: 1, time: -3, current: -1 }),
|
|
2312
|
+
prefixable: true
|
|
2313
|
+
},
|
|
2314
|
+
ohm: {
|
|
2315
|
+
multiplier: 1,
|
|
2316
|
+
dimensions: dim({ length: 2, mass: 1, time: -3, current: -2 }),
|
|
2317
|
+
prefixable: true
|
|
2318
|
+
},
|
|
2319
|
+
\u03A9: {
|
|
2320
|
+
multiplier: 1,
|
|
2321
|
+
dimensions: dim({ length: 2, mass: 1, time: -3, current: -2 }),
|
|
2322
|
+
prefixable: true
|
|
2323
|
+
},
|
|
2324
|
+
F: {
|
|
2325
|
+
multiplier: 1,
|
|
2326
|
+
dimensions: dim({ length: -2, mass: -1, time: 4, current: 2 }),
|
|
2327
|
+
prefixable: true
|
|
2328
|
+
},
|
|
2329
|
+
H: {
|
|
2330
|
+
multiplier: 1,
|
|
2331
|
+
dimensions: dim({ length: 2, mass: 1, time: -2, current: -2 }),
|
|
2332
|
+
prefixable: true
|
|
2333
|
+
},
|
|
2334
|
+
// --- Frequency ------------------------------------------------------------
|
|
2335
|
+
Hz: { multiplier: 1, dimensions: dim({ time: -1 }), prefixable: true },
|
|
2336
|
+
// --- Volume ---------------------------------------------------------------
|
|
2337
|
+
L: { multiplier: 1e-3, dimensions: dim({ length: 3 }), prefixable: true },
|
|
2338
|
+
l: { multiplier: 1e-3, dimensions: dim({ length: 3 }), prefixable: true },
|
|
2339
|
+
gal: { multiplier: 0.003785411784, dimensions: dim({ length: 3 }) },
|
|
2340
|
+
// US gallon
|
|
2341
|
+
// --- Area -----------------------------------------------------------------
|
|
2342
|
+
ha: { multiplier: 1e4, dimensions: dim({ length: 2 }) },
|
|
2343
|
+
// hectare
|
|
2344
|
+
acre: { multiplier: 4046.8564224, dimensions: dim({ length: 2 }) }
|
|
2345
|
+
};
|
|
2346
|
+
var ALL_UNITS = { ...BASE_UNITS, ...DERIVED_UNITS };
|
|
2347
|
+
var UNIT_ALIASES = {
|
|
2348
|
+
"\xB0C": "degC",
|
|
2349
|
+
"\xB0F": "degF",
|
|
2350
|
+
"\xB0R": "degR",
|
|
2351
|
+
"\xB0": "deg"
|
|
2352
|
+
// The Unicode micro sign is handled in prefixes; °K is non-standard.
|
|
2353
|
+
};
|
|
2354
|
+
function getUnitDef(name) {
|
|
2355
|
+
return ALL_UNITS[name];
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
// src/types/unit-prefixes.ts
|
|
2359
|
+
var SI_PREFIXES = {
|
|
2360
|
+
// Positive powers of ten
|
|
2361
|
+
Y: 1e24,
|
|
2362
|
+
// yotta
|
|
2363
|
+
Z: 1e21,
|
|
2364
|
+
// zetta
|
|
2365
|
+
E: 1e18,
|
|
2366
|
+
// exa
|
|
2367
|
+
P: 1e15,
|
|
2368
|
+
// peta
|
|
2369
|
+
T: 1e12,
|
|
2370
|
+
// tera
|
|
2371
|
+
G: 1e9,
|
|
2372
|
+
// giga
|
|
2373
|
+
M: 1e6,
|
|
2374
|
+
// mega
|
|
2375
|
+
k: 1e3,
|
|
2376
|
+
// kilo
|
|
2377
|
+
h: 100,
|
|
2378
|
+
// hecto
|
|
2379
|
+
da: 10,
|
|
2380
|
+
// deka (two-letter)
|
|
2381
|
+
// Negative powers of ten
|
|
2382
|
+
d: 0.1,
|
|
2383
|
+
// deci
|
|
2384
|
+
c: 0.01,
|
|
2385
|
+
// centi
|
|
2386
|
+
m: 1e-3,
|
|
2387
|
+
// milli
|
|
2388
|
+
u: 1e-6,
|
|
2389
|
+
// micro (ASCII fallback)
|
|
2390
|
+
\u00B5: 1e-6,
|
|
2391
|
+
// micro (Unicode)
|
|
2392
|
+
n: 1e-9,
|
|
2393
|
+
// nano
|
|
2394
|
+
p: 1e-12,
|
|
2395
|
+
// pico
|
|
2396
|
+
f: 1e-15,
|
|
2397
|
+
// femto
|
|
2398
|
+
a: 1e-18,
|
|
2399
|
+
// atto
|
|
2400
|
+
z: 1e-21,
|
|
2401
|
+
// zepto
|
|
2402
|
+
y: 1e-24
|
|
2403
|
+
// yocto
|
|
2404
|
+
};
|
|
2405
|
+
var SI_PREFIX_KEYS = Object.keys(SI_PREFIXES).sort(
|
|
2406
|
+
(a, b) => b.length - a.length
|
|
2407
|
+
);
|
|
2408
|
+
var BEST_PREFIXES = [
|
|
2409
|
+
"Y",
|
|
2410
|
+
"Z",
|
|
2411
|
+
"E",
|
|
2412
|
+
"P",
|
|
2413
|
+
"T",
|
|
2414
|
+
"G",
|
|
2415
|
+
"M",
|
|
2416
|
+
"k",
|
|
2417
|
+
"",
|
|
2418
|
+
"m",
|
|
2419
|
+
"u",
|
|
2420
|
+
"n",
|
|
2421
|
+
"p",
|
|
2422
|
+
"f",
|
|
2423
|
+
"a",
|
|
2424
|
+
"z",
|
|
2425
|
+
"y"
|
|
2426
|
+
];
|
|
2427
|
+
function getPrefix(name) {
|
|
2428
|
+
return SI_PREFIXES[name];
|
|
2429
|
+
}
|
|
2430
|
+
|
|
2431
|
+
// src/types/unit.ts
|
|
2432
|
+
var DimensionMismatchError = class extends Error {
|
|
2433
|
+
constructor(left, right, op) {
|
|
2434
|
+
super(
|
|
2435
|
+
`Dimension mismatch in ${op}: ${formatDimensions(left)} vs ${formatDimensions(right)}`
|
|
2436
|
+
);
|
|
2437
|
+
this.left = left;
|
|
2438
|
+
this.right = right;
|
|
2439
|
+
}
|
|
2440
|
+
left;
|
|
2441
|
+
right;
|
|
2442
|
+
name = "DimensionMismatchError";
|
|
2443
|
+
};
|
|
2444
|
+
var UnitParseError = class extends Error {
|
|
2445
|
+
name = "UnitParseError";
|
|
2446
|
+
constructor(notation, reason) {
|
|
2447
|
+
super(`Cannot parse unit notation "${notation}": ${reason}`);
|
|
2448
|
+
}
|
|
2449
|
+
};
|
|
2450
|
+
var DIM_KEYS = [
|
|
2451
|
+
"length",
|
|
2452
|
+
"mass",
|
|
2453
|
+
"time",
|
|
2454
|
+
"current",
|
|
2455
|
+
"temperature",
|
|
2456
|
+
"amount",
|
|
2457
|
+
"luminosity"
|
|
2458
|
+
];
|
|
2459
|
+
function dimsEqual(a, b) {
|
|
2460
|
+
for (const k of DIM_KEYS) {
|
|
2461
|
+
if (a[k] !== b[k]) return false;
|
|
2462
|
+
}
|
|
2463
|
+
return true;
|
|
2464
|
+
}
|
|
2465
|
+
function dimsAdd(a, b) {
|
|
2466
|
+
return {
|
|
2467
|
+
length: a.length + b.length,
|
|
2468
|
+
mass: a.mass + b.mass,
|
|
2469
|
+
time: a.time + b.time,
|
|
2470
|
+
current: a.current + b.current,
|
|
2471
|
+
temperature: a.temperature + b.temperature,
|
|
2472
|
+
amount: a.amount + b.amount,
|
|
2473
|
+
luminosity: a.luminosity + b.luminosity
|
|
2474
|
+
};
|
|
2475
|
+
}
|
|
2476
|
+
function dimsSub(a, b) {
|
|
2477
|
+
return {
|
|
2478
|
+
length: a.length - b.length,
|
|
2479
|
+
mass: a.mass - b.mass,
|
|
2480
|
+
time: a.time - b.time,
|
|
2481
|
+
current: a.current - b.current,
|
|
2482
|
+
temperature: a.temperature - b.temperature,
|
|
2483
|
+
amount: a.amount - b.amount,
|
|
2484
|
+
luminosity: a.luminosity - b.luminosity
|
|
2485
|
+
};
|
|
2486
|
+
}
|
|
2487
|
+
function dimsScale(a, n) {
|
|
2488
|
+
return {
|
|
2489
|
+
length: a.length * n,
|
|
2490
|
+
mass: a.mass * n,
|
|
2491
|
+
time: a.time * n,
|
|
2492
|
+
current: a.current * n,
|
|
2493
|
+
temperature: a.temperature * n,
|
|
2494
|
+
amount: a.amount * n,
|
|
2495
|
+
luminosity: a.luminosity * n
|
|
2496
|
+
};
|
|
2497
|
+
}
|
|
2498
|
+
function isDimensionless(d) {
|
|
2499
|
+
return dimsEqual(d, DIMENSIONLESS);
|
|
2500
|
+
}
|
|
2501
|
+
function formatDimensions(d) {
|
|
2502
|
+
const parts = [];
|
|
2503
|
+
for (const k of DIM_KEYS) {
|
|
2504
|
+
const e = d[k];
|
|
2505
|
+
if (e !== 0) parts.push(`${k}^${e}`);
|
|
2506
|
+
}
|
|
2507
|
+
return parts.length === 0 ? "dimensionless" : parts.join("\xB7");
|
|
2508
|
+
}
|
|
2509
|
+
function resolveAtom(token) {
|
|
2510
|
+
const aliased = UNIT_ALIASES[token] ?? token;
|
|
2511
|
+
const direct = getUnitDef(aliased);
|
|
2512
|
+
if (direct) {
|
|
2513
|
+
return {
|
|
2514
|
+
multiplier: direct.multiplier,
|
|
2515
|
+
offset: direct.offset ?? 0,
|
|
2516
|
+
dimensions: direct.dimensions,
|
|
2517
|
+
prefix: "",
|
|
2518
|
+
base: aliased
|
|
2519
|
+
};
|
|
2520
|
+
}
|
|
2521
|
+
for (const prefix of SI_PREFIX_KEYS) {
|
|
2522
|
+
if (!aliased.startsWith(prefix) || aliased.length === prefix.length) continue;
|
|
2523
|
+
const rest = aliased.slice(prefix.length);
|
|
2524
|
+
const baseDef = getUnitDef(rest);
|
|
2525
|
+
if (!baseDef) continue;
|
|
2526
|
+
if (!baseDef.prefixable) continue;
|
|
2527
|
+
return {
|
|
2528
|
+
multiplier: baseDef.multiplier * SI_PREFIXES[prefix],
|
|
2529
|
+
// Offset is in base units, so prefix the multiplier but not the offset
|
|
2530
|
+
// (we never apply prefixes to temperature units in practice anyway).
|
|
2531
|
+
offset: baseDef.offset ?? 0,
|
|
2532
|
+
dimensions: baseDef.dimensions,
|
|
2533
|
+
prefix,
|
|
2534
|
+
base: rest
|
|
2535
|
+
};
|
|
2536
|
+
}
|
|
2537
|
+
throw new UnitParseError(token, `unknown unit "${token}"`);
|
|
2538
|
+
}
|
|
2539
|
+
function splitScalar(input) {
|
|
2540
|
+
const m = input.match(/^\s*([+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\s+(.*)$/);
|
|
2541
|
+
if (m) {
|
|
2542
|
+
return { scalar: parseFloat(m[1]), rest: m[2].trim() };
|
|
2543
|
+
}
|
|
2544
|
+
const scalarOnly = input.match(/^\s*([+-]?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?)\s*$/);
|
|
2545
|
+
if (scalarOnly) {
|
|
2546
|
+
return { scalar: parseFloat(scalarOnly[1]), rest: "" };
|
|
2547
|
+
}
|
|
2548
|
+
return { scalar: 1, rest: input.trim() };
|
|
2549
|
+
}
|
|
2550
|
+
function parseUnitExpr(expr) {
|
|
2551
|
+
if (expr === "" || expr === "1") return [];
|
|
2552
|
+
const normalized = expr.replace(/[·⋅]/g, "*");
|
|
2553
|
+
const atoms = [];
|
|
2554
|
+
let pos = 0;
|
|
2555
|
+
let nextSign = 1;
|
|
2556
|
+
while (pos < normalized.length) {
|
|
2557
|
+
while (pos < normalized.length && /\s/.test(normalized[pos])) pos++;
|
|
2558
|
+
if (pos >= normalized.length) break;
|
|
2559
|
+
const ch = normalized[pos];
|
|
2560
|
+
if (ch === "*") {
|
|
2561
|
+
pos++;
|
|
2562
|
+
continue;
|
|
2563
|
+
}
|
|
2564
|
+
if (ch === "/") {
|
|
2565
|
+
nextSign = -1;
|
|
2566
|
+
pos++;
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
if (ch === "1") {
|
|
2570
|
+
const next = normalized[pos + 1];
|
|
2571
|
+
if (next === void 0 || next === " " || next === "/" || next === "*") {
|
|
2572
|
+
pos++;
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
if (!/[A-Za-z°µΩ]/.test(ch)) {
|
|
2577
|
+
throw new UnitParseError(expr, `unexpected character "${ch}" at position ${pos}`);
|
|
2578
|
+
}
|
|
2579
|
+
const idStart = pos;
|
|
2580
|
+
while (pos < normalized.length && /[A-Za-z°µΩ]/.test(normalized[pos])) pos++;
|
|
2581
|
+
const identifier = normalized.slice(idStart, pos);
|
|
2582
|
+
let exponent = 1;
|
|
2583
|
+
if (pos < normalized.length && normalized[pos] === "^") {
|
|
2584
|
+
pos++;
|
|
2585
|
+
const expMatch = normalized.slice(pos).match(/^([+-]?\d+(?:\.\d+)?)/);
|
|
2586
|
+
if (!expMatch) {
|
|
2587
|
+
throw new UnitParseError(expr, `expected exponent after "^" at position ${pos}`);
|
|
2588
|
+
}
|
|
2589
|
+
exponent = parseFloat(expMatch[1]);
|
|
2590
|
+
pos += expMatch[1].length;
|
|
2591
|
+
} else {
|
|
2592
|
+
const supMatch = normalized.slice(pos).match(/^([²³⁴⁵⁶⁷⁸⁹⁰¹]+)/);
|
|
2593
|
+
if (supMatch) {
|
|
2594
|
+
exponent = parseSuperscript(supMatch[1]);
|
|
2595
|
+
pos += supMatch[1].length;
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
const resolved = resolveAtom(identifier);
|
|
2599
|
+
atoms.push({
|
|
2600
|
+
multiplier: resolved.multiplier,
|
|
2601
|
+
offset: resolved.offset,
|
|
2602
|
+
dimensions: resolved.dimensions,
|
|
2603
|
+
exponent: nextSign * exponent,
|
|
2604
|
+
source: identifier
|
|
2605
|
+
});
|
|
2606
|
+
nextSign = 1;
|
|
2607
|
+
}
|
|
2608
|
+
return atoms;
|
|
2609
|
+
}
|
|
2610
|
+
var SUPERSCRIPT_MAP = {
|
|
2611
|
+
"\u2070": "0",
|
|
2612
|
+
"\xB9": "1",
|
|
2613
|
+
"\xB2": "2",
|
|
2614
|
+
"\xB3": "3",
|
|
2615
|
+
"\u2074": "4",
|
|
2616
|
+
"\u2075": "5",
|
|
2617
|
+
"\u2076": "6",
|
|
2618
|
+
"\u2077": "7",
|
|
2619
|
+
"\u2078": "8",
|
|
2620
|
+
"\u2079": "9"
|
|
2621
|
+
};
|
|
2622
|
+
function parseSuperscript(s) {
|
|
2623
|
+
let out = "";
|
|
2624
|
+
for (const ch of s) out += SUPERSCRIPT_MAP[ch] ?? ch;
|
|
2625
|
+
return parseInt(out, 10);
|
|
2626
|
+
}
|
|
2627
|
+
function parse(input) {
|
|
2628
|
+
const { scalar, rest } = splitScalar(input);
|
|
2629
|
+
return { scalar, atoms: parseUnitExpr(rest) };
|
|
2630
|
+
}
|
|
2631
|
+
var Unit = class _Unit {
|
|
2632
|
+
/** Discriminator (matches the prototype-flag convention). */
|
|
2633
|
+
type = "Unit";
|
|
2634
|
+
/** Used by `isUnit()` and typed-function dispatch. */
|
|
2635
|
+
isUnit = true;
|
|
2636
|
+
/** Numeric magnitude in **SI base units**. */
|
|
2637
|
+
value;
|
|
2638
|
+
/** Dimensional signature. */
|
|
2639
|
+
dimensions;
|
|
2640
|
+
/** Original notation string, preserved for `toString()` round-trip. */
|
|
2641
|
+
notation;
|
|
2642
|
+
constructor(value, notationOrDims, notation) {
|
|
2643
|
+
if (typeof notationOrDims === "string") {
|
|
2644
|
+
const parsed = parse(notationOrDims);
|
|
2645
|
+
const composed = composeAtoms(parsed.atoms);
|
|
2646
|
+
const inputValue = parsed.scalar * value;
|
|
2647
|
+
let canonical;
|
|
2648
|
+
if (parsed.atoms.length === 1 && parsed.atoms[0].exponent === 1) {
|
|
2649
|
+
const a = parsed.atoms[0];
|
|
2650
|
+
canonical = inputValue * a.multiplier + a.offset;
|
|
2651
|
+
} else {
|
|
2652
|
+
canonical = inputValue * composed.multiplier;
|
|
2653
|
+
}
|
|
2654
|
+
this.value = canonical;
|
|
2655
|
+
this.dimensions = composed.dimensions;
|
|
2656
|
+
this.notation = notationOrDims;
|
|
2657
|
+
} else {
|
|
2658
|
+
this.value = value;
|
|
2659
|
+
this.dimensions = notationOrDims;
|
|
2660
|
+
this.notation = notation ?? "";
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
// -------------------------------------------------------------------------
|
|
2664
|
+
// Arithmetic
|
|
2665
|
+
// -------------------------------------------------------------------------
|
|
2666
|
+
/**
|
|
2667
|
+
* Add two units. Requires identical dimensions.
|
|
2668
|
+
*
|
|
2669
|
+
* @throws {DimensionMismatchError} if dimensions differ.
|
|
2670
|
+
*/
|
|
2671
|
+
add(other) {
|
|
2672
|
+
if (!dimsEqual(this.dimensions, other.dimensions)) {
|
|
2673
|
+
throw new DimensionMismatchError(this.dimensions, other.dimensions, "add");
|
|
2674
|
+
}
|
|
2675
|
+
return new _Unit(this.value + other.value, this.dimensions, this.notation);
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2678
|
+
* Subtract two units. Requires identical dimensions.
|
|
2679
|
+
*
|
|
2680
|
+
* @throws {DimensionMismatchError} if dimensions differ.
|
|
2681
|
+
*/
|
|
2682
|
+
sub(other) {
|
|
2683
|
+
if (!dimsEqual(this.dimensions, other.dimensions)) {
|
|
2684
|
+
throw new DimensionMismatchError(this.dimensions, other.dimensions, "sub");
|
|
2685
|
+
}
|
|
2686
|
+
return new _Unit(this.value - other.value, this.dimensions, this.notation);
|
|
2687
|
+
}
|
|
2688
|
+
/**
|
|
2689
|
+
* Multiply by another unit or a scalar. Dimensions add when multiplying by
|
|
2690
|
+
* another unit; unchanged when multiplying by a scalar.
|
|
2691
|
+
*/
|
|
2692
|
+
mul(other) {
|
|
2693
|
+
if (typeof other === "number") {
|
|
2694
|
+
return new _Unit(this.value * other, this.dimensions, this.notation);
|
|
2695
|
+
}
|
|
2696
|
+
return new _Unit(
|
|
2697
|
+
this.value * other.value,
|
|
2698
|
+
dimsAdd(this.dimensions, other.dimensions),
|
|
2699
|
+
composeNotation(this.notation, other.notation, "*")
|
|
2700
|
+
);
|
|
2701
|
+
}
|
|
2702
|
+
/**
|
|
2703
|
+
* Divide by another unit or a scalar. Dimensions subtract when dividing by
|
|
2704
|
+
* another unit; unchanged when dividing by a scalar.
|
|
2705
|
+
*/
|
|
2706
|
+
div(other) {
|
|
2707
|
+
if (typeof other === "number") {
|
|
2708
|
+
return new _Unit(this.value / other, this.dimensions, this.notation);
|
|
2709
|
+
}
|
|
2710
|
+
return new _Unit(
|
|
2711
|
+
this.value / other.value,
|
|
2712
|
+
dimsSub(this.dimensions, other.dimensions),
|
|
2713
|
+
composeNotation(this.notation, other.notation, "/")
|
|
2714
|
+
);
|
|
2715
|
+
}
|
|
2716
|
+
/**
|
|
2717
|
+
* Raise to an integer or rational power. Dimensions scale by `n`.
|
|
2718
|
+
*/
|
|
2719
|
+
pow(n) {
|
|
2720
|
+
return new _Unit(
|
|
2721
|
+
this.value ** n,
|
|
2722
|
+
dimsScale(this.dimensions, n),
|
|
2723
|
+
this.notation ? `(${this.notation})^${n}` : ""
|
|
2724
|
+
);
|
|
2725
|
+
}
|
|
2726
|
+
// -------------------------------------------------------------------------
|
|
2727
|
+
// Conversion
|
|
2728
|
+
// -------------------------------------------------------------------------
|
|
2729
|
+
/**
|
|
2730
|
+
* Convert to a target unit. Dimensions must match.
|
|
2731
|
+
*
|
|
2732
|
+
* @throws {DimensionMismatchError} if `targetNotation`'s dimensions differ
|
|
2733
|
+
* from this unit's.
|
|
2734
|
+
*/
|
|
2735
|
+
to(targetNotation) {
|
|
2736
|
+
const parsed = parse(targetNotation);
|
|
2737
|
+
const composed = composeAtoms(parsed.atoms);
|
|
2738
|
+
if (!dimsEqual(this.dimensions, composed.dimensions)) {
|
|
2739
|
+
throw new DimensionMismatchError(
|
|
2740
|
+
this.dimensions,
|
|
2741
|
+
composed.dimensions,
|
|
2742
|
+
`to("${targetNotation}")`
|
|
2743
|
+
);
|
|
2744
|
+
}
|
|
2745
|
+
return new _Unit(this.value, composed.dimensions, targetNotation);
|
|
2746
|
+
}
|
|
2747
|
+
/**
|
|
2748
|
+
* Pick a unit (with SI prefix) that gives a magnitude closest to 1.
|
|
2749
|
+
*
|
|
2750
|
+
* Searches the unit registry for entries matching this unit's dimensions,
|
|
2751
|
+
* then applies each SI prefix in {@link BEST_PREFIXES} to find the
|
|
2752
|
+
* representation minimising `|log10(displayed value)|`.
|
|
2753
|
+
*
|
|
2754
|
+
* If no registered unit matches the dimensions, falls back to a synthetic
|
|
2755
|
+
* notation describing the dimensional vector.
|
|
2756
|
+
*/
|
|
2757
|
+
toBest() {
|
|
2758
|
+
if (isDimensionless(this.dimensions)) {
|
|
2759
|
+
return new _Unit(this.value, this.dimensions, "");
|
|
2760
|
+
}
|
|
2761
|
+
let bestNotation;
|
|
2762
|
+
let bestScore = Infinity;
|
|
2763
|
+
for (const [name, def] of Object.entries(ALL_UNITS)) {
|
|
2764
|
+
if (name === "kg") continue;
|
|
2765
|
+
if (!dimsEqual(def.dimensions, this.dimensions)) continue;
|
|
2766
|
+
if (def.offset !== void 0) continue;
|
|
2767
|
+
const candidatePrefixes = def.prefixable ? BEST_PREFIXES : [""];
|
|
2768
|
+
for (const prefix of candidatePrefixes) {
|
|
2769
|
+
const factor = prefix === "" ? def.multiplier : def.multiplier * SI_PREFIXES[prefix];
|
|
2770
|
+
if (factor === 0) continue;
|
|
2771
|
+
const displayed = this.value / factor;
|
|
2772
|
+
if (displayed === 0) continue;
|
|
2773
|
+
const score = Math.abs(Math.log10(Math.abs(displayed)));
|
|
2774
|
+
if (score < bestScore) {
|
|
2775
|
+
bestScore = score;
|
|
2776
|
+
bestNotation = prefix + name;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
if (bestNotation === void 0) {
|
|
2781
|
+
return new _Unit(
|
|
2782
|
+
this.value,
|
|
2783
|
+
this.dimensions,
|
|
2784
|
+
this.notation || formatDimensions(this.dimensions)
|
|
2785
|
+
);
|
|
2786
|
+
}
|
|
2787
|
+
return new _Unit(this.value, this.dimensions, bestNotation);
|
|
2788
|
+
}
|
|
2789
|
+
// -------------------------------------------------------------------------
|
|
2790
|
+
// Comparison
|
|
2791
|
+
// -------------------------------------------------------------------------
|
|
2792
|
+
/** True iff dimensions and canonical value are equal. */
|
|
2793
|
+
equals(other) {
|
|
2794
|
+
return dimsEqual(this.dimensions, other.dimensions) && this.value === other.value;
|
|
2795
|
+
}
|
|
2796
|
+
/** True iff dimensions match (values may differ). */
|
|
2797
|
+
dimensionsEqual(other) {
|
|
2798
|
+
return dimsEqual(this.dimensions, other.dimensions);
|
|
2799
|
+
}
|
|
2800
|
+
// -------------------------------------------------------------------------
|
|
2801
|
+
// Formatting
|
|
2802
|
+
// -------------------------------------------------------------------------
|
|
2803
|
+
/**
|
|
2804
|
+
* Pretty-print. If `notation` is set (the common case), display the value
|
|
2805
|
+
* in that notation — otherwise emit a dimensional signature.
|
|
2806
|
+
*/
|
|
2807
|
+
toString() {
|
|
2808
|
+
if (this.notation === "") {
|
|
2809
|
+
if (isDimensionless(this.dimensions)) return String(this.value);
|
|
2810
|
+
return `${this.value} ${formatDimensions(this.dimensions)}`;
|
|
2811
|
+
}
|
|
2812
|
+
const displayed = displayedValue(this.value, this.notation);
|
|
2813
|
+
return `${formatNumber(displayed)} ${this.notation}`;
|
|
2814
|
+
}
|
|
2815
|
+
/** JSON-serialisable record. */
|
|
2816
|
+
toJSON() {
|
|
2817
|
+
return { mathts: "Unit", value: this.value, notation: this.notation };
|
|
2818
|
+
}
|
|
2819
|
+
/**
|
|
2820
|
+
* Rehydrate from a JSON record produced by {@link toJSON}.
|
|
2821
|
+
*
|
|
2822
|
+
* @throws {UnitParseError} if `json.notation` cannot be parsed.
|
|
2823
|
+
*/
|
|
2824
|
+
static fromJSON(json) {
|
|
2825
|
+
const parsed = parse(json.notation);
|
|
2826
|
+
const composed = composeAtoms(parsed.atoms);
|
|
2827
|
+
return new _Unit(json.value, composed.dimensions, json.notation);
|
|
2828
|
+
}
|
|
2829
|
+
/**
|
|
2830
|
+
* Parse a full unit expression (with optional leading numeric coefficient).
|
|
2831
|
+
*
|
|
2832
|
+
* Example: `Unit.parse('5 km/h')`.
|
|
2833
|
+
*/
|
|
2834
|
+
static parse(input) {
|
|
2835
|
+
const { scalar, rest } = splitScalar(input);
|
|
2836
|
+
return new _Unit(scalar, rest);
|
|
2837
|
+
}
|
|
2838
|
+
};
|
|
2839
|
+
Unit.prototype.isUnit = true;
|
|
2840
|
+
function composeAtoms(atoms) {
|
|
2841
|
+
let multiplier = 1;
|
|
2842
|
+
let dimensions = { ...DIMENSIONLESS };
|
|
2843
|
+
for (const a of atoms) {
|
|
2844
|
+
multiplier *= a.multiplier ** a.exponent;
|
|
2845
|
+
dimensions = dimsAdd(dimensions, dimsScale(a.dimensions, a.exponent));
|
|
2846
|
+
}
|
|
2847
|
+
return { multiplier, dimensions };
|
|
2848
|
+
}
|
|
2849
|
+
function displayedValue(canonical, notation) {
|
|
2850
|
+
const parsed = parse(notation);
|
|
2851
|
+
const composed = composeAtoms(parsed.atoms);
|
|
2852
|
+
if (parsed.atoms.length === 1 && parsed.atoms[0].exponent === 1) {
|
|
2853
|
+
const a = parsed.atoms[0];
|
|
2854
|
+
return (canonical - a.offset) / a.multiplier;
|
|
2855
|
+
}
|
|
2856
|
+
return canonical / composed.multiplier;
|
|
2857
|
+
}
|
|
2858
|
+
function formatNumber(n) {
|
|
2859
|
+
if (Number.isInteger(n)) return n.toString();
|
|
2860
|
+
const rounded = Number(n.toPrecision(12));
|
|
2861
|
+
return rounded.toString();
|
|
2862
|
+
}
|
|
2863
|
+
function composeNotation(a, b, op) {
|
|
2864
|
+
if (a === "" && b === "") return "";
|
|
2865
|
+
if (a === "") return op === "*" ? b : `1/${b}`;
|
|
2866
|
+
if (b === "") return a;
|
|
2867
|
+
const sep = op === "*" ? "*" : "/";
|
|
2868
|
+
return `${a}${sep}${b}`;
|
|
2869
|
+
}
|
|
2870
|
+
function isUnit(value) {
|
|
2871
|
+
return value instanceof Unit;
|
|
2872
|
+
}
|
|
2077
2873
|
|
|
2078
2874
|
// src/typed/mathts-typed.ts
|
|
2079
2875
|
import typed, { create } from "typed-function";
|
|
@@ -2115,7 +2911,14 @@ var isBigNumber2 = (x) => isBigNumber(x);
|
|
|
2115
2911
|
var isMatrix = (x) => isObject(x) && "rows" in x && "cols" in x && "get" in x && typeof x.type === "string";
|
|
2116
2912
|
var isDenseMatrix = (x) => isMatrix(x) && x.type === "DenseMatrix";
|
|
2117
2913
|
var isSparseMatrix = (x) => isMatrix(x) && x.type === "SparseMatrix";
|
|
2118
|
-
var
|
|
2914
|
+
var isUnit2 = (x) => {
|
|
2915
|
+
if (!isObject(x)) return false;
|
|
2916
|
+
const o = x;
|
|
2917
|
+
if (typeof o.type !== "string") return false;
|
|
2918
|
+
if (o.type === "Unit" && "value" in o && "dimensions" in o) return true;
|
|
2919
|
+
if ("value" in o && "unit" in o) return true;
|
|
2920
|
+
return false;
|
|
2921
|
+
};
|
|
2119
2922
|
var MATHTS_TYPES = [
|
|
2120
2923
|
// bigint is not built into typed-function
|
|
2121
2924
|
{ name: "bigint", test: isBigInt },
|
|
@@ -2135,7 +2938,7 @@ var MATHTS_TYPES = [
|
|
|
2135
2938
|
{ name: "SparseMatrix", test: isSparseMatrix },
|
|
2136
2939
|
{ name: "Matrix", test: isMatrix },
|
|
2137
2940
|
// Other types
|
|
2138
|
-
{ name: "Unit", test:
|
|
2941
|
+
{ name: "Unit", test: isUnit2 }
|
|
2139
2942
|
];
|
|
2140
2943
|
var MATHTS_CONVERSIONS = [
|
|
2141
2944
|
// -------------------------------------------------------------------------
|
|
@@ -2551,6 +3354,9 @@ var math = {
|
|
|
2551
3354
|
// src/index.ts
|
|
2552
3355
|
var VERSION = "0.1.0";
|
|
2553
3356
|
export {
|
|
3357
|
+
ALL_UNITS,
|
|
3358
|
+
BASE_UNITS,
|
|
3359
|
+
BEST_PREFIXES,
|
|
2554
3360
|
BIGNUMBER_E,
|
|
2555
3361
|
BIGNUMBER_LN10,
|
|
2556
3362
|
BIGNUMBER_LN2,
|
|
@@ -2565,6 +3371,9 @@ export {
|
|
|
2565
3371
|
COMPLEX_ZERO,
|
|
2566
3372
|
Complex,
|
|
2567
3373
|
DEFAULT_CONFIG,
|
|
3374
|
+
DERIVED_UNITS,
|
|
3375
|
+
DIMENSIONLESS,
|
|
3376
|
+
DimensionMismatchError,
|
|
2568
3377
|
FRACTION_HALF,
|
|
2569
3378
|
FRACTION_NEG_ONE,
|
|
2570
3379
|
FRACTION_ONE,
|
|
@@ -2576,12 +3385,19 @@ export {
|
|
|
2576
3385
|
I,
|
|
2577
3386
|
MATHTS_CONVERSIONS,
|
|
2578
3387
|
MATHTS_TYPES,
|
|
3388
|
+
SI_PREFIXES,
|
|
2579
3389
|
TypeRegistry,
|
|
3390
|
+
UNIT_ALIASES,
|
|
3391
|
+
Unit,
|
|
3392
|
+
UnitParseError,
|
|
2580
3393
|
VERSION,
|
|
2581
3394
|
create,
|
|
2582
3395
|
createFactory,
|
|
2583
3396
|
createMathTSTyped,
|
|
2584
3397
|
createTypedFunction,
|
|
3398
|
+
dim,
|
|
3399
|
+
getPrefix,
|
|
3400
|
+
getUnitDef,
|
|
2585
3401
|
initTypedWasm,
|
|
2586
3402
|
isArray,
|
|
2587
3403
|
isBigInt,
|
|
@@ -2599,7 +3415,8 @@ export {
|
|
|
2599
3415
|
isString,
|
|
2600
3416
|
isTypedWasmAvailable,
|
|
2601
3417
|
isUndefined,
|
|
2602
|
-
isUnit,
|
|
3418
|
+
isUnit2 as isUnit,
|
|
3419
|
+
isUnit as isUnitValue,
|
|
2603
3420
|
math,
|
|
2604
3421
|
mathTyped,
|
|
2605
3422
|
registerNativeTypes,
|