export function gcd(a: number, b: number): number { a = Math.abs(a); b = Math.abs(b); while (b) { [a, b] = [b, a % b]; } return a; } export function lcm(a: number, b: number): number { return Math.abs(a * b) / gcd(a, b); } /** * An immutable fraction. Sign is kept on the numerator; arithmetic returns a new * Fraction and does NOT auto-simplify — call `.simplified()` when you want lowest * terms (this lets teaching views show the un-reduced working). Prefer this over * passing `(num, den)` pairs around: positional pairs are easy to mis-order. */ export class Fraction { readonly n: number; readonly d: number; constructor(n: number, d: number) { // Canonical sign: keep it on the numerator. A zero denominator is tolerated // (kept as-is) so divide-by-zero in a lesson renders rather than throwing. if (d < 0) { n = -n; d = -d; } this.n = n; this.d = d; } static of(n: number, d: number): Fraction { return new Fraction(n, d); } get isZero(): boolean { return this.n === 0; } plus(o: Fraction): Fraction { return new Fraction(this.n * o.d + o.n * this.d, this.d * o.d); } minus(o: Fraction): Fraction { return new Fraction(this.n * o.d - o.n * this.d, this.d * o.d); } times(o: Fraction): Fraction { return new Fraction(this.n * o.n, this.d * o.d); } dividedBy(o: Fraction): Fraction { return new Fraction(this.n * o.d, this.d * o.n); } simplified(): Fraction { if (this.d === 0) return this; if (this.n === 0) return new Fraction(0, 1); const g = gcd(Math.abs(this.n), Math.abs(this.d)); return new Fraction(this.n / g, this.d / g); } get isSimplified(): boolean { return gcd(Math.abs(this.n), Math.abs(this.d)) === 1; } /** Value equality (cross-multiplication), independent of the written form. */ equals(o: Fraction): boolean { return this.n * o.d === o.n * this.d; } toDecimal(): number { return this.n / this.d; } /** [whole, remainderNumerator, denominator] */ toMixed(): [number, number, number] { const whole = Math.trunc(this.n / this.d); const remainder = Math.abs(this.n % this.d); return [whole, remainder, this.d]; } toKatex(): string { if (this.d === 1) return `${this.n}`; return `\\frac{${this.n}}{${this.d}}`; } /** Like toKatex, but writes negatives with the sign in front: -\frac{3}{4} rather than \frac{-3}{4}. */ toSignedKatex(): string { if (this.d === 1) return `${this.n}`; const sign = this.n < 0 ? "-" : ""; return `${sign}\\frac{${Math.abs(this.n)}}{${this.d}}`; } toString(): string { return this.d === 1 ? `${this.n}` : `${this.n}/${this.d}`; } } /** * Convenience KaTeX formatter for raw (num, den) pairs. Handy in the step-by-step * lesson views, which build up strings from loose numerators/denominators rather * than Fraction objects. For arithmetic, use the Fraction class instead. */ export function toKatex(n: number, d: number): string { return new Fraction(n, d).toKatex(); }