You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

466 lines
15 KiB

6 months ago
  1. # Fraction.js - ℚ in JavaScript
  2. [![NPM Package](https://img.shields.io/npm/v/fraction.js.svg?style=flat)](https://npmjs.org/package/fraction.js "View this project on npm")
  3. [![MIT license](http://img.shields.io/badge/license-MIT-brightgreen.svg)](http://opensource.org/licenses/MIT)
  4. Tired of inprecise numbers represented by doubles, which have to store rational and irrational numbers like PI or sqrt(2) the same way? Obviously the following problem is preventable:
  5. ```javascript
  6. 1 / 98 * 98 // = 0.9999999999999999
  7. ```
  8. If you need more precision or just want a fraction as a result, just include *Fraction.js*:
  9. ```javascript
  10. var Fraction = require('fraction.js');
  11. // or
  12. import Fraction from 'fraction.js';
  13. ```
  14. and give it a trial:
  15. ```javascript
  16. Fraction(1).div(98).mul(98) // = 1
  17. ```
  18. Internally, numbers are represented as *numerator / denominator*, which adds just a little overhead. However, the library is written with performance and accuracy in mind, which makes it the perfect basis for [Polynomial.js](https://github.com/infusion/Polynomial.js) and [Math.js](https://github.com/josdejong/mathjs).
  19. Convert decimal to fraction
  20. ===
  21. The simplest job for fraction.js is to get a fraction out of a decimal:
  22. ```javascript
  23. var x = new Fraction(1.88);
  24. var res = x.toFraction(true); // String "1 22/25"
  25. ```
  26. Examples / Motivation
  27. ===
  28. A simple example might be
  29. ```javascript
  30. var f = new Fraction("9.4'31'"); // 9.4313131313131...
  31. f.mul([-4, 3]).mod("4.'8'"); // 4.88888888888888...
  32. ```
  33. The result is
  34. ```javascript
  35. console.log(f.toFraction()); // -4154 / 1485
  36. ```
  37. You could of course also access the sign (s), numerator (n) and denominator (d) on your own:
  38. ```javascript
  39. f.s * f.n / f.d = -1 * 4154 / 1485 = -2.797306...
  40. ```
  41. If you would try to calculate it yourself, you would come up with something like:
  42. ```javascript
  43. (9.4313131 * (-4 / 3)) % 4.888888 = -2.797308133...
  44. ```
  45. Quite okay, but yea - not as accurate as it could be.
  46. Laplace Probability
  47. ===
  48. Simple example. What's the probability of throwing a 3, and 1 or 4, and 2 or 4 or 6 with a fair dice?
  49. P({3}):
  50. ```javascript
  51. var p = new Fraction([3].length, 6).toString(); // 0.1(6)
  52. ```
  53. P({1, 4}):
  54. ```javascript
  55. var p = new Fraction([1, 4].length, 6).toString(); // 0.(3)
  56. ```
  57. P({2, 4, 6}):
  58. ```javascript
  59. var p = new Fraction([2, 4, 6].length, 6).toString(); // 0.5
  60. ```
  61. Convert degrees/minutes/seconds to precise rational representation:
  62. ===
  63. 57+45/60+17/3600
  64. ```javascript
  65. var deg = 57; // 57°
  66. var min = 45; // 45 Minutes
  67. var sec = 17; // 17 Seconds
  68. new Fraction(deg).add(min, 60).add(sec, 3600).toString() // -> 57.7547(2)
  69. ```
  70. Rational approximation of irrational numbers
  71. ===
  72. Now it's getting messy ;d To approximate a number like *sqrt(5) - 2* with a numerator and denominator, you can reformat the equation as follows: *pow(n / d + 2, 2) = 5*.
  73. Then the following algorithm will generate the rational number besides the binary representation.
  74. ```javascript
  75. var x = "/", s = "";
  76. var a = new Fraction(0),
  77. b = new Fraction(1);
  78. for (var n = 0; n <= 10; n++) {
  79. var c = a.add(b).div(2);
  80. console.log(n + "\t" + a + "\t" + b + "\t" + c + "\t" + x);
  81. if (c.add(2).pow(2) < 5) {
  82. a = c;
  83. x = "1";
  84. } else {
  85. b = c;
  86. x = "0";
  87. }
  88. s+= x;
  89. }
  90. console.log(s)
  91. ```
  92. The result is
  93. ```
  94. n a[n] b[n] c[n] x[n]
  95. 0 0/1 1/1 1/2 /
  96. 1 0/1 1/2 1/4 0
  97. 2 0/1 1/4 1/8 0
  98. 3 1/8 1/4 3/16 1
  99. 4 3/16 1/4 7/32 1
  100. 5 7/32 1/4 15/64 1
  101. 6 15/64 1/4 31/128 1
  102. 7 15/64 31/128 61/256 0
  103. 8 15/64 61/256 121/512 0
  104. 9 15/64 121/512 241/1024 0
  105. 10 241/1024 121/512 483/2048 1
  106. ```
  107. Thus the approximation after 11 iterations of the bisection method is *483 / 2048* and the binary representation is 0.00111100011 (see [WolframAlpha](http://www.wolframalpha.com/input/?i=sqrt%285%29-2+binary))
  108. I published another example on how to approximate PI with fraction.js on my [blog](http://www.xarg.org/2014/03/precise-calculations-in-javascript/) (Still not the best idea to approximate irrational numbers, but it illustrates the capabilities of Fraction.js perfectly).
  109. Get the exact fractional part of a number
  110. ---
  111. ```javascript
  112. var f = new Fraction("-6.(3416)");
  113. console.log("" + f.mod(1).abs()); // 0.(3416)
  114. ```
  115. Mathematical correct modulo
  116. ---
  117. The behaviour on negative congruences is different to most modulo implementations in computer science. Even the *mod()* function of Fraction.js behaves in the typical way. To solve the problem of having the mathematical correct modulo with Fraction.js you could come up with this:
  118. ```javascript
  119. var a = -1;
  120. var b = 10.99;
  121. console.log(new Fraction(a)
  122. .mod(b)); // Not correct, usual Modulo
  123. console.log(new Fraction(a)
  124. .mod(b).add(b).mod(b)); // Correct! Mathematical Modulo
  125. ```
  126. fmod() impreciseness circumvented
  127. ---
  128. It turns out that Fraction.js outperforms almost any fmod() implementation, including JavaScript itself, [php.js](http://phpjs.org/functions/fmod/), C++, Python, Java and even Wolframalpha due to the fact that numbers like 0.05, 0.1, ... are infinite decimal in base 2.
  129. The equation *fmod(4.55, 0.05)* gives *0.04999999999999957*, wolframalpha says *1/20*. The correct answer should be **zero**, as 0.05 divides 4.55 without any remainder.
  130. Parser
  131. ===
  132. Any function (see below) as well as the constructor of the *Fraction* class parses its input and reduce it to the smallest term.
  133. You can pass either Arrays, Objects, Integers, Doubles or Strings.
  134. Arrays / Objects
  135. ---
  136. ```javascript
  137. new Fraction(numerator, denominator);
  138. new Fraction([numerator, denominator]);
  139. new Fraction({n: numerator, d: denominator});
  140. ```
  141. Integers
  142. ---
  143. ```javascript
  144. new Fraction(123);
  145. ```
  146. Doubles
  147. ---
  148. ```javascript
  149. new Fraction(55.4);
  150. ```
  151. **Note:** If you pass a double as it is, Fraction.js will perform a number analysis based on Farey Sequences. If you concern performance, cache Fraction.js objects and pass arrays/objects.
  152. The method is really precise, but too large exact numbers, like 1234567.9991829 will result in a wrong approximation. If you want to keep the number as it is, convert it to a string, as the string parser will not perform any further observations. If you have problems with the approximation, in the file `examples/approx.js` is a different approximation algorithm, which might work better in some more specific use-cases.
  153. Strings
  154. ---
  155. ```javascript
  156. new Fraction("123.45");
  157. new Fraction("123/45"); // A rational number represented as two decimals, separated by a slash
  158. new Fraction("123:45"); // A rational number represented as two decimals, separated by a colon
  159. new Fraction("4 123/45"); // A rational number represented as a whole number and a fraction
  160. new Fraction("123.'456'"); // Note the quotes, see below!
  161. new Fraction("123.(456)"); // Note the brackets, see below!
  162. new Fraction("123.45'6'"); // Note the quotes, see below!
  163. new Fraction("123.45(6)"); // Note the brackets, see below!
  164. ```
  165. Two arguments
  166. ---
  167. ```javascript
  168. new Fraction(3, 2); // 3/2 = 1.5
  169. ```
  170. Repeating decimal places
  171. ---
  172. *Fraction.js* can easily handle repeating decimal places. For example *1/3* is *0.3333...*. There is only one repeating digit. As you can see in the examples above, you can pass a number like *1/3* as "0.'3'" or "0.(3)", which are synonym. There are no tests to parse something like 0.166666666 to 1/6! If you really want to handle this number, wrap around brackets on your own with the function below for example: 0.1(66666666)
  173. Assume you want to divide 123.32 / 33.6(567). [WolframAlpha](http://www.wolframalpha.com/input/?i=123.32+%2F+%2812453%2F370%29) states that you'll get a period of 1776 digits. *Fraction.js* comes to the same result. Give it a try:
  174. ```javascript
  175. var f = new Fraction("123.32");
  176. console.log("Bam: " + f.div("33.6(567)"));
  177. ```
  178. To automatically make a number like "0.123123123" to something more Fraction.js friendly like "0.(123)", I hacked this little brute force algorithm in a 10 minutes. Improvements are welcome...
  179. ```javascript
  180. function formatDecimal(str) {
  181. var comma, pre, offset, pad, times, repeat;
  182. if (-1 === (comma = str.indexOf(".")))
  183. return str;
  184. pre = str.substr(0, comma + 1);
  185. str = str.substr(comma + 1);
  186. for (var i = 0; i < str.length; i++) {
  187. offset = str.substr(0, i);
  188. for (var j = 0; j < 5; j++) {
  189. pad = str.substr(i, j + 1);
  190. times = Math.ceil((str.length - offset.length) / pad.length);
  191. repeat = new Array(times + 1).join(pad); // Silly String.repeat hack
  192. if (0 === (offset + repeat).indexOf(str)) {
  193. return pre + offset + "(" + pad + ")";
  194. }
  195. }
  196. }
  197. return null;
  198. }
  199. var f, x = formatDecimal("13.0123123123"); // = 13.0(123)
  200. if (x !== null) {
  201. f = new Fraction(x);
  202. }
  203. ```
  204. Attributes
  205. ===
  206. The Fraction object allows direct access to the numerator, denominator and sign attributes. It is ensured that only the sign-attribute holds sign information so that a sign comparison is only necessary against this attribute.
  207. ```javascript
  208. var f = new Fraction('-1/2');
  209. console.log(f.n); // Numerator: 1
  210. console.log(f.d); // Denominator: 2
  211. console.log(f.s); // Sign: -1
  212. ```
  213. Functions
  214. ===
  215. Fraction abs()
  216. ---
  217. Returns the actual number without any sign information
  218. Fraction neg()
  219. ---
  220. Returns the actual number with flipped sign in order to get the additive inverse
  221. Fraction add(n)
  222. ---
  223. Returns the sum of the actual number and the parameter n
  224. Fraction sub(n)
  225. ---
  226. Returns the difference of the actual number and the parameter n
  227. Fraction mul(n)
  228. ---
  229. Returns the product of the actual number and the parameter n
  230. Fraction div(n)
  231. ---
  232. Returns the quotient of the actual number and the parameter n
  233. Fraction pow(exp)
  234. ---
  235. Returns the power of the actual number, raised to an possible rational exponent. If the result becomes non-rational the function returns `null`.
  236. Fraction mod(n)
  237. ---
  238. Returns the modulus (rest of the division) of the actual object and n (this % n). It's a much more precise [fmod()](#fmod-impreciseness-circumvented) if you like. Please note that *mod()* is just like the modulo operator of most programming languages. If you want a mathematical correct modulo, see [here](#mathematical-correct-modulo).
  239. Fraction mod()
  240. ---
  241. Returns the modulus (rest of the division) of the actual object (numerator mod denominator)
  242. Fraction gcd(n)
  243. ---
  244. Returns the fractional greatest common divisor
  245. Fraction lcm(n)
  246. ---
  247. Returns the fractional least common multiple
  248. Fraction ceil([places=0-16])
  249. ---
  250. Returns the ceiling of a rational number with Math.ceil
  251. Fraction floor([places=0-16])
  252. ---
  253. Returns the floor of a rational number with Math.floor
  254. Fraction round([places=0-16])
  255. ---
  256. Returns the rational number rounded with Math.round
  257. Fraction roundTo(multiple)
  258. ---
  259. Rounds a fraction to the closest multiple of another fraction.
  260. Fraction inverse()
  261. ---
  262. Returns the multiplicative inverse of the actual number (n / d becomes d / n) in order to get the reciprocal
  263. Fraction simplify([eps=0.001])
  264. ---
  265. Simplifies the rational number under a certain error threshold. Ex. `0.333` will be `1/3` with `eps=0.001`
  266. boolean equals(n)
  267. ---
  268. Check if two numbers are equal
  269. int compare(n)
  270. ---
  271. Compare two numbers.
  272. ```
  273. result < 0: n is greater than actual number
  274. result > 0: n is smaller than actual number
  275. result = 0: n is equal to the actual number
  276. ```
  277. boolean divisible(n)
  278. ---
  279. Check if two numbers are divisible (n divides this)
  280. double valueOf()
  281. ---
  282. Returns a decimal representation of the fraction
  283. String toString([decimalPlaces=15])
  284. ---
  285. Generates an exact string representation of the actual object. For repeated decimal places all digits are collected within brackets, like `1/3 = "0.(3)"`. For all other numbers, up to `decimalPlaces` significant digits are collected - which includes trailing zeros if the number is getting truncated. However, `1/2 = "0.5"` without trailing zeros of course.
  286. **Note:** As `valueOf()` and `toString()` are provided, `toString()` is only called implicitly in a real string context. Using the plus-operator like `"123" + new Fraction` will call valueOf(), because JavaScript tries to combine two primitives first and concatenates them later, as string will be the more dominant type. `alert(new Fraction)` or `String(new Fraction)` on the other hand will do what you expect. If you really want to have control, you should call `toString()` or `valueOf()` explicitly!
  287. String toLatex(excludeWhole=false)
  288. ---
  289. Generates an exact LaTeX representation of the actual object. You can see a [live demo](http://www.xarg.org/2014/03/precise-calculations-in-javascript/) on my blog.
  290. The optional boolean parameter indicates if you want to exclude the whole part. "1 1/3" instead of "4/3"
  291. String toFraction(excludeWhole=false)
  292. ---
  293. Gets a string representation of the fraction
  294. The optional boolean parameter indicates if you want to exclude the whole part. "1 1/3" instead of "4/3"
  295. Array toContinued()
  296. ---
  297. Gets an array of the fraction represented as a continued fraction. The first element always contains the whole part.
  298. ```javascript
  299. var f = new Fraction('88/33');
  300. var c = f.toContinued(); // [2, 1, 2]
  301. ```
  302. Fraction clone()
  303. ---
  304. Creates a copy of the actual Fraction object
  305. Exceptions
  306. ===
  307. If a really hard error occurs (parsing error, division by zero), *fraction.js* throws exceptions! Please make sure you handle them correctly.
  308. Installation
  309. ===
  310. Installing fraction.js is as easy as cloning this repo or use the following command:
  311. ```
  312. npm install fraction.js
  313. ```
  314. Using Fraction.js with the browser
  315. ===
  316. ```html
  317. <script src="fraction.js"></script>
  318. <script>
  319. console.log(Fraction("123/456"));
  320. </script>
  321. ```
  322. Using Fraction.js with TypeScript
  323. ===
  324. ```js
  325. import Fraction from "fraction.js";
  326. console.log(Fraction("123/456"));
  327. ```
  328. Coding Style
  329. ===
  330. As every library I publish, fraction.js is also built to be as small as possible after compressing it with Google Closure Compiler in advanced mode. Thus the coding style orientates a little on maxing-out the compression rate. Please make sure you keep this style if you plan to extend the library.
  331. Precision
  332. ===
  333. Fraction.js tries to circumvent floating point errors, by having an internal representation of numerator and denominator. As it relies on JavaScript, there is also a limit. The biggest number representable is `Number.MAX_SAFE_INTEGER / 1` and the smallest is `-1 / Number.MAX_SAFE_INTEGER`, with `Number.MAX_SAFE_INTEGER=9007199254740991`. If this is not enough, there is `bigfraction.js` shipped experimentally, which relies on `BigInt` and should become the new Fraction.js eventually.
  334. Testing
  335. ===
  336. If you plan to enhance the library, make sure you add test cases and all the previous tests are passing. You can test the library with
  337. ```
  338. npm test
  339. ```
  340. Copyright and licensing
  341. ===
  342. Copyright (c) 2023, [Robert Eisele](https://raw.org/)
  343. Licensed under the MIT license.