measurable 2.0.0 → 3.1.0
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/CHANGELOG.md +84 -0
- package/README.md +129 -27
- package/dist/dimensions/angle.js +12 -5
- package/dist/dimensions/area.js +37 -16
- package/dist/dimensions/data.js +11 -8
- package/dist/dimensions/energy.js +18 -11
- package/dist/dimensions/force.js +5 -5
- package/dist/dimensions/frequency.js +2 -2
- package/dist/dimensions/illuminance.js +7 -4
- package/dist/dimensions/length.js +6 -6
- package/dist/dimensions/luminance.js +5 -8
- package/dist/dimensions/luminousIntensity.js +7 -7
- package/dist/dimensions/mass.js +12 -8
- package/dist/dimensions/power.js +4 -4
- package/dist/dimensions/pressure.js +12 -9
- package/dist/dimensions/temperature.js +3 -3
- package/dist/dimensions/time.js +18 -6
- package/dist/dimensions/volume.js +56 -23
- package/dist/errors/AmbiguousUnitError.js +1 -2
- package/dist/errors/ArgumentError.d.ts +7 -0
- package/dist/errors/ArgumentError.js +11 -0
- package/dist/errors/DimensionMismatchError.d.ts +13 -0
- package/dist/errors/DimensionMismatchError.js +16 -0
- package/dist/errors/DuplicateUnitError.d.ts +5 -0
- package/dist/errors/DuplicateUnitError.js +10 -0
- package/dist/errors/ParseError.d.ts +4 -0
- package/dist/errors/ParseError.js +10 -0
- package/dist/errors/UnsupportedDimensionError.d.ts +10 -0
- package/dist/errors/UnsupportedDimensionError.js +14 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/lib/Dimension.d.ts +14 -5
- package/dist/lib/Dimension.js +22 -19
- package/dist/lib/MeasurementSystem.js +4 -15
- package/dist/lib/Quantity.d.ts +81 -3
- package/dist/lib/Quantity.js +82 -4
- package/dist/lib/Rational.d.ts +1 -1
- package/dist/lib/Rational.js +6 -5
- package/dist/lib/Unit.d.ts +18 -11
- package/dist/lib/Unit.js +7 -3
- package/dist/utils/definePrefixed.d.ts +8 -15
- package/dist/utils/definePrefixed.js +21 -10
- package/package.json +8 -3
package/dist/lib/Quantity.d.ts
CHANGED
|
@@ -7,6 +7,45 @@ export interface ParseOptions {
|
|
|
7
7
|
/** Preferred measurement system, used only to break ties on shared aliases. */
|
|
8
8
|
prefer?: MeasurementSystem;
|
|
9
9
|
}
|
|
10
|
+
/** Options for {@link Quantity.format}. */
|
|
11
|
+
export interface FormatOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Which label to render after the magnitude:
|
|
14
|
+
* - `"auto"` (default) — singular `name` when the magnitude is exactly ±1,
|
|
15
|
+
* otherwise the `plural`.
|
|
16
|
+
* - `"name"` — always the singular name.
|
|
17
|
+
* - `"plural"` — always the plural.
|
|
18
|
+
* - `"symbol"` — the unit's symbol.
|
|
19
|
+
*
|
|
20
|
+
* `plural`/`symbol` fall back to the unit's `name` when that field is unset.
|
|
21
|
+
* Labels are English/canonical; localize the *magnitude* via {@link locale} /
|
|
22
|
+
* {@link numberFormat}.
|
|
23
|
+
*/
|
|
24
|
+
unit?: "auto" | "name" | "plural" | "symbol";
|
|
25
|
+
/**
|
|
26
|
+
* BCP 47 locale(s) used to render the magnitude (e.g. `"de-DE"`, `["fr", "en"]`).
|
|
27
|
+
* Passed straight to `Number.prototype.toLocaleString`. When omitted, the
|
|
28
|
+
* runtime's default locale is used.
|
|
29
|
+
*/
|
|
30
|
+
locale?: string | string[];
|
|
31
|
+
/**
|
|
32
|
+
* `Intl.NumberFormat` options for the magnitude — precision
|
|
33
|
+
* (`minimumFractionDigits` / `maximumFractionDigits`), `style: "currency"`,
|
|
34
|
+
* grouping, and so on. Passed straight to `Number.prototype.toLocaleString`.
|
|
35
|
+
*/
|
|
36
|
+
numberFormat?: Intl.NumberFormatOptions;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The rendered pieces of a formatted quantity, as returned by
|
|
40
|
+
* {@link Quantity.formatParts}. Each is already a finished string; the caller
|
|
41
|
+
* decides how to assemble them (a plain join, JSX, a template, …).
|
|
42
|
+
*/
|
|
43
|
+
export interface FormattedParts {
|
|
44
|
+
/** The locale-formatted magnitude, e.g. `"1.234,5"`. */
|
|
45
|
+
magnitude: string;
|
|
46
|
+
/** The chosen unit label, e.g. `"kilometers"`, `"km"`. */
|
|
47
|
+
unit: string;
|
|
48
|
+
}
|
|
10
49
|
/** A magnitude paired with a unit (e.g. `5` `kilometer`). */
|
|
11
50
|
export declare class Quantity {
|
|
12
51
|
readonly unit: Unit;
|
|
@@ -28,12 +67,51 @@ export declare class Quantity {
|
|
|
28
67
|
in(target: Unit): number;
|
|
29
68
|
/** This quantity's exact magnitude expressed in `target`. */
|
|
30
69
|
private inRational;
|
|
70
|
+
/**
|
|
71
|
+
* Re-express this quantity in the best-fit unit among `units`: the largest
|
|
72
|
+
* unit whose absolute magnitude is still at least 1 (falling back to the
|
|
73
|
+
* smallest unit when even that rounds below 1). Requires at least one unit,
|
|
74
|
+
* and each must belong to this quantity's dimension (else
|
|
75
|
+
* {@link DimensionMismatchError}).
|
|
76
|
+
*/
|
|
77
|
+
best(...units: Unit[]): Quantity;
|
|
31
78
|
/** Render as `"<magnitude> <unit name>"`, e.g. `"5 kilometer"`. */
|
|
32
79
|
toString(): string;
|
|
80
|
+
/**
|
|
81
|
+
* Render as `"<magnitude> <label>"`, choosing the label per `options.unit`
|
|
82
|
+
* (default `"auto"`: magnitude-aware singular/plural). Unlike {@link toString},
|
|
83
|
+
* this can use the unit's symbol or plural — e.g. `"5 grams"`, `"5 g"`,
|
|
84
|
+
* `"1 gram"`. Pass `locale` / `numberFormat` to localize the magnitude via
|
|
85
|
+
* `toLocaleString` — e.g. `format({ locale: "de-DE" })` → `"1.234,5 meters"`,
|
|
86
|
+
* or `format({ numberFormat: { maximumFractionDigits: 2 } })` for precision.
|
|
87
|
+
*
|
|
88
|
+
* For non-string output (e.g. JSX), use {@link formatParts} and assemble the
|
|
89
|
+
* pieces yourself.
|
|
90
|
+
*/
|
|
91
|
+
format(options?: FormatOptions): string;
|
|
92
|
+
/**
|
|
93
|
+
* Like {@link format}, but returns the rendered magnitude and label as
|
|
94
|
+
* separate strings instead of joining them, so the caller controls the
|
|
95
|
+
* assembly. Useful when a single string won't do — e.g. styling the magnitude
|
|
96
|
+
* in a React component:
|
|
97
|
+
*
|
|
98
|
+
* ```tsx
|
|
99
|
+
* const { magnitude, unit } = q.formatParts({ locale: "de-DE" });
|
|
100
|
+
* return <><b>{magnitude}</b> {unit}</>;
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
formatParts(options?: FormatOptions): FormattedParts;
|
|
104
|
+
/**
|
|
105
|
+
* Render the magnitude via `toLocaleString`. With no `locale`/`numberFormat`
|
|
106
|
+
* it uses the runtime's default locale; supply either for locale- and
|
|
107
|
+
* precision-aware formatting.
|
|
108
|
+
*/
|
|
109
|
+
private formatMagnitude;
|
|
110
|
+
private formatLabel;
|
|
33
111
|
/**
|
|
34
112
|
* Add another quantity, returned in *this* quantity's unit. The other operand
|
|
35
113
|
* is converted into this unit first, so the two may use different units of the
|
|
36
|
-
* same dimension (e.g. `mile.plus(km)`). Throws {@link
|
|
114
|
+
* same dimension (e.g. `mile.plus(km)`). Throws {@link DimensionMismatchError}
|
|
37
115
|
* if the operands belong to different dimensions.
|
|
38
116
|
*
|
|
39
117
|
* Note: for affine units (e.g. temperature) addition is mathematically defined
|
|
@@ -51,7 +129,7 @@ export declare class Quantity {
|
|
|
51
129
|
* Divide this quantity by `other` of the same dimension, yielding the
|
|
52
130
|
* dimensionless ratio between them — i.e. how many of `other` fit in this.
|
|
53
131
|
* Unlike {@link in}, this accounts for `other`'s magnitude, not just its unit.
|
|
54
|
-
* Throws {@link
|
|
132
|
+
* Throws {@link DimensionMismatchError} across dimensions.
|
|
55
133
|
*/
|
|
56
134
|
ratioTo(other: Quantity): number;
|
|
57
135
|
/** Return this quantity with its magnitude negated. */
|
|
@@ -72,7 +150,7 @@ export declare class Quantity {
|
|
|
72
150
|
div(divisor: number | Rational): Quantity;
|
|
73
151
|
/**
|
|
74
152
|
* Whether this quantity equals `other`, compared in this quantity's unit.
|
|
75
|
-
* Throws {@link
|
|
153
|
+
* Throws {@link DimensionMismatchError} if the operands belong to different
|
|
76
154
|
* dimensions. Comparison is exact rational equality, so quantities that are
|
|
77
155
|
* mathematically equal compare equal even if reaching them involved a
|
|
78
156
|
* conversion that would have drifted in floating point.
|
package/dist/lib/Quantity.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Quantity = void 0;
|
|
4
|
+
const assert_never_1 = require("assert-never");
|
|
4
5
|
const AmbiguousUnitError_1 = require("../errors/AmbiguousUnitError");
|
|
6
|
+
const ArgumentError_1 = require("../errors/ArgumentError");
|
|
7
|
+
const ParseError_1 = require("../errors/ParseError");
|
|
5
8
|
const UnknownUnitError_1 = require("../errors/UnknownUnitError");
|
|
6
9
|
const scaleOf_1 = require("../utils/scaleOf");
|
|
7
10
|
const Rational_1 = require("./Rational");
|
|
@@ -27,14 +30,89 @@ class Quantity {
|
|
|
27
30
|
inRational(target) {
|
|
28
31
|
return this.unit.dimension.convertRational(this.rational, this.unit, target);
|
|
29
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Re-express this quantity in the best-fit unit among `units`: the largest
|
|
35
|
+
* unit whose absolute magnitude is still at least 1 (falling back to the
|
|
36
|
+
* smallest unit when even that rounds below 1). Requires at least one unit,
|
|
37
|
+
* and each must belong to this quantity's dimension (else
|
|
38
|
+
* {@link DimensionMismatchError}).
|
|
39
|
+
*/
|
|
40
|
+
best(...units) {
|
|
41
|
+
const candidates = [...units].sort((a, b) => (0, scaleOf_1.scaleOf)(a) - (0, scaleOf_1.scaleOf)(b));
|
|
42
|
+
if (candidates.length === 0) {
|
|
43
|
+
throw new ArgumentError_1.ArgumentError("Quantity.best requires at least one unit");
|
|
44
|
+
}
|
|
45
|
+
let chosen = candidates[0];
|
|
46
|
+
for (const unit of candidates) {
|
|
47
|
+
if (Math.abs(this.in(unit)) >= 1) {
|
|
48
|
+
chosen = unit;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return this.to(chosen);
|
|
55
|
+
}
|
|
30
56
|
/** Render as `"<magnitude> <unit name>"`, e.g. `"5 kilometer"`. */
|
|
31
57
|
toString() {
|
|
32
58
|
return `${this.magnitude} ${this.unit.name}`;
|
|
33
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Render as `"<magnitude> <label>"`, choosing the label per `options.unit`
|
|
62
|
+
* (default `"auto"`: magnitude-aware singular/plural). Unlike {@link toString},
|
|
63
|
+
* this can use the unit's symbol or plural — e.g. `"5 grams"`, `"5 g"`,
|
|
64
|
+
* `"1 gram"`. Pass `locale` / `numberFormat` to localize the magnitude via
|
|
65
|
+
* `toLocaleString` — e.g. `format({ locale: "de-DE" })` → `"1.234,5 meters"`,
|
|
66
|
+
* or `format({ numberFormat: { maximumFractionDigits: 2 } })` for precision.
|
|
67
|
+
*
|
|
68
|
+
* For non-string output (e.g. JSX), use {@link formatParts} and assemble the
|
|
69
|
+
* pieces yourself.
|
|
70
|
+
*/
|
|
71
|
+
format(options = {}) {
|
|
72
|
+
const { magnitude, unit } = this.formatParts(options);
|
|
73
|
+
return `${magnitude} ${unit}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Like {@link format}, but returns the rendered magnitude and label as
|
|
77
|
+
* separate strings instead of joining them, so the caller controls the
|
|
78
|
+
* assembly. Useful when a single string won't do — e.g. styling the magnitude
|
|
79
|
+
* in a React component:
|
|
80
|
+
*
|
|
81
|
+
* ```tsx
|
|
82
|
+
* const { magnitude, unit } = q.formatParts({ locale: "de-DE" });
|
|
83
|
+
* return <><b>{magnitude}</b> {unit}</>;
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
formatParts(options = {}) {
|
|
87
|
+
return { magnitude: this.formatMagnitude(options), unit: this.formatLabel(options) };
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Render the magnitude via `toLocaleString`. With no `locale`/`numberFormat`
|
|
91
|
+
* it uses the runtime's default locale; supply either for locale- and
|
|
92
|
+
* precision-aware formatting.
|
|
93
|
+
*/
|
|
94
|
+
formatMagnitude({ locale, numberFormat }) {
|
|
95
|
+
return this.magnitude.toLocaleString(locale, numberFormat);
|
|
96
|
+
}
|
|
97
|
+
formatLabel({ unit = "auto" }) {
|
|
98
|
+
const { name, symbol, plural } = this.unit;
|
|
99
|
+
switch (unit) {
|
|
100
|
+
case "symbol":
|
|
101
|
+
return symbol ?? name;
|
|
102
|
+
case "name":
|
|
103
|
+
return name;
|
|
104
|
+
case "plural":
|
|
105
|
+
return plural ?? name;
|
|
106
|
+
case "auto":
|
|
107
|
+
return Math.abs(this.magnitude) === 1 ? name : (plural ?? name);
|
|
108
|
+
default:
|
|
109
|
+
return (0, assert_never_1.assertNever)(unit);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
34
112
|
/**
|
|
35
113
|
* Add another quantity, returned in *this* quantity's unit. The other operand
|
|
36
114
|
* is converted into this unit first, so the two may use different units of the
|
|
37
|
-
* same dimension (e.g. `mile.plus(km)`). Throws {@link
|
|
115
|
+
* same dimension (e.g. `mile.plus(km)`). Throws {@link DimensionMismatchError}
|
|
38
116
|
* if the operands belong to different dimensions.
|
|
39
117
|
*
|
|
40
118
|
* Note: for affine units (e.g. temperature) addition is mathematically defined
|
|
@@ -60,7 +138,7 @@ class Quantity {
|
|
|
60
138
|
* Divide this quantity by `other` of the same dimension, yielding the
|
|
61
139
|
* dimensionless ratio between them — i.e. how many of `other` fit in this.
|
|
62
140
|
* Unlike {@link in}, this accounts for `other`'s magnitude, not just its unit.
|
|
63
|
-
* Throws {@link
|
|
141
|
+
* Throws {@link DimensionMismatchError} across dimensions.
|
|
64
142
|
*/
|
|
65
143
|
ratioTo(other) {
|
|
66
144
|
return this.rational.dividedBy(other.inRational(this.unit)).toNumber();
|
|
@@ -106,7 +184,7 @@ class Quantity {
|
|
|
106
184
|
}
|
|
107
185
|
/**
|
|
108
186
|
* Whether this quantity equals `other`, compared in this quantity's unit.
|
|
109
|
-
* Throws {@link
|
|
187
|
+
* Throws {@link DimensionMismatchError} if the operands belong to different
|
|
110
188
|
* dimensions. Comparison is exact rational equality, so quantities that are
|
|
111
189
|
* mathematically equal compare equal even if reaching them involved a
|
|
112
190
|
* conversion that would have drifted in floating point.
|
|
@@ -204,7 +282,7 @@ class Quantity {
|
|
|
204
282
|
}
|
|
205
283
|
}
|
|
206
284
|
if (!total || !finest) {
|
|
207
|
-
throw new
|
|
285
|
+
throw new ParseError_1.ParseError(str);
|
|
208
286
|
}
|
|
209
287
|
return total.to(finest);
|
|
210
288
|
}
|
package/dist/lib/Rational.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ export declare class Rational {
|
|
|
16
16
|
/**
|
|
17
17
|
* Build a rational from integer numerator and denominator. Pass exact ratios
|
|
18
18
|
* the literal way — `new Rational(5, 9)` — using `bigint` or integer `number`.
|
|
19
|
-
* For a decimal value, use {@link Rational.
|
|
19
|
+
* For a decimal value, use {@link Rational.from} instead.
|
|
20
20
|
*/
|
|
21
21
|
constructor(numerator: bigint | number, denominator?: bigint | number);
|
|
22
22
|
/** Coerce a `number | Rational` to a `Rational`, parsing numbers as decimals. */
|
package/dist/lib/Rational.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Rational = void 0;
|
|
4
|
+
const ArgumentError_1 = require("../errors/ArgumentError");
|
|
4
5
|
/**
|
|
5
6
|
* An exact rational number (`n / d`) used to make conversions between linear
|
|
6
7
|
* and affine units lossless. Such conversions are inherently rational — a foot
|
|
@@ -17,13 +18,13 @@ class Rational {
|
|
|
17
18
|
/**
|
|
18
19
|
* Build a rational from integer numerator and denominator. Pass exact ratios
|
|
19
20
|
* the literal way — `new Rational(5, 9)` — using `bigint` or integer `number`.
|
|
20
|
-
* For a decimal value, use {@link Rational.
|
|
21
|
+
* For a decimal value, use {@link Rational.from} instead.
|
|
21
22
|
*/
|
|
22
23
|
constructor(numerator, denominator = 1n) {
|
|
23
24
|
let n = toBigInt(numerator);
|
|
24
25
|
let d = toBigInt(denominator);
|
|
25
26
|
if (d === 0n) {
|
|
26
|
-
throw new
|
|
27
|
+
throw new ArgumentError_1.ArgumentError("Rational denominator cannot be zero");
|
|
27
28
|
}
|
|
28
29
|
if (d < 0n) {
|
|
29
30
|
n = -n;
|
|
@@ -48,11 +49,11 @@ class Rational {
|
|
|
48
49
|
*/
|
|
49
50
|
static fromNumber(value) {
|
|
50
51
|
if (!Number.isFinite(value)) {
|
|
51
|
-
throw new
|
|
52
|
+
throw new ArgumentError_1.ArgumentError(`Cannot derive a rational from ${value}`);
|
|
52
53
|
}
|
|
53
54
|
const match = /^(-?)(\d+)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/.exec(value.toString());
|
|
54
55
|
if (!match) {
|
|
55
|
-
throw new
|
|
56
|
+
throw new ArgumentError_1.ArgumentError(`Cannot derive a rational from ${value}`);
|
|
56
57
|
}
|
|
57
58
|
const [, sign, intPart, fracPart = "", expPart] = match;
|
|
58
59
|
let n = BigInt(intPart + fracPart);
|
|
@@ -168,7 +169,7 @@ function toBigInt(value) {
|
|
|
168
169
|
return value;
|
|
169
170
|
}
|
|
170
171
|
if (!Number.isInteger(value)) {
|
|
171
|
-
throw new
|
|
172
|
+
throw new ArgumentError_1.ArgumentError(`Rational numerator and denominator must be integers; got ${value}`);
|
|
172
173
|
}
|
|
173
174
|
return BigInt(value);
|
|
174
175
|
}
|
package/dist/lib/Unit.d.ts
CHANGED
|
@@ -9,21 +9,23 @@ export interface LinearTransform {
|
|
|
9
9
|
scale: Rational;
|
|
10
10
|
offset: Rational;
|
|
11
11
|
}
|
|
12
|
-
interface BaseUnitOptions {
|
|
12
|
+
export interface BaseUnitOptions {
|
|
13
13
|
name: string;
|
|
14
14
|
dimension: Dimension;
|
|
15
|
+
/** Canonical symbol, e.g. `"g"`, `"km"`, `"°C"` (optional). */
|
|
16
|
+
symbol?: string;
|
|
17
|
+
/** Plural name, e.g. `"grams"`, `"kilometers"` (optional). */
|
|
18
|
+
plural?: string;
|
|
15
19
|
}
|
|
16
|
-
|
|
20
|
+
export type UnitConversionOptions = {
|
|
17
21
|
/** Exact transform for linear / affine units (the common case). */
|
|
18
22
|
linear: LinearTransform;
|
|
19
|
-
}
|
|
20
|
-
interface CustomConversionOptions {
|
|
23
|
+
} | {
|
|
21
24
|
/** Hand-written transform for non-linear units; mutually exclusive with `linear`. */
|
|
22
25
|
toBase: (value: number) => number;
|
|
23
26
|
fromBase: (value: number) => number;
|
|
24
|
-
}
|
|
25
|
-
export type
|
|
26
|
-
type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
27
|
+
};
|
|
28
|
+
export type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
27
29
|
/**
|
|
28
30
|
* A single unit of measurement (e.g. "meter", "celsius").
|
|
29
31
|
*
|
|
@@ -44,8 +46,10 @@ type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
|
44
46
|
* {@link MeasurementSystem}s (metric/imperial/…); that membership lives on the
|
|
45
47
|
* measurement systems, not here, so a `Unit` stays a lean descriptor.
|
|
46
48
|
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
+
* Parsing aliases live in the dimension's lookup index. A unit additionally
|
|
50
|
+
* carries its canonical {@link symbol} and {@link plural} as first-class data
|
|
51
|
+
* so callers can choose how to render it (see {@link Quantity.format}); both are
|
|
52
|
+
* optional and English-centric — real localization is left to the consumer.
|
|
49
53
|
*
|
|
50
54
|
* Units are normally created through a {@link Dimension}'s builder methods
|
|
51
55
|
* (`base`, `unit`, `affine`, `custom`) rather than constructed directly.
|
|
@@ -53,8 +57,12 @@ type UnitOptions = BaseUnitOptions & UnitConversionOptions;
|
|
|
53
57
|
export declare class Unit {
|
|
54
58
|
readonly name: string;
|
|
55
59
|
readonly dimension: Dimension;
|
|
60
|
+
/** Canonical symbol, e.g. `"g"`, `"km"`, `"°C"` (optional). */
|
|
61
|
+
readonly symbol?: string;
|
|
62
|
+
/** Plural name, e.g. `"grams"`, `"kilometers"` (optional). */
|
|
63
|
+
readonly plural?: string;
|
|
56
64
|
private readonly conversion;
|
|
57
|
-
constructor({ name, dimension, ...conversionOptions }: UnitOptions);
|
|
65
|
+
constructor({ name, dimension, symbol, plural, ...conversionOptions }: UnitOptions);
|
|
58
66
|
/**
|
|
59
67
|
* Exact affine transform to the base unit, present for all but non-linear
|
|
60
68
|
* units. When both ends of a conversion have one, {@link Dimension.convert}
|
|
@@ -66,4 +74,3 @@ export declare class Unit {
|
|
|
66
74
|
/** Convert a value in the dimension's base unit to this unit. */
|
|
67
75
|
fromBase(value: number): number;
|
|
68
76
|
}
|
|
69
|
-
export {};
|
package/dist/lib/Unit.js
CHANGED
|
@@ -21,16 +21,20 @@ exports.Unit = void 0;
|
|
|
21
21
|
* {@link MeasurementSystem}s (metric/imperial/…); that membership lives on the
|
|
22
22
|
* measurement systems, not here, so a `Unit` stays a lean descriptor.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* Parsing aliases live in the dimension's lookup index. A unit additionally
|
|
25
|
+
* carries its canonical {@link symbol} and {@link plural} as first-class data
|
|
26
|
+
* so callers can choose how to render it (see {@link Quantity.format}); both are
|
|
27
|
+
* optional and English-centric — real localization is left to the consumer.
|
|
26
28
|
*
|
|
27
29
|
* Units are normally created through a {@link Dimension}'s builder methods
|
|
28
30
|
* (`base`, `unit`, `affine`, `custom`) rather than constructed directly.
|
|
29
31
|
*/
|
|
30
32
|
class Unit {
|
|
31
|
-
constructor({ name, dimension, ...conversionOptions }) {
|
|
33
|
+
constructor({ name, dimension, symbol, plural, ...conversionOptions }) {
|
|
32
34
|
this.name = name;
|
|
33
35
|
this.dimension = dimension;
|
|
36
|
+
this.symbol = symbol;
|
|
37
|
+
this.plural = plural;
|
|
34
38
|
this.conversion = conversionOptions;
|
|
35
39
|
}
|
|
36
40
|
/**
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { Dimension } from "../lib/Dimension";
|
|
2
|
-
import { Rational } from "../lib/Rational";
|
|
3
2
|
import type { Unit } from "../lib/Unit";
|
|
4
3
|
/** A metric (SI) prefix: a name, symbol, and power-of-ten factor. */
|
|
5
4
|
export interface SiPrefix {
|
|
@@ -11,20 +10,14 @@ export interface SiPrefix {
|
|
|
11
10
|
export declare const SI_PREFIXES: readonly SiPrefix[];
|
|
12
11
|
/** SI prefixes for fractions only (deci and smaller) — for units like seconds. */
|
|
13
12
|
export declare const SI_SUBMULTIPLE_PREFIXES: readonly SiPrefix[];
|
|
14
|
-
/** The unit a set of metric prefixes is generated relative to. */
|
|
15
|
-
export interface PrefixReference {
|
|
16
|
-
/** Singular unit name, e.g. "meter" → "kilometer", "millimeter", … */
|
|
17
|
-
name: string;
|
|
18
|
-
/** Primary symbol, e.g. "m" → "km", "mm", … */
|
|
19
|
-
symbol: string;
|
|
20
|
-
/** Scale of the reference relative to its dimension's base (meter → 1, gram → 0.001) (default 1). */
|
|
21
|
-
scale?: number | Rational;
|
|
22
|
-
}
|
|
23
13
|
/**
|
|
24
|
-
* Define metric-prefixed variants of a reference unit on
|
|
25
|
-
* variant is named `${prefix}${reference.name}` (e.g. "kilometer")
|
|
26
|
-
* `reference
|
|
27
|
-
*
|
|
14
|
+
* Define metric-prefixed variants of a `reference` unit on its dimension. Each
|
|
15
|
+
* variant is named `${prefix}${reference.name}` (e.g. "kilometer"), scaled by
|
|
16
|
+
* `prefix.factor` relative to the reference (its own scale is read from the unit
|
|
17
|
+
* via `scaleOf`, so prefixing a non-base unit like the watt-hour works
|
|
18
|
+
* automatically). Each variant carries a generated `symbol`
|
|
19
|
+
* (`${prefix.symbol}${reference.symbol}`, when the reference has a symbol) and a
|
|
20
|
+
* `plural` (`${name}s`), plus an ASCII "u" alias for micro.
|
|
28
21
|
*
|
|
29
22
|
* Prefixes whose generated name already exists on the dimension are skipped, so
|
|
30
23
|
* a base like "kilogram" is left intact when prefixing "gram".
|
|
@@ -32,4 +25,4 @@ export interface PrefixReference {
|
|
|
32
25
|
* Returns the created units keyed by name, for spreading into a
|
|
33
26
|
* {@link MeasurementSystem} or destructuring into named exports.
|
|
34
27
|
*/
|
|
35
|
-
export declare function definePrefixed(dimension: Dimension, reference:
|
|
28
|
+
export declare function definePrefixed(dimension: Dimension, reference: Unit, prefixes?: readonly SiPrefix[]): Record<string, Unit>;
|
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.SI_SUBMULTIPLE_PREFIXES = exports.SI_PREFIXES = void 0;
|
|
4
4
|
exports.definePrefixed = definePrefixed;
|
|
5
5
|
const Rational_1 = require("../lib/Rational");
|
|
6
|
+
const scaleOf_1 = require("./scaleOf");
|
|
6
7
|
/** The full set of SI prefixes, yotta (1e24) down to yocto (1e-24). */
|
|
7
8
|
exports.SI_PREFIXES = [
|
|
8
9
|
{ name: "yotta", symbol: "Y", factor: 1e24 },
|
|
@@ -29,10 +30,13 @@ exports.SI_PREFIXES = [
|
|
|
29
30
|
/** SI prefixes for fractions only (deci and smaller) — for units like seconds. */
|
|
30
31
|
exports.SI_SUBMULTIPLE_PREFIXES = exports.SI_PREFIXES.filter((prefix) => prefix.factor < 1);
|
|
31
32
|
/**
|
|
32
|
-
* Define metric-prefixed variants of a reference unit on
|
|
33
|
-
* variant is named `${prefix}${reference.name}` (e.g. "kilometer")
|
|
34
|
-
* `reference
|
|
35
|
-
*
|
|
33
|
+
* Define metric-prefixed variants of a `reference` unit on its dimension. Each
|
|
34
|
+
* variant is named `${prefix}${reference.name}` (e.g. "kilometer"), scaled by
|
|
35
|
+
* `prefix.factor` relative to the reference (its own scale is read from the unit
|
|
36
|
+
* via `scaleOf`, so prefixing a non-base unit like the watt-hour works
|
|
37
|
+
* automatically). Each variant carries a generated `symbol`
|
|
38
|
+
* (`${prefix.symbol}${reference.symbol}`, when the reference has a symbol) and a
|
|
39
|
+
* `plural` (`${name}s`), plus an ASCII "u" alias for micro.
|
|
36
40
|
*
|
|
37
41
|
* Prefixes whose generated name already exists on the dimension are skipped, so
|
|
38
42
|
* a base like "kilogram" is left intact when prefixing "gram".
|
|
@@ -41,21 +45,28 @@ exports.SI_SUBMULTIPLE_PREFIXES = exports.SI_PREFIXES.filter((prefix) => prefix.
|
|
|
41
45
|
* {@link MeasurementSystem} or destructuring into named exports.
|
|
42
46
|
*/
|
|
43
47
|
function definePrefixed(dimension, reference, prefixes = exports.SI_PREFIXES) {
|
|
48
|
+
// Prefer the reference's exact rational scale so the prefixed scale stays
|
|
49
|
+
// exact; fall back to its float scale only for non-linear references.
|
|
50
|
+
const referenceScale = reference.linear
|
|
51
|
+
? reference.linear.scale
|
|
52
|
+
: Rational_1.Rational.from((0, scaleOf_1.scaleOf)(reference));
|
|
44
53
|
const units = {};
|
|
45
54
|
for (const prefix of prefixes) {
|
|
46
55
|
const name = `${prefix.name}${reference.name}`;
|
|
47
56
|
if (dimension.get(name)) {
|
|
48
57
|
continue;
|
|
49
58
|
}
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
aliases.push(`u${reference.symbol}`);
|
|
53
|
-
}
|
|
59
|
+
const symbol = reference.symbol ? `${prefix.symbol}${reference.symbol}` : undefined;
|
|
60
|
+
const aliases = prefix.name === "micro" && reference.symbol ? [`u${reference.symbol}`] : [];
|
|
54
61
|
// Multiply as rationals: each factor (a power of ten, or an exact reference
|
|
55
62
|
// scale) is lossless on its own, but multiplying them as floats can drift
|
|
56
63
|
// (e.g. 3600 * 1e-9). Rational multiplication keeps the prefixed scale exact.
|
|
57
|
-
const scale =
|
|
58
|
-
units[name] = dimension.unit(name, scale,
|
|
64
|
+
const scale = referenceScale.times(Rational_1.Rational.from(prefix.factor));
|
|
65
|
+
units[name] = dimension.unit(name, scale, {
|
|
66
|
+
symbol,
|
|
67
|
+
plural: `${name}s`,
|
|
68
|
+
aliases,
|
|
69
|
+
});
|
|
59
70
|
}
|
|
60
71
|
return units;
|
|
61
72
|
}
|