import { gcd } from "."; /** Serializable representation of a ratio. */ export type RatioData = [numerator: number, denominator: number]; /** Representation of a ratio for performing fractional artithmetic. */ export default class Ratio { readonly #numerator: number; readonly #denominator: number; get numerator(): number { return this.#numerator; } get denominator(): number { return this.#denominator; } constructor(numerator: number, denominator: number) { if (!Number.isInteger(numerator) || !Number.isInteger(denominator)) { throw new TypeError( `Ratio must have integer parts: ${numerator} / ${denominator}`, ); } if (denominator === 0) { throw new RangeError("Ratio demnominator cannot be zero"); } const divisor = gcd(numerator, denominator); this.#numerator = numerator / divisor; this.#denominator = denominator / divisor; } multiplyRatio(other: Ratio): Ratio { return new Ratio( this.numerator * other.numerator, this.denominator * other.denominator, ); } divideRatio(other: Ratio): Ratio { return new Ratio( this.numerator * other.denominator, this.denominator * other.numerator, ); } add(other: Ratio): Ratio { return new Ratio( this.numerator * other.denominator + other.numerator * this.denominator, this.denominator * other.denominator, ); } subtract(other: Ratio): Ratio { return new Ratio( this.numerator * other.denominator - other.numerator * this.denominator, this.denominator * other.denominator, ); } compare(other: Ratio): number { const left = this.numerator * other.denominator; const right = other.numerator * this.denominator; return left < right ? -1 : left > right ? 1 : 0; } equals(other: Ratio): boolean { return this.compare(other) === 0; } toNumber(): number { return this.numerator / this.denominator; } toString(): string { return `${this.numerator}/${this.denominator}`; } [Symbol.for("nodejs.util.inspect.custom")](): string { return `Ratio { ${this.numerator}/${this.denominator} }`; } static fromInteger(n: number): Ratio { return new Ratio(n, 1); } toData(): RatioData { return [this.numerator, this.denominator]; } static fromData(ratio: RatioData): Ratio { return new Ratio(ratio[0], ratio[1]); } static min(...ratios: Ratio[]): Ratio { return ratios.reduce((a, b) => (a.compare(b) <= 0 ? a : b)); } static max(...ratios: Ratio[]): Ratio { return ratios.reduce((a, b) => (a.compare(b) >= 0 ? a : b)); } }