/**
 * A class representing mathematical fractions, which have a numerator and a denominator.
 */ export class Fraction { private static maximumAllowedNumber : number = 46340 ; private numerator : number = 0 ; private denominator : number = 1 ; private wholeValue : number = 0 ; private realValue : number ; param f1 param f2 returns { Fraction } public static max ( f1 , f2 ) { if ( f1 . RealValue > f2 . RealValue ) { return f1 ; } else { return f2 ; } } public static Equal ( f1 , f2 ) { return f1 . wholeValue === f2 . wholeValue && f1 . Denominator === f2 . Denominator && f1 . Numerator === f2 . Numerator ; } param fraction returns { Fraction } public static createFromFraction ( fraction ) { return new Fraction ( fraction . numerator , fraction . denominator , fraction . wholeValue , false ) ; } public static plus ( f1 , f2 ) { const sum = f1 . clone ( ) ; sum . Add ( f2 ) ; return sum ; } public static minus ( f1 , f2 ) { const sum = f1 . clone ( ) ; sum . Sub ( f2 ) ; return sum ; } public static multiply ( f1 , f2 ) { return new Fraction ( ( f1 . wholeValue * f1 . denominator + f1 . numerator ) * ( f2 . wholeValue * f2 . denominator + f2 . numerator ) , f1 . denominator * f2 . denominator ) ; } private static greatestCommonDenominator ( a , b ) { if ( a === 0 ) { return b ; } if ( b === 1 ) { return 1 ; } while ( Math . abs ( b ) > 1e-8 ) { if ( a > b ) { a -= b ; } else { b -= a ; } } return Math . round ( a ) ; } param numerator param denominator param wholeValue param simplify constructor ( numerator = 0 , denominator = 1 , wholeValue = 0 , simplify = true ) { this . numerator = numerator ; this . denominator = denominator ; this . wholeValue = wholeValue ; if ( simplify ) { this . simplify ( ) ; } this . setRealValue ( ) ; } public toString ( ) { let result = this . numerator + "/" + this . denominator ; if ( this . wholeValue !== 0 ) { result = this . wholeValue + " " + result ; } return result ; } public clone ( ) { return new Fraction ( this . numerator , this . denominator , this . wholeValue , false ) ; } public get Numerator ( ) : number { return this . numerator ; } public set Numerator ( value ) { if ( this . numerator !== value ) { this . numerator = value ; this . simplify ( ) ; this . setRealValue ( ) ; } } public get Denominator ( ) : number { return this . denominator ; } public set Denominator ( value ) { if ( this . denominator !== value ) { this . denominator = value ; if ( this . numerator !== 0 ) { this . simplify ( ) ; } this . setRealValue ( ) ; } } public get WholeValue ( ) : number { return this . wholeValue ; } public set WholeValue ( value ) { if ( this . wholeValue !== value ) { this . wholeValue = value ; this . setRealValue ( ) ; } } /**
     * Returns the unified numerator where the whole value will be expanded
     * with the denominator and added to the existing numerator.
     */ public GetExpandedNumerator ( ) { return this . wholeValue * this . denominator + this . numerator ; } public calculateNumberOfNeededDots ( ) { let num = 1 ; let product = 2 ; const expandedNumerator = this . GetExpandedNumerator ( ) ; while ( product < expandedNumerator ) { num ++ ; product = Math . pow ( 2 , num ) ; } return Math . min ( 3 , num - 1 ) ; } public IsNegative ( ) { return this . realValue < 0 ; } public get RealValue ( ) : number { return this . realValue ; } public expand ( expansionValue ) { this . numerator *= expansionValue ; this . denominator *= expansionValue ; if ( this . wholeValue !== 0 ) { this . numerator += this . wholeValue * this . denominator ; this . wholeValue = 0 ; } } param fraction public Add ( fraction ) { this . numerator = ( this . wholeValue * this . denominator + this . numerator ) * fraction . denominator + ( fraction . wholeValue * fraction . denominator + fraction . numerator ) * this . denominator ; this . denominator = this . denominator * fraction . denominator ; this . wholeValue = 0 ; this . simplify ( ) ; this . setRealValue ( ) ; return this ; } param fraction public Sub ( fraction ) { this . numerator = ( this . wholeValue * this . denominator + this . numerator ) * fraction . denominator - ( fraction . wholeValue * fraction . denominator + fraction . numerator ) * this . denominator ; this . denominator = this . denominator * fraction . denominator ; this . wholeValue = 0 ; this . simplify ( ) ; this . setRealValue ( ) ; return this ; } param maxAllowedDenominator public Quantize ( maxAllowedDenominator ) { if ( this . denominator <= maxAllowedDenominator ) { return this ; } const upTestFraction = new Fraction ( this . numerator + 1 , this . denominator , this . wholeValue ) ; while ( upTestFraction . Denominator > maxAllowedDenominator ) { upTestFraction . Numerator ++ ; } if ( this . numerator > this . denominator ) { const downTestFraction = new Fraction ( this . numerator - 1 , this . denominator , this . wholeValue ) ; while ( downTestFraction . Denominator > maxAllowedDenominator ) { downTestFraction . Numerator -- ; } if ( downTestFraction . Denominator < upTestFraction . Denominator ) { return downTestFraction ; } } return upTestFraction ; } public Equals ( obj ) { return this . realValue === obj ?. realValue ; } public CompareTo ( obj ) { const diff = this . realValue - obj . realValue ; return diff ? diff < 0 ? - 1 : 1 : 0 ; } public lt ( frac ) { return this . realValue < frac . realValue ; } public lte ( frac ) { return this . realValue <= frac . realValue ; } public gt ( frac ) { return ! this . lte ( frac ) ; } public gte ( frac ) { return ! this . lt ( frac ) ; } private setRealValue ( ) { this . realValue = this . wholeValue + this . numerator / this . denominator ; } private simplify ( ) { if ( this . numerator === 0 ) { this . denominator = 1 ; return ; } const i = Fraction . greatestCommonDenominator ( Math . abs ( this . numerator ) , Math . abs ( this . denominator ) ) ; this . numerator /= i ; this . denominator /= i ; const whole = Math . floor ( this . numerator / this . denominator ) ; if ( whole !== 0 ) { this . wholeValue += whole ; this . numerator -= whole * this . denominator ; if ( this . numerator === 0 ) { this . denominator = 1 ; } } if ( this . denominator > Fraction . maximumAllowedNumber ) { const factor = this . denominator / Fraction . maximumAllowedNumber ; this . numerator = Math . round ( this . numerator / factor ) ; this . denominator = Math . round ( this . denominator / factor ) ; } if ( this . numerator > Fraction . maximumAllowedNumber ) { const factor = this . numerator / Fraction . maximumAllowedNumber ; this . numerator = Math . round ( this . numerator / factor ) ; this . denominator = Math . round ( this . denominator / factor ) ; } } public static FloatInaccuracyTolerance : number = 0.0001 ; public isOnBeat ( timeSignature ) { const beatDistance = this . distanceFromBeat ( timeSignature ) ; return Math . abs ( beatDistance ) < Fraction . FloatInaccuracyTolerance ; } public distanceFromBeat ( timeSignature ) { const beatStep = new Fraction ( 1 , timeSignature . Denominator ) ; const distanceFromBeat = this . RealValue % beatStep . RealValue ; return distanceFromBeat ; } }