@lvlte/rationalize
v1.0.3
Published
Represent a floating point number as a rational number
Maintainers
Readme
rationalize
Inspired by Julia's rationalize
Represent a floating point number
xas a rational number[p, q]where|x - p/q| ≤ tol(the result will differ from x by no more than the given tolerance).
function rationalize(x: number, tol: number = eps(x)): [number, number]Install
npm install @lvlte/rationalizeUsage
// ESM
import { rationalize } from '@lvlte/rationalize';// CJS
const { rationalize } = require('@lvlte/rationalize');const [p1, q1] = rationalize(0.1); // [1, 10]
const [p2, q2] = rationalize(Math.PI); // [165707065, 52746197]
const [p3, q3] = rationalize(Math.PI, 0.01); // [22, 7]
// The sign of x always goes to the numerator
const [p4, q4] = rationalize(-13.78); // [-689, 50]
const [p5, q5] = rationalize(-0); // [-0, 1]Safe Integers
The output ratio's components are guaranteed to be safe integers (float64
integers in the range [-2^53 + 1, +2^53 - 1]). In case one of
the component goes out of this range, a RangeError is thrown, which does not
happen as long as the tolerance is set to a value greater than or equal to
eps(x) (default). The exception to this rule concerns absolute values of x
less than 1/(2^53 - 1) or greater than 2^53 - 1. To rationalize these
tiny/huge numbers with the best precision, the numerator and denominator needs
to be represented with bigint (coming soon).
Tolerance
The default tolerance is the ULP of x, which
allows the best accuracy knowing that most of the floating point numbers we are
using, such as 0.1, are already approximations. In most cases, you don't need
to change that except if you want to reduce the precision of the output. In case
you want to increase it, note that using a zero tolerance will yield the exact
(binary) representation of x as a decimal ratio. One legitimate case of using
tolerance less than eps(x) exists though : ensuring the result of the floating
point division p/q equals x, in this case you need to set the tolerance to
eps(x)/2 :
import { rationalize, eps } from '@lvlte/rationalize';
let x = 0.8; // 0.8000000000000000(444089209850062616169452667236328125)
const [p1, q1] = rationalize(x); // [4, 5]
const [p2, q2] = rationalize(x, 0); // [3602879701896397, 4503599627370496]
x = 0.1 + 0.2; // 0.30000000000000004(44089209850062616169452667236328125)
const [p3, q3] = rationalize(x); // [3, 10]
const [p4, q4] = rationalize(x, eps(x)/2); // [415716888680356, 1385722962267853]
console.log(p3/q3 === x) // false
console.log(p4/q4 === x) // true