Fraction.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451
  1. // TODO: Check the operators' names
  2. // TODO: This class should probably be immutable?
  3. /**
  4. * A class representing mathematical fractions, which have a numerator and a denominator.
  5. */
  6. export class Fraction {
  7. private static maximumAllowedNumber: number = 46340; // sqrt(int.Max) --> signed int with 4 bytes (2^31)
  8. private numerator: number = 0;
  9. private denominator: number = 1;
  10. private wholeValue: number = 0;
  11. private realValue: number;
  12. /**
  13. * Returns the maximum of two fractions (does not clone)
  14. * @param f1
  15. * @param f2
  16. * @returns {Fraction}
  17. */
  18. public static max(f1: Fraction, f2: Fraction): Fraction {
  19. if (f1.RealValue > f2.RealValue) {
  20. return f1;
  21. } else {
  22. return f2;
  23. }
  24. }
  25. public static Equal(f1: Fraction, f2: Fraction): boolean {
  26. return f1.wholeValue === f2.wholeValue && f1.Denominator === f2.Denominator && f1.Numerator === f2.Numerator;
  27. }
  28. /**
  29. * The same as Fraction.clone
  30. * @param fraction
  31. * @returns {Fraction}
  32. */
  33. public static createFromFraction(fraction: Fraction): Fraction {
  34. return new Fraction(fraction.numerator, fraction.denominator, fraction.wholeValue, false);
  35. }
  36. public static plus(f1: Fraction, f2: Fraction): Fraction {
  37. const sum: Fraction = f1.clone();
  38. sum.Add(f2);
  39. return sum;
  40. }
  41. public static minus(f1: Fraction, f2: Fraction): Fraction {
  42. const sum: Fraction = f1.clone();
  43. sum.Sub(f2);
  44. return sum;
  45. }
  46. private static greatestCommonDenominator(a: number, b: number): number {
  47. if (a === 0) {
  48. return b;
  49. }
  50. if (b === 1) {
  51. return 1;
  52. }
  53. while (b !== 0) {
  54. if (a > b) {
  55. a -= b;
  56. } else {
  57. b -= a;
  58. }
  59. }
  60. return a;
  61. }
  62. /**
  63. *
  64. * @param numerator
  65. * @param denominator
  66. * @param wholeValue - the integer number, needed for values greater than 1
  67. * @param simplify - If simplify is true, then the fraction is simplified
  68. * to make both the numerator and denominator coprime, and less than maximumAllowedNumber.
  69. */
  70. constructor(numerator: number = 0, denominator: number = 1, wholeValue: number = 0, simplify: boolean = true) {
  71. this.numerator = numerator;
  72. this.denominator = denominator;
  73. this.wholeValue = wholeValue;
  74. if (simplify) {
  75. this.simplify();
  76. }
  77. this.setRealValue();
  78. }
  79. public toString(): string {
  80. let result: string = this.numerator + "/" + this.denominator;
  81. if (this.wholeValue !== 0) {
  82. result = this.wholeValue + " " + result;
  83. }
  84. return result;
  85. }
  86. public clone(): Fraction {
  87. return new Fraction(this.numerator, this.denominator, this.wholeValue, false);
  88. }
  89. public get Numerator(): number {
  90. return this.numerator;
  91. }
  92. public set Numerator(value: number) {
  93. if (this.numerator !== value) {
  94. this.numerator = value;
  95. this.simplify();
  96. this.setRealValue();
  97. }
  98. }
  99. public get Denominator(): number {
  100. return this.denominator;
  101. }
  102. public set Denominator(value: number) {
  103. if (this.denominator !== value) {
  104. this.denominator = value;
  105. // don't simplify in case of a GraceNote (need it in order to set the right symbol)
  106. if (this.numerator !== 0) {
  107. this.simplify();
  108. }
  109. this.setRealValue();
  110. }
  111. }
  112. public get WholeValue(): number {
  113. return this.wholeValue;
  114. }
  115. public set WholeValue(value: number) {
  116. if (this.wholeValue !== value) {
  117. this.wholeValue = value;
  118. this.setRealValue();
  119. }
  120. }
  121. /**
  122. * Returns the unified numerator where the whole value will be expanded
  123. * with the denominator and added to the existing numerator.
  124. */
  125. public GetExpandedNumerator(): number {
  126. return this.wholeValue * this.denominator + this.numerator;
  127. }
  128. public IsNegative(): boolean {
  129. return this.realValue < 0;
  130. }
  131. public get RealValue(): number {
  132. return this.realValue;
  133. }
  134. public expand(expansionValue: number): void {
  135. this.numerator *= expansionValue;
  136. this.denominator *= expansionValue;
  137. if (this.wholeValue !== 0) {
  138. this.numerator += this.wholeValue * this.denominator;
  139. this.wholeValue = 0;
  140. }
  141. }
  142. // public multiplyDenominatorWithFactor(factor: number): void {
  143. // this.denominator *= factor;
  144. // this.setRealValue();
  145. // }
  146. /**
  147. * Adds a Fraction to this Fraction.
  148. * Attention: This changes the already existing Fraction, which might be referenced elsewhere!
  149. * Use Fraction.plus() for creating a new Fraction object being the sum of two Fractions.
  150. * @param fraction the Fraction to add.
  151. */
  152. public Add(fraction: Fraction): void {
  153. // normally should check if denominator or fraction.denominator is 0 but in our case
  154. // a zero denominator doesn't make sense
  155. this.numerator = (this.wholeValue * this.denominator + this.numerator) * fraction.denominator +
  156. (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
  157. this.denominator = this.denominator * fraction.denominator;
  158. this.wholeValue = 0;
  159. this.simplify();
  160. this.setRealValue();
  161. }
  162. /**
  163. * Subtracts a Fraction from this Fraction.
  164. * Attention: This changes the already existing Fraction, which might be referenced elsewhere!
  165. * Use Fraction.minus() for creating a new Fraction object being the difference of two Fractions.
  166. * @param fraction the Fraction to subtract.
  167. */
  168. public Sub(fraction: Fraction): void {
  169. // normally should check if denominator or fraction.denominator is 0 but in our case
  170. // a zero denominator doesn't make sense
  171. this.numerator = (this.wholeValue * this.denominator + this.numerator) * fraction.denominator -
  172. (fraction.wholeValue * fraction.denominator + fraction.numerator) * this.denominator;
  173. this.denominator = this.denominator * fraction.denominator;
  174. this.wholeValue = 0;
  175. this.simplify();
  176. this.setRealValue();
  177. }
  178. /**
  179. * Brute Force quanization by searching incremental with the numerator until the denominator is
  180. * smaller/equal than the desired one.
  181. * @param maxAllowedDenominator
  182. */
  183. public Quantize(maxAllowedDenominator: number): Fraction {
  184. if (this.denominator <= maxAllowedDenominator) {
  185. return this;
  186. }
  187. const upTestFraction: Fraction = new Fraction(this.numerator + 1, this.denominator, this.wholeValue);
  188. while (upTestFraction.Denominator > maxAllowedDenominator) {
  189. upTestFraction.Numerator++;
  190. }
  191. if (this.numerator > this.denominator) {
  192. const downTestFraction: Fraction = new Fraction(this.numerator - 1, this.denominator, this.wholeValue);
  193. while (downTestFraction.Denominator > maxAllowedDenominator) {
  194. downTestFraction.Numerator--;
  195. }
  196. if (downTestFraction.Denominator < upTestFraction.Denominator) {
  197. return downTestFraction;
  198. }
  199. }
  200. return upTestFraction;
  201. }
  202. public Equals(obj: Fraction): boolean {
  203. return this.realValue === obj.realValue;
  204. }
  205. public CompareTo(obj: Fraction): number {
  206. const diff: number = this.realValue - obj.realValue;
  207. // Return the sign of diff
  208. return diff ? diff < 0 ? -1 : 1 : 0;
  209. }
  210. public lt(frac: Fraction): boolean {
  211. return this.realValue < frac.realValue;
  212. }
  213. public lte(frac: Fraction): boolean {
  214. return this.realValue <= frac.realValue;
  215. }
  216. //public Equals(f: Fraction): boolean {
  217. // if (ReferenceEquals(this, f))
  218. // return true;
  219. // if (ReferenceEquals(f, undefined))
  220. // return false;
  221. // return <number>this.numerator * f.denominator === <number>f.numerator * this.denominator;
  222. //}
  223. private setRealValue(): void {
  224. this.realValue = this.wholeValue + this.numerator / this.denominator;
  225. }
  226. private simplify(): void {
  227. // don't simplify in case of a GraceNote (need it in order to set the right symbol)
  228. if (this.numerator === 0) {
  229. this.denominator = 1;
  230. return;
  231. }
  232. // normally should check if denominator or fraction.denominator is 0 but in our case a zero denominator
  233. // doesn't make sense. Could probably be optimized
  234. const i: number = Fraction.greatestCommonDenominator(Math.abs(this.numerator), Math.abs(this.denominator));
  235. this.numerator /= i;
  236. this.denominator /= i;
  237. const whole: number = Math.floor(this.numerator / this.denominator);
  238. if (whole !== 0) {
  239. this.wholeValue += whole;
  240. this.numerator -= whole * this.denominator;
  241. if (this.numerator === 0) {
  242. this.denominator = 1;
  243. }
  244. }
  245. if (this.denominator > Fraction.maximumAllowedNumber) {
  246. const factor: number = <number>this.denominator / Fraction.maximumAllowedNumber;
  247. this.numerator = <number>Math.round(this.numerator / factor);
  248. this.denominator = <number>Math.round(this.denominator / factor);
  249. }
  250. if (this.numerator > Fraction.maximumAllowedNumber) {
  251. const factor: number = <number>this.numerator / Fraction.maximumAllowedNumber;
  252. this.numerator = <number>Math.round(this.numerator / factor);
  253. this.denominator = <number>Math.round(this.denominator / factor);
  254. }
  255. }
  256. //private static equals(f1: Fraction, f2: Fraction): boolean {
  257. // return <number>f1.numerator * f2.denominator === <number>f2.numerator * f1.denominator;
  258. //}
  259. //
  260. //public static ApproximateFractionFromValue(value: number, epsilonForPrecision: number): Fraction {
  261. // let n: number = 1;
  262. // let d: number = 1;
  263. // let fraction: number = n / d;
  264. // while (Math.abs(fraction - value) > epsilonForPrecision) {
  265. // if (fraction < value) {
  266. // n++;
  267. // }
  268. // else {
  269. // d++;
  270. // n = <number>Math.round(value * d);
  271. // }
  272. // fraction = n / <number>d;
  273. // }
  274. // return new Fraction(n, d);
  275. //}
  276. //public static GetEarlierTimestamp(m1: Fraction, m2: Fraction): Fraction {
  277. // if (m1 < m2)
  278. // return m1;
  279. // else return m2;
  280. //}
  281. //public static getFraction(value: number, denominatorPrecision: number): Fraction {
  282. // let numerator: number = <number>Math.round(value / (1.0 / denominatorPrecision));
  283. // return new Fraction(numerator, denominatorPrecision);
  284. //}
  285. //public static fractionMin(f1: Fraction, f2: Fraction): Fraction {
  286. // if (f1 < f2)
  287. // return f1;
  288. // else return f2;
  289. //}
  290. //public static GetMaxValue(): Fraction {
  291. // return new Fraction(Fraction.maximumAllowedNumber, 1);
  292. //}
  293. //public static get MaxAllowedNumerator(): number {
  294. // return Fraction.maximumAllowedNumber;
  295. //}
  296. //public static get MaxAllowedDenominator(): number {
  297. // return Fraction.maximumAllowedNumber;
  298. //}
  299. //public ToFloatingString(): string {
  300. // return this.RealValue.ToString();
  301. //}
  302. //public Compare(x: Fraction, y: Fraction): number {
  303. // if (x > y)
  304. // return 1;
  305. // if (x < y)
  306. // return -1;
  307. // return 0;
  308. //}
  309. //#region operators
  310. //
  311. // // operator overloads must always come in pairs
  312. // // operator overload +
  313. // public static Fraction operator + (Fraction f1, Fraction f2)
  314. //{
  315. // Fraction sum = new Fraction(f1);
  316. // sum.Add(f2);
  317. // return sum;
  318. //}
  319. //
  320. //// operator overload -
  321. //public static Fraction operator - (Fraction f1, Fraction f2)
  322. //{
  323. // Fraction diff = new Fraction(f1);
  324. // diff.Sub(f2);
  325. // return diff;
  326. //}
  327. //
  328. //// operator overloads must always come in pairs
  329. //// operator overload >
  330. //public static bool operator > (Fraction f1, Fraction f2)
  331. //{
  332. // //return (long) f1.Numerator*f2._denominator > (long) f2._numerator*f1._denominator;
  333. // return f1.RealValue > f2.RealValue;
  334. //}
  335. //
  336. //// operator overload <
  337. //public static bool operator < (Fraction f1, Fraction f2)
  338. //{
  339. // //return (long) f1._numerator*f2._denominator < (long) f2._numerator*f1._denominator;
  340. // return f1.RealValue < f2.RealValue;
  341. //}
  342. //
  343. //// operator overload ==
  344. //public static bool operator === (Fraction f1, Fraction f2)
  345. //{
  346. // // code enhanced for performance
  347. // // System.Object.ReferenceEquals(f1, undefined) is better than if (f1 === undefined)
  348. // // and comparisons between booleans are quick
  349. // bool f1IsNull = System.Object.ReferenceEquals(f1, undefined);
  350. // bool f2IsNull = System.Object.ReferenceEquals(f2, undefined);
  351. //
  352. // // method returns true when both are undefined, false when only the first is undefined, otherwise the result of equals
  353. // if (f1IsNull !== f2IsNull)
  354. // return false;
  355. //
  356. // if (f1IsNull /*&& f2IsNull*/)
  357. // return true;
  358. //
  359. // return equals(f1, f2);
  360. //}
  361. //
  362. //// operator overload !=
  363. //public static bool operator !== (Fraction f1, Fraction f2)
  364. //{
  365. // return (!(f1 === f2));
  366. //}
  367. //
  368. //// operator overload >=
  369. //public static bool operator >= (Fraction f1, Fraction f2)
  370. //{
  371. // return (!(f1 < f2));
  372. //}
  373. //
  374. //// operator overload <=
  375. //public static bool operator <= (Fraction f1,Fraction f2)
  376. //{
  377. // return (!(f1 > f2));
  378. //}
  379. //
  380. //public static Fraction operator / (Fraction f, int i)
  381. //{
  382. // return new Fraction(f._numerator, f._denominator *= i);
  383. //}
  384. //
  385. //public static Fraction operator / (Fraction f1, Fraction f2)
  386. //{
  387. // let res = new Fraction(f1.Numerator*f2.Denominator, f1.Denominator*f2.Numerator);
  388. // return res.Denominator === 0 ? new Fraction(0, 1) : res;
  389. //}
  390. //
  391. //public static Fraction operator * (Fraction f1, Fraction f2)
  392. //{
  393. // return new Fraction(f1.Numerator*f2.Numerator, f1.Denominator*f2.Denominator);
  394. //}
  395. //
  396. //public static Fraction operator % (Fraction f1, Fraction f2)
  397. //{
  398. // let a = f1/f2;
  399. // return new Fraction(a.Numerator%a.Denominator, a.Denominator)*f2;
  400. //}
  401. //
  402. //#endregion operators
  403. }