UNPKG

measure-convert

Version:

JS/TS package for managing units of measurement. Convert, add, subtract, multiply, divide, and compare units of measurement.

297 lines (296 loc) 14.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Measurement = void 0; // src/Measurement.ts const Unit_1 = require("./units/Unit"); const UnitAngle_1 = require("./units/UnitAngle"); const UnitFuelEfficiency_1 = require("./units/UnitFuelEfficiency"); const UnitTemperature_1 = require("./units/UnitTemperature"); const UnitAcceleration_1 = require("./units/UnitAcceleration"); const UnitArea_1 = require("./units/UnitArea"); const UnitConcentrationMass_1 = require("./units/UnitConcentrationMass"); const UnitDispersion_1 = require("./units/UnitDispersion"); const UnitDuration_1 = require("./units/UnitDuration"); const UnitElectricCharge_1 = require("./units/UnitElectricCharge"); const UnitElectricCurrent_1 = require("./units/UnitElectricCurrent"); const UnitElectricPotentialDifference_1 = require("./units/UnitElectricPotentialDifference"); const UnitElectricResistance_1 = require("./units/UnitElectricResistance"); const UnitEnergy_1 = require("./units/UnitEnergy"); const UnitFrequency_1 = require("./units/UnitFrequency"); const UnitIlluminance_1 = require("./units/UnitIlluminance"); const UnitInformationStorage_1 = require("./units/UnitInformationStorage"); const UnitLength_1 = require("./units/UnitLength"); const UnitMass_1 = require("./units/UnitMass"); const UnitPower_1 = require("./units/UnitPower"); const UnitPressure_1 = require("./units/UnitPressure"); const UnitSpeed_1 = require("./units/UnitSpeed"); const UnitVolume_1 = require("./units/UnitVolume"); const UnitBiologicalActivity_1 = require("./units/UnitBiologicalActivity"); // Static registry of all unit classes for detection const UNIT_CLASSES = [ { name: 'UnitAcceleration', class: UnitAcceleration_1.UnitAcceleration }, { name: 'UnitAngle', class: UnitAngle_1.UnitAngle }, { name: 'UnitArea', class: UnitArea_1.UnitArea }, { name: 'UnitConcentrationMass', class: UnitConcentrationMass_1.UnitConcentrationMass }, { name: 'UnitDispersion', class: UnitDispersion_1.UnitDispersion }, { name: 'UnitDuration', class: UnitDuration_1.UnitDuration }, { name: 'UnitElectricCharge', class: UnitElectricCharge_1.UnitElectricCharge }, { name: 'UnitElectricCurrent', class: UnitElectricCurrent_1.UnitElectricCurrent }, { name: 'UnitElectricPotentialDifference', class: UnitElectricPotentialDifference_1.UnitElectricPotentialDifference }, { name: 'UnitElectricResistance', class: UnitElectricResistance_1.UnitElectricResistance }, { name: 'UnitEnergy', class: UnitEnergy_1.UnitEnergy }, { name: 'UnitFrequency', class: UnitFrequency_1.UnitFrequency }, { name: 'UnitFuelEfficiency', class: UnitFuelEfficiency_1.UnitFuelEfficiency }, { name: 'UnitIlluminance', class: UnitIlluminance_1.UnitIlluminance }, { name: 'UnitInformationStorage', class: UnitInformationStorage_1.UnitInformationStorage }, { name: 'UnitLength', class: UnitLength_1.UnitLength }, { name: 'UnitMass', class: UnitMass_1.UnitMass }, { name: 'UnitPower', class: UnitPower_1.UnitPower }, { name: 'UnitPressure', class: UnitPressure_1.UnitPressure }, { name: 'UnitSpeed', class: UnitSpeed_1.UnitSpeed }, { name: 'UnitTemperature', class: UnitTemperature_1.UnitTemperature }, { name: 'UnitVolume', class: UnitVolume_1.UnitVolume }, { name: 'UnitBiologicalActivity', class: UnitBiologicalActivity_1.UnitBiologicalActivity }, ]; class Measurement { constructor(value, unit) { this.value = value; this.unit = unit; } // Adjusted to handle specialized conversions converted(targetUnit) { if (this.unit.constructor !== targetUnit.constructor) { throw new Error(`Cannot convert from ${this.unit.name} (${this.unit.constructor.name}) to ${targetUnit.name} (${targetUnit.constructor.name}): incompatible unit types.`); } // Handle specialized conversions if necessary if (this.unit instanceof UnitFuelEfficiency_1.UnitFuelEfficiency && targetUnit instanceof UnitFuelEfficiency_1.UnitFuelEfficiency) { const convertedValue = this.unit.convert(this.value, targetUnit); return new Measurement(convertedValue, targetUnit); } if (this.unit instanceof UnitTemperature_1.UnitTemperature && targetUnit instanceof UnitTemperature_1.UnitTemperature) { const convertedValue = this.unit.convert(this.value, targetUnit); return new Measurement(convertedValue, targetUnit); } // Default linear conversion for all other units const conversionFactor = this.unit.baseUnitConversionFactor / targetUnit.baseUnitConversionFactor; return new Measurement(this.value * conversionFactor, targetUnit); } // Instance method for addition add(other) { return Measurement.add(this, other); } // Static method for addition static add(measurement1, measurement2) { if (measurement1.unit === measurement2.unit) { return new Measurement(measurement1.value + measurement2.value, measurement1.unit); } else { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return new Measurement(measurement1.value + convertedMeasurement2.value, measurement1.unit); } } // Instance method for subtraction subtract(other) { return Measurement.subtract(this, other); } // Static method for subtraction static subtract(measurement1, measurement2) { if (measurement1.unit === measurement2.unit) { return new Measurement(measurement1.value - measurement2.value, measurement1.unit); } else { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return new Measurement(measurement1.value - convertedMeasurement2.value, measurement1.unit); } } // Equality check equals(other) { return Measurement.equals(this, other); } static equals(measurement1, measurement2) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return measurement1.value === convertedMeasurement2.value; } closeTo(other, tolerance) { return Measurement.closeTo(this, other, tolerance); } static closeTo(measurement1, measurement2, tolerance) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); if (tolerance < 0) { throw new Error("Tolerance must be a non-negative number."); } return Math.abs(measurement1.value - convertedMeasurement2.value) <= tolerance; } // Comparison methods implemented similarly greaterThan(other) { return Measurement.greaterThan(this, other); } static greaterThan(measurement1, measurement2) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return measurement1.value > convertedMeasurement2.value; } lessThan(other) { return Measurement.lessThan(this, other); } static lessThan(measurement1, measurement2) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return measurement1.value < convertedMeasurement2.value; } greaterThanOrEqual(other) { return Measurement.greaterThanOrEqual(this, other); } static greaterThanOrEqual(measurement1, measurement2) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return measurement1.value >= convertedMeasurement2.value; } lessThanOrEqual(other) { return Measurement.lessThanOrEqual(this, other); } static lessThanOrEqual(measurement1, measurement2) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); return measurement1.value <= convertedMeasurement2.value; } ratioTo(other) { return Measurement.ratioTo(this, other); } static ratioTo(measurement1, measurement2) { const convertedMeasurement2 = measurement2.converted(measurement1.unit); if (convertedMeasurement2.value === 0) { throw new Error("Cannot calculate ratio: denominator measurement has zero value"); } return measurement1.value / convertedMeasurement2.value; } scaledBy(factor) { return Measurement.scaledBy(this, factor); } static scaledBy(measurement, factor) { return new Measurement(measurement.value * factor, measurement.unit); } // Helper method to format value with intelligent decimal places formatValue(value, decimalPlaces, showApprox = false) { if (decimalPlaces !== undefined) { // Use specified decimal places, but avoid showing 0 for significant values const rounded = Number(value.toFixed(decimalPlaces)); if (rounded === 0 && value > 0) { // If rounding to 0 but value is positive, show with more decimals or approx if (showApprox) { return `~${rounded}`; } // Find minimum decimal places needed to avoid 0 for (let i = decimalPlaces + 1; i <= 6; i++) { const testRounded = Number(value.toFixed(i)); if (testRounded > 0) { return testRounded.toString(); } } return showApprox ? `~0` : `<0.000001`; } return rounded.toString(); } // Intelligent default decimal places const absValue = Math.abs(value); let defaultDecimals; if (absValue >= 100) { defaultDecimals = 0; } else if (absValue >= 10) { defaultDecimals = 1; } else if (absValue >= 1) { defaultDecimals = 2; } else if (absValue >= 0.1) { defaultDecimals = 3; } else { defaultDecimals = 4; } const rounded = Number(value.toFixed(defaultDecimals)); if (rounded === 0 && value > 0) { return showApprox ? `~0` : `<${Math.pow(10, -defaultDecimals)}`; } return rounded.toString(); } // Customized short label method for handling degrees specifically get shortLabel() { return this.getShortLabel(); } getShortLabel(decimalPlaces, showApprox = false) { const formattedValue = this.formatValue(this.value, decimalPlaces, showApprox); // Check if the unit is 'degrees' if (this.unit === UnitAngle_1.UnitAngle.degrees) { return `${formattedValue}${this.unit.symbol}`; // No space for degrees } return `${formattedValue} ${this.unit.symbol}`; // Default formatting for others } get longLabel() { return this.getLongLabel(); } getLongLabel(decimalPlaces, showApprox = false) { const formattedValue = this.formatValue(this.value, decimalPlaces, showApprox); return `${formattedValue} ${this.unit.name}`; } // Hydrate when you know the unit type static hydrate(UnitClass, data) { const { value, unit: unitData } = data; // Find matching unit in the specified unit class const staticProps = Object.getOwnPropertyNames(UnitClass); for (const propName of staticProps) { const staticUnit = UnitClass[propName]; // Skip non-unit properties if (!staticUnit || typeof staticUnit !== 'object' || !(staticUnit instanceof Unit_1.Unit)) { continue; } // Check if this unit matches our serialized data if (staticUnit.name === unitData.name && staticUnit.symbol === unitData.symbol && staticUnit.baseUnitConversionFactor === unitData.baseUnitConversionFactor) { return new Measurement(value, staticUnit); } } throw new Error(`Unable to hydrate measurement: No matching unit found in ${UnitClass.name} for "${unitData.name}"`); } // Auto-hydrate by detecting the unit type first static hydrateAuto(data) { for (const { class: UnitClass } of UNIT_CLASSES) { // Get all static properties that are unit instances const staticProps = Object.getOwnPropertyNames(UnitClass); for (const propName of staticProps) { const staticUnit = UnitClass[propName]; // Skip non-unit properties if (!staticUnit || typeof staticUnit !== 'object' || !(staticUnit instanceof Unit_1.Unit)) { continue; } // Check if this unit matches our serialized data if (staticUnit.name === data.unit.name && staticUnit.symbol === data.unit.symbol && staticUnit.baseUnitConversionFactor === data.unit.baseUnitConversionFactor) { return new Measurement(data.value, staticUnit); } } } throw new Error(`Unable to hydrate measurement: Unknown unit with name "${data.unit.name}", symbol "${data.unit.symbol}"`); } // Detect what unit type a serialized measurement is (for advanced use cases) static detectUnitType(data) { for (const { name: className, class: UnitClass } of UNIT_CLASSES) { // Get all static properties that are unit instances const staticProps = Object.getOwnPropertyNames(UnitClass); for (const propName of staticProps) { const staticUnit = UnitClass[propName]; // Skip non-unit properties if (!staticUnit || typeof staticUnit !== 'object' || !(staticUnit instanceof Unit_1.Unit)) { continue; } // Check if this unit matches our serialized data if (staticUnit.name === data.unit.name && staticUnit.symbol === data.unit.symbol && staticUnit.baseUnitConversionFactor === data.unit.baseUnitConversionFactor) { return className; } } } return null; } } exports.Measurement = Measurement;