measurable 1.1.1 → 3.0.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +149 -0
  2. package/README.md +271 -53
  3. package/dist/dimensions/angle.js +13 -6
  4. package/dist/dimensions/area.d.ts +14 -0
  5. package/dist/dimensions/area.js +46 -0
  6. package/dist/dimensions/data.d.ts +10 -0
  7. package/dist/dimensions/data.js +39 -0
  8. package/dist/dimensions/energy.d.ts +14 -0
  9. package/dist/dimensions/energy.js +29 -0
  10. package/dist/dimensions/force.js +6 -6
  11. package/dist/dimensions/frequency.d.ts +7 -0
  12. package/dist/dimensions/frequency.js +11 -0
  13. package/dist/dimensions/illuminance.d.ts +9 -0
  14. package/dist/dimensions/illuminance.js +16 -0
  15. package/dist/dimensions/index.d.ts +9 -0
  16. package/dist/dimensions/index.js +9 -0
  17. package/dist/dimensions/length.js +7 -7
  18. package/dist/dimensions/luminance.d.ts +7 -0
  19. package/dist/dimensions/luminance.js +13 -0
  20. package/dist/dimensions/luminousIntensity.d.ts +9 -0
  21. package/dist/dimensions/luminousIntensity.js +16 -0
  22. package/dist/dimensions/mass.js +13 -9
  23. package/dist/dimensions/power.d.ts +9 -0
  24. package/dist/dimensions/power.js +13 -0
  25. package/dist/dimensions/pressure.d.ts +14 -0
  26. package/dist/dimensions/pressure.js +21 -0
  27. package/dist/dimensions/temperature.js +9 -3
  28. package/dist/dimensions/time.js +19 -7
  29. package/dist/dimensions/volume.js +57 -24
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/lib/Dimension.d.ts +49 -14
  33. package/dist/lib/Dimension.js +58 -21
  34. package/dist/lib/MeasurementSystem.js +2 -2
  35. package/dist/lib/Quantity.d.ts +94 -10
  36. package/dist/lib/Quantity.js +92 -37
  37. package/dist/lib/Rational.d.ts +58 -0
  38. package/dist/lib/Rational.js +174 -0
  39. package/dist/lib/Unit.d.ts +52 -11
  40. package/dist/lib/Unit.js +41 -8
  41. package/dist/lib/prefixes.d.ts +2 -2
  42. package/dist/lib/prefixes.js +1 -1
  43. package/dist/systems/imperial.js +19 -1
  44. package/dist/systems/metric.js +25 -1
  45. package/dist/systems/usCustomary.js +19 -1
  46. package/dist/utils/definePrefixed.d.ts +28 -0
  47. package/dist/utils/definePrefixed.js +72 -0
  48. package/dist/utils/scaleOf.d.ts +3 -0
  49. package/dist/utils/scaleOf.js +6 -0
  50. package/package.json +13 -4
package/dist/index.d.ts CHANGED
@@ -3,6 +3,7 @@ export * from "./errors/InvalidConversionError";
3
3
  export * from "./errors/UnknownUnitError";
4
4
  export * from "./lib/Dimension";
5
5
  export * from "./lib/MeasurementSystem";
6
- export * from "./lib/prefixes";
7
6
  export * from "./lib/Quantity";
7
+ export * from "./lib/Rational";
8
8
  export * from "./lib/Unit";
9
+ export * from "./utils/definePrefixed";
package/dist/index.js CHANGED
@@ -19,6 +19,7 @@ __exportStar(require("./errors/InvalidConversionError"), exports);
19
19
  __exportStar(require("./errors/UnknownUnitError"), exports);
20
20
  __exportStar(require("./lib/Dimension"), exports);
21
21
  __exportStar(require("./lib/MeasurementSystem"), exports);
22
- __exportStar(require("./lib/prefixes"), exports);
23
22
  __exportStar(require("./lib/Quantity"), exports);
23
+ __exportStar(require("./lib/Rational"), exports);
24
24
  __exportStar(require("./lib/Unit"), exports);
25
+ __exportStar(require("./utils/definePrefixed"), exports);
@@ -1,26 +1,44 @@
1
+ import { Rational } from "./Rational";
1
2
  import { Unit } from "./Unit";
2
3
  /** A linear unit with an additive offset (e.g. temperature scales). */
3
4
  export interface AffineSpec {
4
- /** Multiplier applied when converting a value to the base unit. */
5
- scale: number;
5
+ /**
6
+ * Multiplier applied when converting a value to the base unit. Pass a
7
+ * {@link Rational} (e.g. `new Rational(5, 9)`) for ratios that a decimal
8
+ * cannot represent exactly.
9
+ */
10
+ scale: number | Rational;
6
11
  /** Constant added (in base units) after scaling. */
7
- offset: number;
12
+ offset: number | Rational;
8
13
  }
9
14
  /** A fully custom transform pair for non-linear units. */
10
15
  export interface CustomSpec {
11
16
  toBase: (value: number) => number;
12
17
  fromBase: (value: number) => number;
13
18
  }
19
+ /** Optional descriptors for a unit: its symbol, plural, and extra parse aliases. */
20
+ export interface UnitDef {
21
+ /** Canonical symbol, e.g. `"g"`, `"km"`, `"°C"`. Also registered for parsing. */
22
+ symbol?: string;
23
+ /** Plural name, e.g. `"grams"`. Also registered for parsing. */
24
+ plural?: string;
25
+ /** Additional names this unit parses from (beyond name, symbol, and plural). */
26
+ aliases?: string[];
27
+ }
14
28
  /**
15
29
  * A dimension is a single *kind* of measurable quantity (length, volume, mass,
16
30
  * temperature, …). It owns one canonical **base unit** that every other unit in
17
31
  * the dimension is defined relative to, and it is the single place where all
18
32
  * conversion math happens.
19
33
  *
20
- * Converting `A → B` is always `B.fromBase(A.toBase(value))`: route through the
21
- * base unit. This gives transitive conversions for free (any unit ↔ any unit)
22
- * and means each unit only ever stores its relationship to the base — never a
23
- * redundant, drift-prone pair of factors.
34
+ * Converting `A → B` routes through the base unit — conceptually
35
+ * `B.fromBase(A.toBase(value))`. This gives transitive conversions for free
36
+ * (any unit ↔ any unit) and means each unit only ever stores its relationship
37
+ * to the base — never a redundant, drift-prone pair of factors. Linear and
38
+ * affine units carry that relationship as an exact {@link Rational} transform,
39
+ * so their conversions are done in rational arithmetic and collapsed to a float
40
+ * once at the end, avoiding the binary rounding of routing through the base.
41
+ * Only non-linear units (defined via {@link custom}) fall back to float.
24
42
  *
25
43
  * A dimension is distinct from a {@link MeasurementSystem} (metric/imperial/…):
26
44
  * the dimension decides what *can convert*, while a measurement system is a tag
@@ -39,21 +57,38 @@ export declare class Dimension {
39
57
  baseUnit?: Unit;
40
58
  constructor(name: string);
41
59
  /** Define the canonical base unit (identity transform). */
42
- base(name: string, aliases?: string[]): Unit;
60
+ base(name: string, def?: UnitDef): Unit;
43
61
  /**
44
62
  * Define a linear unit. `scale` is how many base units make up one of this
45
- * unit (e.g. a kilometer is `1000` meters).
63
+ * unit (e.g. a kilometer is `1000` meters). Pass a {@link Rational} for a
64
+ * scale a decimal cannot represent exactly.
46
65
  */
47
- unit(name: string, scale: number, aliases?: string[]): Unit;
66
+ unit(name: string, scale: number | Rational, def?: UnitDef): Unit;
48
67
  /** Define an affine unit (scale plus additive offset, e.g. °C against K). */
49
- affine(name: string, { scale, offset }: AffineSpec, aliases?: string[]): Unit;
50
- /** Define a unit with an arbitrary, hand-written inverse transform pair. */
51
- custom(name: string, { toBase, fromBase }: CustomSpec, aliases?: string[]): Unit;
52
- /** Convert a value between two units of this dimension, routed through the base. */
68
+ affine(name: string, { scale, offset }: AffineSpec, def?: UnitDef): Unit;
69
+ /**
70
+ * Define a non-linear unit from an arbitrary, hand-written inverse transform
71
+ * pair. Reserve this for units that genuinely cannot be expressed as `value *
72
+ * scale + offset` — e.g. logarithmic scales (decibels, octaves). Linear and
73
+ * affine units should use {@link unit} / {@link affine} so conversions stay
74
+ * exact.
75
+ */
76
+ custom(name: string, { toBase, fromBase }: CustomSpec, def?: UnitDef): Unit;
77
+ /** Convert a `number` value between two units of this dimension. */
53
78
  convert(value: number, from: Unit, to: Unit): number;
79
+ /**
80
+ * Convert an exact {@link Rational} value between two units, routed through
81
+ * the base. Linear / affine units stay exact end-to-end (the result is never
82
+ * collapsed to a float here), which is what lets {@link Quantity} chain
83
+ * conversions without drift. A conversion touching a non-linear `custom` unit
84
+ * falls back to float math and recaptures the result as a rational.
85
+ */
86
+ convertRational(value: Rational, from: Unit, to: Unit): Rational;
54
87
  /** Resolve a name or alias to its candidate unit(s) (used by parsing). */
55
88
  get(token: string): Unit[] | undefined;
56
89
  has(unit: Unit): boolean;
90
+ /** Define a linear / affine unit from its exact rational transform. */
91
+ private defineLinear;
57
92
  private define;
58
93
  /** Append a name/alias → unit mapping; shared aliases accumulate candidates. */
59
94
  private register;
@@ -2,17 +2,26 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Dimension = void 0;
4
4
  const InvalidConversionError_1 = require("../errors/InvalidConversionError");
5
+ const Rational_1 = require("./Rational");
5
6
  const Unit_1 = require("./Unit");
7
+ /** The rational `0`. */
8
+ const zero = new Rational_1.Rational(0n);
9
+ /** The rational `1`. */
10
+ const one = new Rational_1.Rational(1n);
6
11
  /**
7
12
  * A dimension is a single *kind* of measurable quantity (length, volume, mass,
8
13
  * temperature, …). It owns one canonical **base unit** that every other unit in
9
14
  * the dimension is defined relative to, and it is the single place where all
10
15
  * conversion math happens.
11
16
  *
12
- * Converting `A → B` is always `B.fromBase(A.toBase(value))`: route through the
13
- * base unit. This gives transitive conversions for free (any unit ↔ any unit)
14
- * and means each unit only ever stores its relationship to the base — never a
15
- * redundant, drift-prone pair of factors.
17
+ * Converting `A → B` routes through the base unit — conceptually
18
+ * `B.fromBase(A.toBase(value))`. This gives transitive conversions for free
19
+ * (any unit ↔ any unit) and means each unit only ever stores its relationship
20
+ * to the base — never a redundant, drift-prone pair of factors. Linear and
21
+ * affine units carry that relationship as an exact {@link Rational} transform,
22
+ * so their conversions are done in rational arithmetic and collapsed to a float
23
+ * once at the end, avoiding the binary rounding of routing through the base.
24
+ * Only non-linear units (defined via {@link custom}) fall back to float.
16
25
  *
17
26
  * A dimension is distinct from a {@link MeasurementSystem} (metric/imperial/…):
18
27
  * the dimension decides what *can convert*, while a measurement system is a tag
@@ -30,35 +39,57 @@ class Dimension {
30
39
  this.index = new Map();
31
40
  }
32
41
  /** Define the canonical base unit (identity transform). */
33
- base(name, aliases = []) {
34
- const unit = this.define(name, (x) => x, (x) => x, aliases);
42
+ base(name, def = {}) {
43
+ const unit = this.defineLinear(name, { scale: one, offset: zero }, def);
35
44
  this.baseUnit = unit;
36
45
  return unit;
37
46
  }
38
47
  /**
39
48
  * Define a linear unit. `scale` is how many base units make up one of this
40
- * unit (e.g. a kilometer is `1000` meters).
49
+ * unit (e.g. a kilometer is `1000` meters). Pass a {@link Rational} for a
50
+ * scale a decimal cannot represent exactly.
41
51
  */
42
- unit(name, scale, aliases = []) {
43
- return this.define(name, (x) => x * scale, (x) => x / scale, aliases);
52
+ unit(name, scale, def = {}) {
53
+ return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: zero }, def);
44
54
  }
45
55
  /** Define an affine unit (scale plus additive offset, e.g. °C against K). */
46
- affine(name, { scale, offset }, aliases = []) {
47
- return this.define(name, (x) => x * scale + offset, (x) => (x - offset) / scale, aliases);
56
+ affine(name, { scale, offset }, def = {}) {
57
+ return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: Rational_1.Rational.from(offset) }, def);
48
58
  }
49
- /** Define a unit with an arbitrary, hand-written inverse transform pair. */
50
- custom(name, { toBase, fromBase }, aliases = []) {
51
- return this.define(name, toBase, fromBase, aliases);
59
+ /**
60
+ * Define a non-linear unit from an arbitrary, hand-written inverse transform
61
+ * pair. Reserve this for units that genuinely cannot be expressed as `value *
62
+ * scale + offset` — e.g. logarithmic scales (decibels, octaves). Linear and
63
+ * affine units should use {@link unit} / {@link affine} so conversions stay
64
+ * exact.
65
+ */
66
+ custom(name, { toBase, fromBase }, def = {}) {
67
+ return this.define(name, def, { toBase, fromBase });
52
68
  }
53
- /** Convert a value between two units of this dimension, routed through the base. */
69
+ /** Convert a `number` value between two units of this dimension. */
54
70
  convert(value, from, to) {
71
+ return this.convertRational(Rational_1.Rational.from(value), from, to).toNumber();
72
+ }
73
+ /**
74
+ * Convert an exact {@link Rational} value between two units, routed through
75
+ * the base. Linear / affine units stay exact end-to-end (the result is never
76
+ * collapsed to a float here), which is what lets {@link Quantity} chain
77
+ * conversions without drift. A conversion touching a non-linear `custom` unit
78
+ * falls back to float math and recaptures the result as a rational.
79
+ */
80
+ convertRational(value, from, to) {
55
81
  if (!this.units.has(from) || !this.units.has(to)) {
56
82
  throw new InvalidConversionError_1.InvalidConversionError(from, to);
57
83
  }
58
84
  if (from === to) {
59
85
  return value;
60
86
  }
61
- return to.fromBase(from.toBase(value));
87
+ if (from.linear && to.linear) {
88
+ // base = from.scale * value + from.offset; result = (base - to.offset) / to.scale
89
+ const base = from.linear.scale.times(value).plus(from.linear.offset);
90
+ return base.minus(to.linear.offset).dividedBy(to.linear.scale);
91
+ }
92
+ return Rational_1.Rational.from(to.fromBase(from.toBase(value.toNumber())));
62
93
  }
63
94
  /** Resolve a name or alias to its candidate unit(s) (used by parsing). */
64
95
  get(token) {
@@ -67,17 +98,23 @@ class Dimension {
67
98
  has(unit) {
68
99
  return this.units.has(unit);
69
100
  }
70
- define(name, toBase, fromBase, aliases) {
101
+ /** Define a linear / affine unit from its exact rational transform. */
102
+ defineLinear(name, linear, def) {
103
+ return this.define(name, def, { linear });
104
+ }
105
+ define(name, { symbol, plural, aliases = [] }, transform) {
71
106
  for (const existing of this.units) {
72
107
  if (existing.name === name) {
73
108
  throw new Error(`Duplicate unit name "${name}" in dimension "${this.name}"`);
74
109
  }
75
110
  }
76
- const unit = new Unit_1.Unit({ name, dimension: this, toBase, fromBase });
111
+ const unit = new Unit_1.Unit({ name, dimension: this, symbol, plural, ...transform });
77
112
  this.units.add(unit);
78
- this.register(name, unit);
79
- for (const alias of aliases) {
80
- this.register(alias, unit);
113
+ // Register every label this unit can be parsed from, de-duplicated so a unit
114
+ // never appears twice among a token's candidates (e.g. if symbol === name).
115
+ const tokens = new Set([name, symbol, plural, ...aliases].filter((t) => !!t));
116
+ for (const token of tokens) {
117
+ this.register(token, unit);
81
118
  }
82
119
  return unit;
83
120
  }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MeasurementSystem = void 0;
4
- const scale_1 = require("./scale");
4
+ const scaleOf_1 = require("../utils/scaleOf");
5
5
  /**
6
6
  * A measurement system (metric, imperial, US customary, …) is a cross-dimension
7
7
  * collection of units that share a real-world standard.
@@ -37,7 +37,7 @@ class MeasurementSystem {
37
37
  * to the smallest unit when even that rounds below 1).
38
38
  */
39
39
  express(quantity) {
40
- const candidates = this.in(quantity.unit.dimension).sort((a, b) => (0, scale_1.scaleOf)(a) - (0, scale_1.scaleOf)(b));
40
+ const candidates = this.in(quantity.unit.dimension).sort((a, b) => (0, scaleOf_1.scaleOf)(a) - (0, scaleOf_1.scaleOf)(b));
41
41
  if (candidates.length === 0) {
42
42
  throw new Error(`Measurement system "${this.name}" has no ` +
43
43
  `"${quantity.unit.dimension.name}" units to express in`);
@@ -1,22 +1,105 @@
1
1
  import type { Dimension } from "./Dimension";
2
2
  import type { MeasurementSystem } from "./MeasurementSystem";
3
+ import { Rational } from "./Rational";
3
4
  import type { Unit } from "./Unit";
4
5
  /** Options for {@link Quantity.parse}. */
5
6
  export interface ParseOptions {
6
7
  /** Preferred measurement system, used only to break ties on shared aliases. */
7
8
  prefer?: MeasurementSystem;
8
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
+ }
9
49
  /** A magnitude paired with a unit (e.g. `5` `kilometer`). */
10
50
  export declare class Quantity {
11
- magnitude: number;
12
- unit: Unit;
13
- constructor(magnitude: number, unit: Unit);
51
+ readonly unit: Unit;
52
+ /**
53
+ * The magnitude as an exact {@link Rational} — the source of truth this
54
+ * quantity is built on. Conversions and arithmetic operate on it directly, so
55
+ * a chain like `q.to(a).to(b)` stays exact instead of accumulating the binary
56
+ * rounding of repeated float round trips. (Exact for linear / affine units; a
57
+ * conversion through a non-linear `custom` unit recaptures a float as a
58
+ * rational, so it is best-effort there.)
59
+ */
60
+ readonly rational: Rational;
61
+ constructor(magnitude: number | Rational, unit: Unit);
62
+ /** The magnitude as a `number`, derived from {@link rational}. */
63
+ get magnitude(): number;
14
64
  /** Return an equivalent quantity expressed in `target`. */
15
65
  to(target: Unit): Quantity;
16
66
  /** Return this quantity's raw magnitude expressed in `target`. */
17
67
  in(target: Unit): number;
68
+ /** This quantity's exact magnitude expressed in `target`. */
69
+ private inRational;
18
70
  /** Render as `"<magnitude> <unit name>"`, e.g. `"5 kilometer"`. */
19
71
  toString(): string;
72
+ /**
73
+ * Render as `"<magnitude> <label>"`, choosing the label per `options.unit`
74
+ * (default `"auto"`: magnitude-aware singular/plural). Unlike {@link toString},
75
+ * this can use the unit's symbol or plural — e.g. `"5 grams"`, `"5 g"`,
76
+ * `"1 gram"`. Pass `locale` / `numberFormat` to localize the magnitude via
77
+ * `toLocaleString` — e.g. `format({ locale: "de-DE" })` → `"1.234,5 meters"`,
78
+ * or `format({ numberFormat: { maximumFractionDigits: 2 } })` for precision.
79
+ *
80
+ * For non-string output (e.g. JSX), use {@link formatParts} and assemble the
81
+ * pieces yourself.
82
+ */
83
+ format(options?: FormatOptions): string;
84
+ /**
85
+ * Like {@link format}, but returns the rendered magnitude and label as
86
+ * separate strings instead of joining them, so the caller controls the
87
+ * assembly. Useful when a single string won't do — e.g. styling the magnitude
88
+ * in a React component:
89
+ *
90
+ * ```tsx
91
+ * const { magnitude, unit } = q.formatParts({ locale: "de-DE" });
92
+ * return <><b>{magnitude}</b> {unit}</>;
93
+ * ```
94
+ */
95
+ formatParts(options?: FormatOptions): FormattedParts;
96
+ /**
97
+ * Render the magnitude via `toLocaleString`. With no `locale`/`numberFormat`
98
+ * it uses the runtime's default locale; supply either for locale- and
99
+ * precision-aware formatting.
100
+ */
101
+ private formatMagnitude;
102
+ private formatLabel;
20
103
  /**
21
104
  * Add another quantity, returned in *this* quantity's unit. The other operand
22
105
  * is converted into this unit first, so the two may use different units of the
@@ -31,9 +114,9 @@ export declare class Quantity {
31
114
  /** Subtract another quantity, returned in this quantity's unit. */
32
115
  minus(other: Quantity): Quantity;
33
116
  /** Scale this quantity by a dimensionless factor. */
34
- times(factor: number): Quantity;
117
+ times(factor: number | Rational): Quantity;
35
118
  /** Divide this quantity by a dimensionless divisor. */
36
- dividedBy(divisor: number): Quantity;
119
+ dividedBy(divisor: number | Rational): Quantity;
37
120
  /**
38
121
  * Divide this quantity by `other` of the same dimension, yielding the
39
122
  * dimensionless ratio between them — i.e. how many of `other` fit in this.
@@ -54,14 +137,15 @@ export declare class Quantity {
54
137
  /** Alias for {@link minus}. */
55
138
  sub(other: Quantity): Quantity;
56
139
  /** Alias for {@link times}. */
57
- mul(factor: number): Quantity;
140
+ mul(factor: number | Rational): Quantity;
58
141
  /** Alias for {@link dividedBy}. */
59
- div(divisor: number): Quantity;
142
+ div(divisor: number | Rational): Quantity;
60
143
  /**
61
144
  * Whether this quantity equals `other`, compared in this quantity's unit.
62
145
  * Throws {@link InvalidConversionError} if the operands belong to different
63
- * dimensions. Comparison is exact, so values that differ only by
64
- * floating-point rounding from a conversion may compare unequal.
146
+ * dimensions. Comparison is exact rational equality, so quantities that are
147
+ * mathematically equal compare equal even if reaching them involved a
148
+ * conversion that would have drifted in floating point.
65
149
  */
66
150
  equals(other: Quantity): boolean;
67
151
  /** Whether this quantity does not equal `other`. */
@@ -104,7 +188,7 @@ export declare class Quantity {
104
188
  * - `"5 hr"` -> `Quantity(5, hour)`
105
189
  * - `"5hr 20min"` -> `Quantity(320, minute)`
106
190
  *
107
- * Compound inputs are summed in base units and returned in the *finest*
191
+ * Compound inputs are summed (exactly) and returned in the *finest*
108
192
  * (smallest-scale) unit present, so `"5hr 20min"` collapses to `320 minute`.
109
193
  *
110
194
  * When a token is a shared alias (e.g. `"ton"` → short ton & long ton), pass