measurable 1.1.1 → 2.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 +100 -0
  2. package/README.md +179 -35
  3. package/dist/dimensions/angle.js +2 -2
  4. package/dist/dimensions/area.d.ts +14 -0
  5. package/dist/dimensions/area.js +25 -0
  6. package/dist/dimensions/data.d.ts +10 -0
  7. package/dist/dimensions/data.js +36 -0
  8. package/dist/dimensions/energy.d.ts +14 -0
  9. package/dist/dimensions/energy.js +22 -0
  10. package/dist/dimensions/force.js +2 -2
  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 +13 -0
  15. package/dist/dimensions/index.d.ts +9 -0
  16. package/dist/dimensions/index.js +9 -0
  17. package/dist/dimensions/length.js +2 -2
  18. package/dist/dimensions/luminance.d.ts +7 -0
  19. package/dist/dimensions/luminance.js +16 -0
  20. package/dist/dimensions/luminousIntensity.d.ts +9 -0
  21. package/dist/dimensions/luminousIntensity.js +16 -0
  22. package/dist/dimensions/mass.js +2 -2
  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 +18 -0
  27. package/dist/dimensions/temperature.js +7 -1
  28. package/dist/dimensions/time.js +2 -2
  29. package/dist/dimensions/volume.js +2 -2
  30. package/dist/index.d.ts +2 -1
  31. package/dist/index.js +2 -1
  32. package/dist/lib/Dimension.d.ts +37 -11
  33. package/dist/lib/Dimension.js +49 -14
  34. package/dist/lib/MeasurementSystem.js +2 -2
  35. package/dist/lib/Quantity.d.ts +24 -10
  36. package/dist/lib/Quantity.js +39 -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 +41 -7
  40. package/dist/lib/Unit.js +35 -6
  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 +35 -0
  47. package/dist/utils/definePrefixed.js +61 -0
  48. package/dist/utils/scaleOf.d.ts +3 -0
  49. package/dist/utils/scaleOf.js +6 -0
  50. package/package.json +7 -3
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.microlux = exports.millilux = exports.kilolux = exports.metricIlluminance = exports.phot = exports.footCandle = exports.lux = exports.illuminance = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
+ /** Illuminance. Base unit: lux. */
7
+ exports.illuminance = new Dimension_1.Dimension("illuminance");
8
+ exports.lux = exports.illuminance.base("lux", ["lx"]);
9
+ exports.footCandle = exports.illuminance.unit("footCandle", 10.764, ["fc", "ft-c"]);
10
+ exports.phot = exports.illuminance.unit("phot", 10000, ["ph", "phots"]);
11
+ /** Every SI-prefixed lux (kilolux, millilux, microlux, …), keyed by name. */
12
+ exports.metricIlluminance = (0, definePrefixed_1.definePrefixed)(exports.illuminance, { name: "lux", symbol: "lx" });
13
+ exports.kilolux = exports.metricIlluminance.kilolux, exports.millilux = exports.metricIlluminance.millilux, exports.microlux = exports.metricIlluminance.microlux;
@@ -1,7 +1,16 @@
1
1
  export * from "./angle";
2
+ export * from "./area";
3
+ export * from "./data";
4
+ export * from "./energy";
2
5
  export * from "./force";
6
+ export * from "./frequency";
7
+ export * from "./illuminance";
3
8
  export * from "./length";
9
+ export * from "./luminance";
10
+ export * from "./luminousIntensity";
4
11
  export * from "./mass";
12
+ export * from "./power";
13
+ export * from "./pressure";
5
14
  export * from "./temperature";
6
15
  export * from "./time";
7
16
  export * from "./volume";
@@ -15,9 +15,18 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./angle"), exports);
18
+ __exportStar(require("./area"), exports);
19
+ __exportStar(require("./data"), exports);
20
+ __exportStar(require("./energy"), exports);
18
21
  __exportStar(require("./force"), exports);
22
+ __exportStar(require("./frequency"), exports);
23
+ __exportStar(require("./illuminance"), exports);
19
24
  __exportStar(require("./length"), exports);
25
+ __exportStar(require("./luminance"), exports);
26
+ __exportStar(require("./luminousIntensity"), exports);
20
27
  __exportStar(require("./mass"), exports);
28
+ __exportStar(require("./power"), exports);
29
+ __exportStar(require("./pressure"), exports);
21
30
  __exportStar(require("./temperature"), exports);
22
31
  __exportStar(require("./time"), exports);
23
32
  __exportStar(require("./volume"), exports);
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nanometer = exports.micrometer = exports.millimeter = exports.centimeter = exports.decimeter = exports.decameter = exports.hectometer = exports.kilometer = exports.metricLength = exports.mile = exports.yard = exports.foot = exports.inch = exports.meter = exports.length = void 0;
4
4
  const Dimension_1 = require("../lib/Dimension");
5
- const prefixes_1 = require("../lib/prefixes");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
6
  /** Length / distance. Base unit: meter. */
7
7
  exports.length = new Dimension_1.Dimension("length");
8
8
  exports.meter = exports.length.base("meter", ["m", "meters"]);
@@ -12,5 +12,5 @@ exports.foot = exports.length.unit("foot", 0.3048, ["ft", "feet"]);
12
12
  exports.yard = exports.length.unit("yard", 0.9144, ["yd", "yards"]);
13
13
  exports.mile = exports.length.unit("mile", 1609.344, ["mi", "miles"]);
14
14
  /** Every SI-prefixed meter (kilometer, centimeter, micrometer, …), keyed by name. */
15
- exports.metricLength = (0, prefixes_1.definePrefixed)(exports.length, { name: "meter", symbol: "m", scale: 1 });
15
+ exports.metricLength = (0, definePrefixed_1.definePrefixed)(exports.length, { name: "meter", symbol: "m" });
16
16
  exports.kilometer = exports.metricLength.kilometer, exports.hectometer = exports.metricLength.hectometer, exports.decameter = exports.metricLength.decameter, exports.decimeter = exports.metricLength.decimeter, exports.centimeter = exports.metricLength.centimeter, exports.millimeter = exports.metricLength.millimeter, exports.micrometer = exports.metricLength.micrometer, exports.nanometer = exports.metricLength.nanometer;
@@ -0,0 +1,7 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Luminance. Base unit: candela per square meter (the nit). */
3
+ export declare const luminance: Dimension;
4
+ export declare const candelaPerSquareMeter: import("..").Unit;
5
+ export declare const stilb: import("..").Unit;
6
+ /** Alias for {@link candelaPerSquareMeter}. */
7
+ export declare const nit: import("..").Unit;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nit = exports.stilb = exports.candelaPerSquareMeter = exports.luminance = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ /** Luminance. Base unit: candela per square meter (the nit). */
6
+ exports.luminance = new Dimension_1.Dimension("luminance");
7
+ exports.candelaPerSquareMeter = exports.luminance.base("candelaPerSquareMeter", [
8
+ "cd/m²",
9
+ "cd/m2",
10
+ "nit",
11
+ "nits",
12
+ "nt",
13
+ ]);
14
+ exports.stilb = exports.luminance.unit("stilb", 1e4, ["sb"]);
15
+ /** Alias for {@link candelaPerSquareMeter}. */
16
+ exports.nit = exports.candelaPerSquareMeter;
@@ -0,0 +1,9 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Luminous intensity. Base unit: candela. */
3
+ export declare const luminousIntensity: Dimension;
4
+ export declare const candela: import("..").Unit;
5
+ export declare const candlepower: import("..").Unit;
6
+ export declare const hefnerkerze: import("..").Unit;
7
+ /** Every SI-prefixed candela (kilocandela, millicandela, …), keyed by name. */
8
+ export declare const metricLuminousIntensity: Record<string, import("..").Unit>;
9
+ export declare const kilocandela: import("..").Unit, millicandela: import("..").Unit, microcandela: import("..").Unit;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.microcandela = exports.millicandela = exports.kilocandela = exports.metricLuminousIntensity = exports.hefnerkerze = exports.candlepower = exports.candela = exports.luminousIntensity = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
+ /** Luminous intensity. Base unit: candela. */
7
+ exports.luminousIntensity = new Dimension_1.Dimension("luminousIntensity");
8
+ exports.candela = exports.luminousIntensity.base("candela", ["cd"]);
9
+ exports.candlepower = exports.luminousIntensity.unit("candlepower", 1, ["cp", "CP"]);
10
+ exports.hefnerkerze = exports.luminousIntensity.unit("hefnerkerze", 0.92, ["HK"]);
11
+ /** Every SI-prefixed candela (kilocandela, millicandela, …), keyed by name. */
12
+ exports.metricLuminousIntensity = (0, definePrefixed_1.definePrefixed)(exports.luminousIntensity, {
13
+ name: "candela",
14
+ symbol: "cd",
15
+ });
16
+ exports.kilocandela = exports.metricLuminousIntensity.kilocandela, exports.millicandela = exports.metricLuminousIntensity.millicandela, exports.microcandela = exports.metricLuminousIntensity.microcandela;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.nanogram = exports.microgram = exports.milligram = exports.centigram = exports.decigram = exports.decagram = exports.hectogram = exports.megagram = exports.kilogram = exports.metricMass = exports.longTon = exports.shortTon = exports.tonne = exports.stone = exports.ounce = exports.pound = exports.gram = exports.mass = void 0;
4
4
  const Dimension_1 = require("../lib/Dimension");
5
- const prefixes_1 = require("../lib/prefixes");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
6
  /**
7
7
  * Mass / weight. Base unit: gram. (SI's official base is the kilogram, but
8
8
  * prefixes attach to the gram, so gram is the natural routing anchor — the
@@ -20,5 +20,5 @@ exports.tonne = exports.mass.unit("tonne", 1000000, ["t", "tonnes"]);
20
20
  exports.shortTon = exports.mass.unit("shortTon", 907184.74, ["ton", "tons"]);
21
21
  exports.longTon = exports.mass.unit("longTon", 1016046.9088, ["ton", "tons"]);
22
22
  /** Every SI-prefixed gram — including the kilogram — keyed by name. */
23
- exports.metricMass = (0, prefixes_1.definePrefixed)(exports.mass, { name: "gram", symbol: "g", scale: 1 });
23
+ exports.metricMass = (0, definePrefixed_1.definePrefixed)(exports.mass, { name: "gram", symbol: "g" });
24
24
  exports.kilogram = exports.metricMass.kilogram, exports.megagram = exports.metricMass.megagram, exports.hectogram = exports.metricMass.hectogram, exports.decagram = exports.metricMass.decagram, exports.decigram = exports.metricMass.decigram, exports.centigram = exports.metricMass.centigram, exports.milligram = exports.metricMass.milligram, exports.microgram = exports.metricMass.microgram, exports.nanogram = exports.metricMass.nanogram;
@@ -0,0 +1,9 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Power. Base unit: watt. */
3
+ export declare const power: Dimension;
4
+ export declare const watt: import("..").Unit;
5
+ export declare const horsepower: import("..").Unit;
6
+ export declare const metricHorsepower: import("..").Unit;
7
+ /** Every SI-prefixed watt (kilowatt, megawatt, gigawatt, milliwatt, …), keyed by name. */
8
+ export declare const metricPower: Record<string, import("..").Unit>;
9
+ export declare const kilowatt: import("..").Unit, megawatt: import("..").Unit, gigawatt: import("..").Unit, terawatt: import("..").Unit, milliwatt: import("..").Unit;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.milliwatt = exports.terawatt = exports.gigawatt = exports.megawatt = exports.kilowatt = exports.metricPower = exports.metricHorsepower = exports.horsepower = exports.watt = exports.power = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
+ /** Power. Base unit: watt. */
7
+ exports.power = new Dimension_1.Dimension("power");
8
+ exports.watt = exports.power.base("watt", ["W", "watts"]);
9
+ exports.horsepower = exports.power.unit("horsepower", 745.699872, ["hp"]);
10
+ exports.metricHorsepower = exports.power.unit("metricHorsepower", 735.49875, ["PS"]);
11
+ /** Every SI-prefixed watt (kilowatt, megawatt, gigawatt, milliwatt, …), keyed by name. */
12
+ exports.metricPower = (0, definePrefixed_1.definePrefixed)(exports.power, { name: "watt", symbol: "W" });
13
+ exports.kilowatt = exports.metricPower.kilowatt, exports.megawatt = exports.metricPower.megawatt, exports.gigawatt = exports.metricPower.gigawatt, exports.terawatt = exports.metricPower.terawatt, exports.milliwatt = exports.metricPower.milliwatt;
@@ -0,0 +1,14 @@
1
+ import { Dimension } from "../lib/Dimension";
2
+ /** Pressure. Base unit: pascal. */
3
+ export declare const pressure: Dimension;
4
+ export declare const pascal: import("..").Unit;
5
+ export declare const bar: import("..").Unit;
6
+ export declare const millibar: import("..").Unit;
7
+ export declare const atmosphere: import("..").Unit;
8
+ export declare const torr: import("..").Unit;
9
+ export declare const psi: import("..").Unit;
10
+ export declare const inchOfMercury: import("..").Unit;
11
+ export declare const inchOfWater: import("..").Unit;
12
+ /** Every SI-prefixed pascal (kilopascal, hectopascal, megapascal, …), keyed by name. */
13
+ export declare const metricPressure: Record<string, import("..").Unit>;
14
+ export declare const kilopascal: import("..").Unit, hectopascal: import("..").Unit, megapascal: import("..").Unit, gigapascal: import("..").Unit;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.gigapascal = exports.megapascal = exports.hectopascal = exports.kilopascal = exports.metricPressure = exports.inchOfWater = exports.inchOfMercury = exports.psi = exports.torr = exports.atmosphere = exports.millibar = exports.bar = exports.pascal = exports.pressure = void 0;
4
+ const Dimension_1 = require("../lib/Dimension");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
+ /** Pressure. Base unit: pascal. */
7
+ exports.pressure = new Dimension_1.Dimension("pressure");
8
+ exports.pascal = exports.pressure.base("pascal", ["Pa", "pascals"]);
9
+ exports.bar = exports.pressure.unit("bar", 1e5, ["bars"]);
10
+ exports.millibar = exports.pressure.unit("millibar", 1e2, ["mbar", "millibars"]);
11
+ exports.atmosphere = exports.pressure.unit("atmosphere", 101325, ["atm", "atmospheres"]);
12
+ exports.torr = exports.pressure.unit("torr", 101325 / 760, ["Torr", "torrs"]);
13
+ exports.psi = exports.pressure.unit("psi", 6894.757293168, ["lbf/in²", "lbf/in2"]);
14
+ exports.inchOfMercury = exports.pressure.unit("inchOfMercury", 3386.389, ["inHg"]);
15
+ exports.inchOfWater = exports.pressure.unit("inchOfWater", 249.0889, ["inAq"]);
16
+ /** Every SI-prefixed pascal (kilopascal, hectopascal, megapascal, …), keyed by name. */
17
+ exports.metricPressure = (0, definePrefixed_1.definePrefixed)(exports.pressure, { name: "pascal", symbol: "Pa" });
18
+ exports.kilopascal = exports.metricPressure.kilopascal, exports.hectopascal = exports.metricPressure.hectopascal, exports.megapascal = exports.metricPressure.megapascal, exports.gigapascal = exports.metricPressure.gigapascal;
@@ -2,11 +2,17 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.fahrenheit = exports.celsius = exports.kelvin = exports.temperature = void 0;
4
4
  const Dimension_1 = require("../lib/Dimension");
5
+ const Rational_1 = require("../lib/Rational");
5
6
  /**
6
7
  * Temperature. Base unit: kelvin. Uses affine units because Celsius and
7
8
  * Fahrenheit are offset from the base, not just scaled.
8
9
  */
9
10
  exports.temperature = new Dimension_1.Dimension("temperature");
11
+ // Fahrenheit's 5/9 ratio is not a terminating decimal, so it is given as an
12
+ // exact Rational; the offset (273.15 − 32 × 5/9 K) is then derived in exact
13
+ // rational arithmetic so conversions round-trip without drift.
14
+ const fahrenheitScale = new Rational_1.Rational(5, 9);
15
+ const fahrenheitOffset = Rational_1.Rational.from(273.15).minus(new Rational_1.Rational(32).times(fahrenheitScale));
10
16
  exports.kelvin = exports.temperature.base("kelvin", ["K"]);
11
17
  exports.celsius = exports.temperature.affine("celsius", { scale: 1, offset: 273.15 }, ["C", "°C"]);
12
- exports.fahrenheit = exports.temperature.affine("fahrenheit", { scale: 5 / 9, offset: 273.15 - 32 * (5 / 9) }, ["F", "°F"]);
18
+ exports.fahrenheit = exports.temperature.affine("fahrenheit", { scale: fahrenheitScale, offset: fahrenheitOffset }, ["F", "°F"]);
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.picosecond = exports.nanosecond = exports.microsecond = exports.millisecond = exports.metricTime = exports.week = exports.day = exports.hour = exports.minute = exports.second = exports.time = void 0;
4
4
  const Dimension_1 = require("../lib/Dimension");
5
- const prefixes_1 = require("../lib/prefixes");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
6
  /** Time / duration. Base unit: second. */
7
7
  exports.time = new Dimension_1.Dimension("time");
8
8
  exports.second = exports.time.base("second", ["s", "sec", "secs", "seconds"]);
@@ -14,5 +14,5 @@ exports.week = exports.time.unit("week", 604800, ["wk", "weeks"]);
14
14
  * SI-submultiple seconds (millisecond, microsecond, nanosecond, …). Only
15
15
  * fractions are generated; larger spans use minute/hour/day/week above.
16
16
  */
17
- exports.metricTime = (0, prefixes_1.definePrefixed)(exports.time, { name: "second", symbol: "s", scale: 1 }, prefixes_1.SI_SUBMULTIPLE_PREFIXES);
17
+ exports.metricTime = (0, definePrefixed_1.definePrefixed)(exports.time, { name: "second", symbol: "s" }, definePrefixed_1.SI_SUBMULTIPLE_PREFIXES);
18
18
  exports.millisecond = exports.metricTime.millisecond, exports.microsecond = exports.metricTime.microsecond, exports.nanosecond = exports.metricTime.nanosecond, exports.picosecond = exports.metricTime.picosecond;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.milliliter = exports.centiliter = exports.deciliter = exports.decaliter = exports.hectoliter = exports.kiloliter = exports.metricVolume = exports.teaspoon = exports.tablespoon = exports.cup = exports.imperialFluidOunce = exports.imperialGill = exports.imperialPint = exports.imperialQuart = exports.imperialGallon = exports.usFluidOunce = exports.usGill = exports.usPint = exports.usQuart = exports.usGallon = exports.liter = exports.volume = void 0;
4
4
  const Dimension_1 = require("../lib/Dimension");
5
- const prefixes_1 = require("../lib/prefixes");
5
+ const definePrefixed_1 = require("../utils/definePrefixed");
6
6
  /** Volume / capacity. Base unit: liter. */
7
7
  exports.volume = new Dimension_1.Dimension("volume");
8
8
  exports.liter = exports.volume.base("liter", ["L", "liters"]);
@@ -33,5 +33,5 @@ exports.cup = exports.volume.unit("cup", 0.2365882365, ["cups"]);
33
33
  exports.tablespoon = exports.volume.unit("tablespoon", 0.01478676478125, ["tbsp", "tablespoons"]);
34
34
  exports.teaspoon = exports.volume.unit("teaspoon", 0.00492892159375, ["tsp", "teaspoons"]);
35
35
  /** Every SI-prefixed liter (milliliter, centiliter, kiloliter, …), keyed by name. */
36
- exports.metricVolume = (0, prefixes_1.definePrefixed)(exports.volume, { name: "liter", symbol: "L", scale: 1 });
36
+ exports.metricVolume = (0, definePrefixed_1.definePrefixed)(exports.volume, { name: "liter", symbol: "L" });
37
37
  exports.kiloliter = exports.metricVolume.kiloliter, exports.hectoliter = exports.metricVolume.hectoliter, exports.decaliter = exports.metricVolume.decaliter, exports.deciliter = exports.metricVolume.deciliter, exports.centiliter = exports.metricVolume.centiliter, exports.milliliter = exports.metricVolume.milliliter;
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,10 +1,15 @@
1
+ import { Rational } from "../lib/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 {
@@ -17,10 +22,14 @@ export interface CustomSpec {
17
22
  * the dimension is defined relative to, and it is the single place where all
18
23
  * conversion math happens.
19
24
  *
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.
25
+ * Converting `A → B` routes through the base unit — conceptually
26
+ * `B.fromBase(A.toBase(value))`. This gives transitive conversions for free
27
+ * (any unit ↔ any unit) and means each unit only ever stores its relationship
28
+ * to the base — never a redundant, drift-prone pair of factors. Linear and
29
+ * affine units carry that relationship as an exact {@link Rational} transform,
30
+ * so their conversions are done in rational arithmetic and collapsed to a float
31
+ * once at the end, avoiding the binary rounding of routing through the base.
32
+ * Only non-linear units (defined via {@link custom}) fall back to float.
24
33
  *
25
34
  * A dimension is distinct from a {@link MeasurementSystem} (metric/imperial/…):
26
35
  * the dimension decides what *can convert*, while a measurement system is a tag
@@ -42,18 +51,35 @@ export declare class Dimension {
42
51
  base(name: string, aliases?: string[]): Unit;
43
52
  /**
44
53
  * Define a linear unit. `scale` is how many base units make up one of this
45
- * unit (e.g. a kilometer is `1000` meters).
54
+ * unit (e.g. a kilometer is `1000` meters). Pass a {@link Rational} for a
55
+ * scale a decimal cannot represent exactly.
46
56
  */
47
- unit(name: string, scale: number, aliases?: string[]): Unit;
57
+ unit(name: string, scale: number | Rational, aliases?: string[]): Unit;
48
58
  /** Define an affine unit (scale plus additive offset, e.g. °C against K). */
49
59
  affine(name: string, { scale, offset }: AffineSpec, aliases?: string[]): Unit;
50
- /** Define a unit with an arbitrary, hand-written inverse transform pair. */
60
+ /**
61
+ * Define a non-linear unit from an arbitrary, hand-written inverse transform
62
+ * pair. Reserve this for units that genuinely cannot be expressed as `value *
63
+ * scale + offset` — e.g. logarithmic scales (decibels, octaves). Linear and
64
+ * affine units should use {@link unit} / {@link affine} so conversions stay
65
+ * exact.
66
+ */
51
67
  custom(name: string, { toBase, fromBase }: CustomSpec, aliases?: string[]): Unit;
52
- /** Convert a value between two units of this dimension, routed through the base. */
68
+ /** Convert a `number` value between two units of this dimension. */
53
69
  convert(value: number, from: Unit, to: Unit): number;
70
+ /**
71
+ * Convert an exact {@link Rational} value between two units, routed through
72
+ * the base. Linear / affine units stay exact end-to-end (the result is never
73
+ * collapsed to a float here), which is what lets {@link Quantity} chain
74
+ * conversions without drift. A conversion touching a non-linear `custom` unit
75
+ * falls back to float math and recaptures the result as a rational.
76
+ */
77
+ convertRational(value: Rational, from: Unit, to: Unit): Rational;
54
78
  /** Resolve a name or alias to its candidate unit(s) (used by parsing). */
55
79
  get(token: string): Unit[] | undefined;
56
80
  has(unit: Unit): boolean;
81
+ /** Define a linear / affine unit from its exact rational transform. */
82
+ private defineLinear;
57
83
  private define;
58
84
  /** Append a name/alias → unit mapping; shared aliases accumulate candidates. */
59
85
  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("../lib/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
@@ -31,34 +40,56 @@ class Dimension {
31
40
  }
32
41
  /** Define the canonical base unit (identity transform). */
33
42
  base(name, aliases = []) {
34
- const unit = this.define(name, (x) => x, (x) => x, aliases);
43
+ const unit = this.defineLinear(name, { scale: one, offset: zero }, aliases);
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
52
  unit(name, scale, aliases = []) {
43
- return this.define(name, (x) => x * scale, (x) => x / scale, aliases);
53
+ return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: zero }, aliases);
44
54
  }
45
55
  /** Define an affine unit (scale plus additive offset, e.g. °C against K). */
46
56
  affine(name, { scale, offset }, aliases = []) {
47
- return this.define(name, (x) => x * scale + offset, (x) => (x - offset) / scale, aliases);
57
+ return this.defineLinear(name, { scale: Rational_1.Rational.from(scale), offset: Rational_1.Rational.from(offset) }, aliases);
48
58
  }
49
- /** Define a unit with an arbitrary, hand-written inverse transform pair. */
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
+ */
50
66
  custom(name, { toBase, fromBase }, aliases = []) {
51
- return this.define(name, toBase, fromBase, aliases);
67
+ return this.define(name, aliases, { 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,13 +98,17 @@ 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, aliases) {
103
+ return this.define(name, aliases, { linear });
104
+ }
105
+ define(name, 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, ...transform });
77
112
  this.units.add(unit);
78
113
  this.register(name, unit);
79
114
  for (const alias of aliases) {
@@ -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,5 +1,6 @@
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 {
@@ -8,13 +9,25 @@ export interface ParseOptions {
8
9
  }
9
10
  /** A magnitude paired with a unit (e.g. `5` `kilometer`). */
10
11
  export declare class Quantity {
11
- magnitude: number;
12
- unit: Unit;
13
- constructor(magnitude: number, unit: Unit);
12
+ readonly unit: Unit;
13
+ /**
14
+ * The magnitude as an exact {@link Rational} — the source of truth this
15
+ * quantity is built on. Conversions and arithmetic operate on it directly, so
16
+ * a chain like `q.to(a).to(b)` stays exact instead of accumulating the binary
17
+ * rounding of repeated float round trips. (Exact for linear / affine units; a
18
+ * conversion through a non-linear `custom` unit recaptures a float as a
19
+ * rational, so it is best-effort there.)
20
+ */
21
+ readonly rational: Rational;
22
+ constructor(magnitude: number | Rational, unit: Unit);
23
+ /** The magnitude as a `number`, derived from {@link rational}. */
24
+ get magnitude(): number;
14
25
  /** Return an equivalent quantity expressed in `target`. */
15
26
  to(target: Unit): Quantity;
16
27
  /** Return this quantity's raw magnitude expressed in `target`. */
17
28
  in(target: Unit): number;
29
+ /** This quantity's exact magnitude expressed in `target`. */
30
+ private inRational;
18
31
  /** Render as `"<magnitude> <unit name>"`, e.g. `"5 kilometer"`. */
19
32
  toString(): string;
20
33
  /**
@@ -31,9 +44,9 @@ export declare class Quantity {
31
44
  /** Subtract another quantity, returned in this quantity's unit. */
32
45
  minus(other: Quantity): Quantity;
33
46
  /** Scale this quantity by a dimensionless factor. */
34
- times(factor: number): Quantity;
47
+ times(factor: number | Rational): Quantity;
35
48
  /** Divide this quantity by a dimensionless divisor. */
36
- dividedBy(divisor: number): Quantity;
49
+ dividedBy(divisor: number | Rational): Quantity;
37
50
  /**
38
51
  * Divide this quantity by `other` of the same dimension, yielding the
39
52
  * dimensionless ratio between them — i.e. how many of `other` fit in this.
@@ -54,14 +67,15 @@ export declare class Quantity {
54
67
  /** Alias for {@link minus}. */
55
68
  sub(other: Quantity): Quantity;
56
69
  /** Alias for {@link times}. */
57
- mul(factor: number): Quantity;
70
+ mul(factor: number | Rational): Quantity;
58
71
  /** Alias for {@link dividedBy}. */
59
- div(divisor: number): Quantity;
72
+ div(divisor: number | Rational): Quantity;
60
73
  /**
61
74
  * Whether this quantity equals `other`, compared in this quantity's unit.
62
75
  * 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.
76
+ * dimensions. Comparison is exact rational equality, so quantities that are
77
+ * mathematically equal compare equal even if reaching them involved a
78
+ * conversion that would have drifted in floating point.
65
79
  */
66
80
  equals(other: Quantity): boolean;
67
81
  /** Whether this quantity does not equal `other`. */
@@ -104,7 +118,7 @@ export declare class Quantity {
104
118
  * - `"5 hr"` -> `Quantity(5, hour)`
105
119
  * - `"5hr 20min"` -> `Quantity(320, minute)`
106
120
  *
107
- * Compound inputs are summed in base units and returned in the *finest*
121
+ * Compound inputs are summed (exactly) and returned in the *finest*
108
122
  * (smallest-scale) unit present, so `"5hr 20min"` collapses to `320 minute`.
109
123
  *
110
124
  * When a token is a shared alias (e.g. `"ton"` → short ton & long ton), pass