508 lines
16 KiB
Markdown
508 lines
16 KiB
Markdown
# Fraction.js - ℚ in JavaScript
|
||
|
||
[](https://npmjs.org/package/fraction.js "View this project on npm")
|
||
[](http://opensource.org/licenses/MIT)
|
||
|
||
Do you find the limitations of floating-point arithmetic frustrating, especially when rational and irrational numbers like π or √2 are stored within the same finite precision? This can lead to avoidable inaccuracies such as:
|
||
|
||
```javascript
|
||
1 / 98 * 98 // Results in 0.9999999999999999
|
||
```
|
||
|
||
For applications requiring higher precision or where working with fractions is preferable, consider incorporating *Fraction.js* into your project. Integration is straightforward:
|
||
|
||
```javascript
|
||
import Fraction from 'fraction.js';
|
||
// Alternatively
|
||
var Fraction = require('fraction.js');
|
||
```
|
||
|
||
The library effectively addresses precision issues, as demonstrated below:
|
||
|
||
```javascript
|
||
Fraction(1).div(98).mul(98) // Returns 1
|
||
```
|
||
|
||
*Fraction.js* uses a `BigInt` representation for both the numerator and denominator, ensuring minimal performance overhead while maximizing accuracy. Its design is optimized for precision, making it an ideal choice as a foundational library for other math tools, such as [Polynomial.js](https://github.com/infusion/Polynomial.js) and [Math.js](https://github.com/josdejong/mathjs).
|
||
|
||
## Convert Decimal to Fraction
|
||
|
||
One of the core features of *Fraction.js* is its ability to seamlessly convert decimal numbers into fractions.
|
||
|
||
```javascript
|
||
let x = new Fraction(1.88);
|
||
let res = x.toFraction(true); // Returns "1 22/25" as a string
|
||
```
|
||
|
||
This is particularly useful when you need precise fraction representations instead of dealing with the limitations of floating-point arithmetic. What if you allow some error tolerance?
|
||
|
||
```javascript
|
||
let x = new Fraction(0.33333);
|
||
let res = x.simplify(0.001) // Error < 0.001
|
||
.toFraction(); // Returns "1/3" as a string
|
||
```
|
||
|
||
## Precision
|
||
|
||
As native `BigInt` support in JavaScript becomes more common, libraries like *Fraction.js* use it to handle calculations with higher precision. This improves the speed and accuracy of math operations with large numbers, providing a better solution for tasks that need more precision than floating-point numbers can offer.
|
||
|
||
## Examples / Motivation
|
||
|
||
A simple example of using *Fraction.js* might look like this:
|
||
|
||
```javascript
|
||
var f = new Fraction("9.4'31'"); // 9.4313131313131...
|
||
f.mul([-4, 3]).mod("4.'8'"); // 4.88888888888888...
|
||
```
|
||
|
||
The result can then be displayed as:
|
||
|
||
```javascript
|
||
console.log(f.toFraction()); // -4154 / 1485
|
||
```
|
||
|
||
Additionally, you can access the internal attributes of the fraction, such as the sign (s), numerator (n), and denominator (d). Keep in mind that these values are stored as `BigInt`:
|
||
|
||
```javascript
|
||
Number(f.s) * Number(f.n) / Number(f.d) = -1 * 4154 / 1485 = -2.797306...
|
||
```
|
||
|
||
If you attempted to calculate this manually using floating-point arithmetic, you'd get something like:
|
||
|
||
```javascript
|
||
(9.4313131 * (-4 / 3)) % 4.888888 = -2.797308133...
|
||
```
|
||
|
||
While the result is reasonably close, it’s not as accurate as the fraction-based approach that *Fraction.js* provides, especially when dealing with repeating decimals or complex operations. This highlights the value of precision that the library brings.
|
||
|
||
### Laplace Probability
|
||
|
||
Here's a straightforward example of using *Fraction.js* to calculate probabilities. Let's determine the probability of rolling a specific outcome on a fair die:
|
||
|
||
- **P({3})**: The probability of rolling a 3.
|
||
- **P({1, 4})**: The probability of rolling either 1 or 4.
|
||
- **P({2, 4, 6})**: The probability of rolling 2, 4, or 6.
|
||
|
||
#### P({3}):
|
||
|
||
```javascript
|
||
var p = new Fraction([3].length, 6).toString(); // "0.1(6)"
|
||
```
|
||
|
||
#### P({1, 4}):
|
||
|
||
```javascript
|
||
var p = new Fraction([1, 4].length, 6).toString(); // "0.(3)"
|
||
```
|
||
|
||
#### P({2, 4, 6}):
|
||
|
||
```javascript
|
||
var p = new Fraction([2, 4, 6].length, 6).toString(); // "0.5"
|
||
```
|
||
|
||
### Convert degrees/minutes/seconds to precise rational representation:
|
||
|
||
57+45/60+17/3600
|
||
|
||
```javascript
|
||
var deg = 57; // 57°
|
||
var min = 45; // 45 Minutes
|
||
var sec = 17; // 17 Seconds
|
||
|
||
new Fraction(deg).add(min, 60).add(sec, 3600).toString() // -> 57.7547(2)
|
||
```
|
||
|
||
|
||
### Rational approximation of irrational numbers
|
||
|
||
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*.
|
||
|
||
Then the following algorithm will generate the rational number besides the binary representation.
|
||
|
||
```javascript
|
||
var x = "/", s = "";
|
||
|
||
var a = new Fraction(0),
|
||
b = new Fraction(1);
|
||
for (var n = 0; n <= 10; n++) {
|
||
|
||
var c = a.add(b).div(2);
|
||
|
||
console.log(n + "\t" + a + "\t" + b + "\t" + c + "\t" + x);
|
||
|
||
if (c.add(2).pow(2).valueOf() < 5) {
|
||
a = c;
|
||
x = "1";
|
||
} else {
|
||
b = c;
|
||
x = "0";
|
||
}
|
||
s+= x;
|
||
}
|
||
console.log(s)
|
||
```
|
||
|
||
The result is
|
||
|
||
```
|
||
n a[n] b[n] c[n] x[n]
|
||
0 0/1 1/1 1/2 /
|
||
1 0/1 1/2 1/4 0
|
||
2 0/1 1/4 1/8 0
|
||
3 1/8 1/4 3/16 1
|
||
4 3/16 1/4 7/32 1
|
||
5 7/32 1/4 15/64 1
|
||
6 15/64 1/4 31/128 1
|
||
7 15/64 31/128 61/256 0
|
||
8 15/64 61/256 121/512 0
|
||
9 15/64 121/512 241/1024 0
|
||
10 241/1024 121/512 483/2048 1
|
||
```
|
||
|
||
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))
|
||
|
||
I published another example on how to approximate PI with fraction.js on my [blog](https://raw.org/article/rational-numbers-in-javascript/) (Still not the best idea to approximate irrational numbers, but it illustrates the capabilities of Fraction.js perfectly).
|
||
|
||
|
||
### Get the exact fractional part of a number
|
||
|
||
```javascript
|
||
var f = new Fraction("-6.(3416)");
|
||
console.log(f.mod(1).abs().toFraction()); // = 3416/9999
|
||
```
|
||
|
||
### Mathematical correct modulo
|
||
|
||
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:
|
||
|
||
```javascript
|
||
var a = -1;
|
||
var b = 10.99;
|
||
|
||
console.log(new Fraction(a)
|
||
.mod(b)); // Not correct, usual Modulo
|
||
|
||
console.log(new Fraction(a)
|
||
.mod(b).add(b).mod(b)); // Correct! Mathematical Modulo
|
||
```
|
||
|
||
fmod() impreciseness circumvented
|
||
---
|
||
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.
|
||
|
||
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.
|
||
|
||
|
||
## Parser
|
||
|
||
Any function (see below) as well as the constructor of the *Fraction* class parses its input and reduce it to the smallest term.
|
||
|
||
You can pass either Arrays, Objects, Integers, Doubles or Strings.
|
||
|
||
### Arrays / Objects
|
||
|
||
```javascript
|
||
new Fraction(numerator, denominator);
|
||
new Fraction([numerator, denominator]);
|
||
new Fraction({n: numerator, d: denominator});
|
||
```
|
||
|
||
### Integers
|
||
|
||
```javascript
|
||
new Fraction(123);
|
||
```
|
||
|
||
### Doubles
|
||
|
||
```javascript
|
||
new Fraction(55.4);
|
||
```
|
||
|
||
**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.
|
||
|
||
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.
|
||
|
||
|
||
### Strings
|
||
|
||
```javascript
|
||
new Fraction("123.45");
|
||
new Fraction("123/45"); // A rational number represented as two decimals, separated by a slash
|
||
new Fraction("123:45"); // A rational number represented as two decimals, separated by a colon
|
||
new Fraction("4 123/45"); // A rational number represented as a whole number and a fraction
|
||
new Fraction("123.'456'"); // Note the quotes, see below!
|
||
new Fraction("123.(456)"); // Note the brackets, see below!
|
||
new Fraction("123.45'6'"); // Note the quotes, see below!
|
||
new Fraction("123.45(6)"); // Note the brackets, see below!
|
||
```
|
||
|
||
### Two arguments
|
||
|
||
```javascript
|
||
new Fraction(3, 2); // 3/2 = 1.5
|
||
```
|
||
|
||
### Repeating decimal places
|
||
|
||
*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)
|
||
|
||
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:
|
||
|
||
```javascript
|
||
var f = new Fraction("123.32");
|
||
console.log("Bam: " + f.div("33.6(567)"));
|
||
```
|
||
|
||
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...
|
||
|
||
```javascript
|
||
function formatDecimal(str) {
|
||
|
||
var comma, pre, offset, pad, times, repeat;
|
||
|
||
if (-1 === (comma = str.indexOf(".")))
|
||
return str;
|
||
|
||
pre = str.substr(0, comma + 1);
|
||
str = str.substr(comma + 1);
|
||
|
||
for (var i = 0; i < str.length; i++) {
|
||
|
||
offset = str.substr(0, i);
|
||
|
||
for (var j = 0; j < 5; j++) {
|
||
|
||
pad = str.substr(i, j + 1);
|
||
|
||
times = Math.ceil((str.length - offset.length) / pad.length);
|
||
|
||
repeat = new Array(times + 1).join(pad); // Silly String.repeat hack
|
||
|
||
if (0 === (offset + repeat).indexOf(str)) {
|
||
return pre + offset + "(" + pad + ")";
|
||
}
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
var f, x = formatDecimal("13.0123123123"); // = 13.0(123)
|
||
if (x !== null) {
|
||
f = new Fraction(x);
|
||
}
|
||
```
|
||
|
||
## Attributes
|
||
|
||
|
||
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.
|
||
|
||
```javascript
|
||
var f = new Fraction('-1/2');
|
||
console.log(f.n); // Numerator: 1
|
||
console.log(f.d); // Denominator: 2
|
||
console.log(f.s); // Sign: -1
|
||
```
|
||
|
||
|
||
## Functions
|
||
|
||
### Fraction abs()
|
||
|
||
Returns the actual number without any sign information
|
||
|
||
### Fraction neg()
|
||
|
||
Returns the actual number with flipped sign in order to get the additive inverse
|
||
|
||
### Fraction add(n)
|
||
|
||
Returns the sum of the actual number and the parameter n
|
||
|
||
### Fraction sub(n)
|
||
|
||
Returns the difference of the actual number and the parameter n
|
||
|
||
### Fraction mul(n)
|
||
|
||
Returns the product of the actual number and the parameter n
|
||
|
||
### Fraction div(n)
|
||
|
||
Returns the quotient of the actual number and the parameter n
|
||
|
||
### Fraction pow(exp)
|
||
|
||
Returns the power of the actual number, raised to an possible rational exponent. If the result becomes non-rational the function returns `null`.
|
||
|
||
### Fraction log(base)
|
||
|
||
Returns the logarithm of the actual number to a given rational base. If the result becomes non-rational the function returns `null`.
|
||
|
||
### Fraction mod(n)
|
||
|
||
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).
|
||
|
||
### Fraction mod()
|
||
|
||
Returns the modulus (rest of the division) of the actual object (numerator mod denominator)
|
||
|
||
### Fraction gcd(n)
|
||
|
||
Returns the fractional greatest common divisor
|
||
|
||
### Fraction lcm(n)
|
||
|
||
Returns the fractional least common multiple
|
||
|
||
### Fraction ceil([places=0-16])
|
||
|
||
Returns the ceiling of a rational number with Math.ceil
|
||
|
||
### Fraction floor([places=0-16])
|
||
|
||
Returns the floor of a rational number with Math.floor
|
||
|
||
### Fraction round([places=0-16])
|
||
|
||
Returns the rational number rounded with Math.round
|
||
|
||
### Fraction roundTo(multiple)
|
||
|
||
Rounds a fraction to the closest multiple of another fraction.
|
||
|
||
### Fraction inverse()
|
||
|
||
Returns the multiplicative inverse of the actual number (n / d becomes d / n) in order to get the reciprocal
|
||
|
||
### Fraction simplify([eps=0.001])
|
||
|
||
Simplifies the rational number under a certain error threshold. Ex. `0.333` will be `1/3` with `eps=0.001`
|
||
|
||
### boolean equals(n)
|
||
|
||
Check if two rational numbers are equal
|
||
|
||
### boolean lt(n)
|
||
|
||
Check if this rational number is less than another
|
||
|
||
### boolean lte(n)
|
||
|
||
Check if this rational number is less than or equal another
|
||
|
||
### boolean gt(n)
|
||
|
||
Check if this rational number is greater than another
|
||
|
||
### boolean gte(n)
|
||
|
||
Check if this rational number is greater than or equal another
|
||
|
||
### int compare(n)
|
||
|
||
Compare two numbers.
|
||
```
|
||
result < 0: n is greater than actual number
|
||
result > 0: n is smaller than actual number
|
||
result = 0: n is equal to the actual number
|
||
```
|
||
|
||
### boolean divisible(n)
|
||
|
||
Check if two numbers are divisible (n divides this)
|
||
|
||
### double valueOf()
|
||
|
||
Returns a decimal representation of the fraction
|
||
|
||
### String toString([decimalPlaces=15])
|
||
|
||
Generates an exact string representation of the given object. For repeating decimal places, digits within repeating cycles are enclosed in parentheses, e.g., `1/3 = "0.(3)"`. For other numbers, the string will include up to the specified `decimalPlaces` significant digits, including any trailing zeros if truncation occurs. For example, `1/2` will be represented as `"0.5"`, without additional trailing zeros.
|
||
|
||
**Note:** Since both `valueOf()` and `toString()` are provided, `toString()` will only be invoked implicitly when the object is used in a string context. For instance, when using the plus operator like `"123" + new Fraction`, `valueOf()` will be called first, as JavaScript attempts to combine primitives before concatenating them, with the string type taking precedence. However, `alert(new Fraction)` or `String(new Fraction)` will behave as expected. To ensure specific behavior, explicitly call either `toString()` or `valueOf()`.
|
||
|
||
### String toLatex(showMixed=false)
|
||
|
||
Generates an exact LaTeX representation of the actual object. You can see a [live demo](https://raw.org/article/rational-numbers-in-javascript/) on my blog.
|
||
|
||
The optional boolean parameter indicates if you want to show the a mixed fraction. "1 1/3" instead of "4/3"
|
||
|
||
### String toFraction(showMixed=false)
|
||
|
||
Gets a string representation of the fraction
|
||
|
||
The optional boolean parameter indicates if you want to showa mixed fraction. "1 1/3" instead of "4/3"
|
||
|
||
### Array toContinued()
|
||
|
||
Gets an array of the fraction represented as a continued fraction. The first element always contains the whole part.
|
||
|
||
```javascript
|
||
var f = new Fraction('88/33');
|
||
var c = f.toContinued(); // [2, 1, 2]
|
||
```
|
||
|
||
### Fraction clone()
|
||
|
||
Creates a copy of the actual Fraction object
|
||
|
||
|
||
## Exceptions
|
||
|
||
If a really hard error occurs (parsing error, division by zero), *Fraction.js* throws exceptions! Please make sure you handle them correctly.
|
||
|
||
|
||
## Installation
|
||
|
||
Installing fraction.js is as easy as cloning this repo or use the following command:
|
||
|
||
```bash
|
||
npm install fraction.js
|
||
```
|
||
|
||
## Using Fraction.js with the browser
|
||
|
||
```html
|
||
<script src="fraction.min.js"></script>
|
||
<script>
|
||
console.log(Fraction("123/456"));
|
||
</script>
|
||
```
|
||
|
||
## Using Fraction.js with TypeScript
|
||
|
||
```js
|
||
import Fraction from "fraction.js";
|
||
console.log(Fraction("123/456"));
|
||
```
|
||
|
||
|
||
## Coding Style
|
||
|
||
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.
|
||
|
||
## Building the library
|
||
|
||
After cloning the Git repository run:
|
||
|
||
```bash
|
||
npm install
|
||
npm run build
|
||
```
|
||
|
||
## Run a test
|
||
|
||
Testing the source against the shipped test suite is as easy as
|
||
|
||
```bash
|
||
npm run test
|
||
```
|
||
|
||
## Copyright and Licensing
|
||
|
||
Copyright (c) 2024, [Robert Eisele](https://raw.org/)
|
||
Licensed under the MIT license.
|