UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

247 lines (205 loc) 7.12 kB
// [VexFlow](https://vexflow.com) - Copyright (c) Mohit Muthanna 2010. // MIT License // // Author: Joshua Koo / @zz85 // Author: @incompleteopus import { Category } from './typeguard'; import { RuntimeError } from './util'; /** Fraction represents a rational number. */ export class Fraction { static get CATEGORY(): string { return Category.Fraction; } // Cached objects for comparisons. private static __staticFractionA = new Fraction(); private static __staticFractionB = new Fraction(); private static __staticFractionTmp = new Fraction(); /** * GCD: Greatest common divisor using the Euclidean algorithm. * Note: GCD(0, 0) => 0 and GCD(0, n) => n. */ static GCD(a: number, b: number): number { if (typeof a !== 'number' || Number.isNaN(a) || typeof b !== 'number' || Number.isNaN(b)) { throw new RuntimeError('BadArgument', `Invalid numbers: ${a}, ${b}`); } let t; while (b !== 0) { t = b; b = a % b; a = t; } return a; } /** LCM: Lowest common multiple. */ static LCM(a: number, b: number): number { return (a * b) / Fraction.GCD(a, b); } /** Lowest common multiple for more than two numbers. */ static LCMM(args: number[]): number { if (args.length === 0) { return 0; } else if (args.length === 1) { return args[0]; } else if (args.length === 2) { return Fraction.LCM(args[0], args[1]); } else { // args.shift() removes the first number. // LCM the first number with the rest of the numbers. return Fraction.LCM(args.shift() as number, Fraction.LCMM(args)); } } numerator: number = 1; denominator: number = 1; /** Set the numerator and denominator. */ constructor(numerator?: number, denominator?: number) { this.set(numerator, denominator); } /** Set the numerator and denominator. */ set(numerator: number = 1, denominator: number = 1): this { this.numerator = numerator; this.denominator = denominator; return this; } /** Return the value of the fraction. */ value(): number { return this.numerator / this.denominator; } /** Simplify numerator and denominator using GCD. */ simplify(): this { let u = this.numerator; let d = this.denominator; const gcd = Fraction.GCD(u, d); u /= gcd; d /= gcd; if (d < 0) { d = -d; u = -u; } return this.set(u, d); } /** Add value of another fraction. */ add(param1: Fraction | number = 0, param2: number = 1): this { const [otherNumerator, otherDenominator] = getNumeratorAndDenominator(param1, param2); const lcm = Fraction.LCM(this.denominator, otherDenominator); const a = lcm / this.denominator; const b = lcm / otherDenominator; const u = this.numerator * a + otherNumerator * b; return this.set(u, lcm); } /** Substract value of another fraction. */ subtract(param1: Fraction | number = 0, param2: number = 1): this { const [otherNumerator, otherDenominator] = getNumeratorAndDenominator(param1, param2); const lcm = Fraction.LCM(this.denominator, otherDenominator); const a = lcm / this.denominator; const b = lcm / otherDenominator; const u = this.numerator * a - otherNumerator * b; return this.set(u, lcm); } /** Multiply by value of another fraction. */ multiply(param1: Fraction | number = 1, param2: number = 1): this { const [otherNumerator, otherDenominator] = getNumeratorAndDenominator(param1, param2); return this.set(this.numerator * otherNumerator, this.denominator * otherDenominator); } /** Divide by value of another Fraction. */ divide(param1: Fraction | number = 1, param2: number = 1): this { const [otherNumerator, otherDenominator] = getNumeratorAndDenominator(param1, param2); return this.set(this.numerator * otherDenominator, this.denominator * otherNumerator); } /** Simplify both sides and check if they are equal. */ equals(compare: Fraction | number): boolean { const a = Fraction.__staticFractionA.copy(compare).simplify(); const b = Fraction.__staticFractionB.copy(this).simplify(); return a.numerator === b.numerator && a.denominator === b.denominator; } /** Greater than operator. */ greaterThan(compare: Fraction | number): boolean { const a = Fraction.__staticFractionB.copy(this); a.subtract(compare); return a.numerator > 0; } /** Greater than or equals operator. */ greaterThanEquals(compare: Fraction | number): boolean { const a = Fraction.__staticFractionB.copy(this); a.subtract(compare); return a.numerator >= 0; } /** Less than operator. */ lessThan(compare: Fraction | number): boolean { return !this.greaterThanEquals(compare); } /** Less than or equals operator. */ lessThanEquals(compare: Fraction | number): boolean { return !this.greaterThan(compare); } /** Return a new copy with current values. */ clone(): Fraction { return new Fraction(this.numerator, this.denominator); } /** Copy value of another fraction. */ copy(other: Fraction | number): this { if (typeof other === 'number') { return this.set(other, 1); } else { return this.set(other.numerator, other.denominator); } } /** Return the integer component (eg. 5/2 => 2). */ quotient(): number { return Math.floor(this.numerator / this.denominator); } /** Return the remainder component (eg. 5/2 => 1). */ remainder(): number { return this.numerator % this.denominator; } /** Calculate absolute value. */ makeAbs(): this { this.denominator = Math.abs(this.denominator); this.numerator = Math.abs(this.numerator); return this; } /** Return a raw string representation (eg. "5/2"). */ toString(): string { return `${this.numerator}/${this.denominator}`; } /** Return a simplified string respresentation. */ toSimplifiedString(): string { return Fraction.__staticFractionTmp.copy(this).simplify().toString(); } /** Return string representation in mixed form. */ toMixedString(): string { let s = ''; const q = this.quotient(); const f = Fraction.__staticFractionTmp.copy(this); if (q < 0) { f.makeAbs(); } if (q !== 0) { s += q; if (f.numerator !== 0) { s += ` ${f.toSimplifiedString()}`; } } else if (f.numerator === 0) { s = '0'; } else { s = f.toSimplifiedString(); } return s; } /** Parse a fraction string. */ parse(str: string): this { const i = str.split('/'); const n = parseInt(i[0], 10); const d = i[1] ? parseInt(i[1], 10) : 1; return this.set(n, d); } } /** Helper function to extract the numerator and denominator from another fraction. */ function getNumeratorAndDenominator(n: Fraction | number, d: number = 1): [number, number] { if (typeof n === 'number') { // Both params are numbers, so we return them as [numerator, denominator]. return [n, d]; } else { // First param is a Fraction object. We ignore the second param. return [n.numerator, n.denominator]; } }