UNPKG

@fractorysolutions/safe-units

Version:
214 lines (179 loc) 5.67 kB
import { getExponentValue } from "../exponent/exponentValueArithmetic"; import { defaultFormatUnit } from "./format"; import { GenericMeasure, MeasureFormatter, NumericOperations, } from "./genericMeasure"; import { AllowedExponents, DivideUnits, DivisorUnit, ExponentiateUnit, MultiplicandUnit, MultiplyUnits, Unit, UnitWithSymbols, } from "./unitTypeArithmetic"; import { divideUnits, exponentiateUnit, multiplyUnits, } from "./unitValueArithmetic"; type GenericMeasureConstructor<N> = new <U extends Unit>( value: N, unit: UnitWithSymbols<U>, symbol?: string, ) => GenericMeasure<N, U>; export function createMeasureClass<N>( num: NumericOperations<N>, ): GenericMeasureConstructor<N> { function getFormatter( formatter: MeasureFormatter<N> | undefined, ): Required<MeasureFormatter<N>> { if (formatter === undefined) { return { formatValue: num.format, formatUnit: defaultFormatUnit, }; } else { return { formatValue: formatter.formatValue || num.format, formatUnit: formatter.formatUnit || defaultFormatUnit, }; } } class Measure<U extends Unit> implements GenericMeasure<N, U> { public readonly symbol: string | undefined; public squared!: "2" extends AllowedExponents<U> ? () => GenericMeasure<N, ExponentiateUnit<U, "2">> : never; public cubed!: "3" extends AllowedExponents<U> ? () => GenericMeasure<N, ExponentiateUnit<U, "3">> : never; constructor( public readonly value: N, public readonly unit: UnitWithSymbols<U>, symbol?: string, ) { this.symbol = symbol; } // Arithmetic public plus(other: GenericMeasure<N, U>): Measure<U> { return new Measure(num.add(this.value, other.value), this.unit); } public minus(other: GenericMeasure<N, U>): Measure<U> { return new Measure(num.sub(this.value, other.value), this.unit); } public negate(): Measure<U> { return new Measure(num.neg(this.value), this.unit); } public scale(value: N): Measure<U> { return new Measure(num.mult(this.value, value), this.unit); } public times<V extends MultiplicandUnit<U>>( other: GenericMeasure<N, V>, ): Measure<MultiplyUnits<U, V>> { return new Measure( num.mult(this.value, other.value), multiplyUnits(this.unit, other.unit), ) as any; } public over<V extends DivisorUnit<U>>( other: GenericMeasure<N, V>, ): Measure<DivideUnits<U, V>> { return new Measure( num.div(this.value, other.value), divideUnits(this.unit, other.unit), ) as any; } public per<V extends DivisorUnit<U>>( other: GenericMeasure<N, V>, ): Measure<DivideUnits<U, V>> { return this.over(other); } public div<V extends DivisorUnit<U>>( other: GenericMeasure<N, V>, ): Measure<DivideUnits<U, V>> { return this.over(other); } public toThe<E extends AllowedExponents<U>>( power: E, ): Measure<ExponentiateUnit<U, E>> { return new Measure( num.pow(this.value, getExponentValue(power)), exponentiateUnit(this.unit, power), ); } public inverse(): GenericMeasure<N, ExponentiateUnit<U, "-1">> { return this.toThe("-1"); } public reciprocal(): GenericMeasure<N, ExponentiateUnit<U, "-1">> { return this.toThe("-1"); } public unsafeMap<V extends Unit>( valueMap: (value: N) => N, unitMap?: (unit: UnitWithSymbols<U>) => UnitWithSymbols<V>, ): GenericMeasure<N, V> { const newUnit = unitMap === undefined ? this.unit : unitMap(this.unit); return new Measure<V>( valueMap(this.value), newUnit as unknown as UnitWithSymbols<V>, ); } // Comparisons public compare(other: GenericMeasure<N, U>): number { return num.compare(this.value, other.value); } public lt(other: GenericMeasure<N, U>): boolean { return this.compare(other) < 0; } public lte(other: GenericMeasure<N, U>): boolean { return this.compare(other) <= 0; } public eq(other: GenericMeasure<N, U>): boolean { return this.compare(other) === 0; } public neq(other: GenericMeasure<N, U>): boolean { return this.compare(other) !== 0; } public gte(other: GenericMeasure<N, U>): boolean { return this.compare(other) >= 0; } public gt(other: GenericMeasure<N, U>): boolean { return this.compare(other) > 0; } // Formatting public toString(formatter?: MeasureFormatter<N>): string { const { formatValue, formatUnit } = getFormatter(formatter); return `${formatValue(this.value)} ${formatUnit(this.unit)}`.trimRight(); } public in( unit: GenericMeasure<N, U>, formatter?: MeasureFormatter<N>, ): string { if (unit.symbol === undefined) { return this.toString(formatter); } const { formatValue } = getFormatter(formatter); const value = formatValue(num.div(this.value, unit.value)); return `${value} ${unit.symbol}`; } public valueIn(unit: GenericMeasure<N, U>): N { return this.over(unit).value; } public withSymbol(symbol: string | undefined): GenericMeasure<N, U> { return new Measure(this.value, this.unit, symbol); } public clone(): GenericMeasure<N, U> { return new Measure(this.value, this.unit); } } Measure.prototype.squared = function (): any { return this.toThe("2"); }; Measure.prototype.cubed = function (): any { return this.toThe("3"); }; return Measure; }