111 lines
3.0 KiB
TypeScript
111 lines
3.0 KiB
TypeScript
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();
|
|
}
|