npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ratmath/core

v0.5.0

Published

Exact integers, rational numbers, fractions, rational intervals and interval sets, and certified approximations

Readme

@ratmath/core

Exact integer, rational, fraction, rational-interval, rational-interval-set, and certified-approximation arithmetic for JavaScript. Values use BigInt, have no runtime dependencies, and work in Node and browser bundles.

RatMath also includes a number-only parser for the exact literal formats used by RiX. It parses numbers, not arithmetic expressions.

Install

npm install @ratmath/core

@ratmath/core is an ES module and requires Node.js 22 or newer.

Exact arithmetic

import { Rational, RationalInterval } from "@ratmath/core";

const half = new Rational(1, 2);
const third = new Rational(1, 3);

half.add(third).toString(); // "5/6"

const uncertainty = new RationalInterval("1.2", "1.3");
uncertainty.multiply(new Rational(2)).toString(); // "12/5:13/5"

JavaScript number constructor arguments must be safe integers. Use bigint or decimal integer strings when a value is outside that range, so it reaches RatMath without first losing precision:

new Rational("9007199254740993", "2");

Parse exact numbers

Use parseNumber when the input may be an integer, rational, or interval:

import {
  parseContinuedFraction,
  parseDecimal,
  parseInterval,
  parseMixedNumber,
  parseNumber,
  parseRational,
} from "@ratmath/core";

parseNumber("42");                 // Integer(42)
parseRational("-3/4");             // Rational(-3, 4)
parseDecimal("0.125");             // Rational(1, 8)
parseDecimal("0.#3");              // Rational(1, 3)
parseMixedNumber("-2..1/4");       // Rational(-9, 4)
parseContinuedFraction("3.~7~15"); // Rational(333, 106)
parseInterval("1/3:2/3");          // RationalInterval(1/3, 2/3)

The accepted base-10 scalar forms are:

| Form | Example | Meaning | | --- | --- | --- | | Integer | -1_000 | -1000 | | Fraction | -3/4 | -3/4 | | Finite decimal | .125 | 1/8 | | Repeating decimal | 0.1#6 | 1/6 | | Mixed fraction | -2..1/4 | -9/4 | | Continued fraction | 3.~7~15 | [3; 7, 15] | | Exact interval | 1/3:2/3 | closed interval [1/3, 2/3] |

# starts the repeating block. A repeating block of zero marks a terminating decimal, so "1.25#0" and "1.25" are both exactly 5/4.

Decimal interval notation

Compact, relative, symmetric, and repeating-endpoint intervals are supported:

parseInterval("1.23[56:67]");    // 1.2356:1.2367
parseInterval("1.23[+5:-6]");    // 1.17:1.28
parseInterval("1.3[+-1]");       // 1.2:1.4
parseInterval("1.2[+-0.1]");     // 1.19:1.21
parseInterval("0.[#3:#6]");      // 1/3:2/3

Unsigned bracket values append digits to the base. Signed offsets instead use the base value's last visible digit as their unit. Thus 1.23[+5:-6] means 1.23 + 5 × 0.01 and 1.23 - 6 × 0.01. Decimal offsets use that same unit: 1.2[+-0.1] applies 0.1 × 0.1, or 0.01, in each direction. For an integer base value, the last-visible-digit unit is 1.

When a bracket contains two values, colon is the required separator.

parseInterval("3/4") creates the point interval 3/4:3/4.

Exact interval sets

RationalInterval is one bounded closed interval. Use RationalIntervalSet when a domain or range is disconnected, open, or unbounded:

import { RationalInterval, RationalIntervalSet } from "@ratmath/core";

const outside = new RationalIntervalSet([
  { low: null, high: -1, lowClosed: false, highClosed: true },
  { low: 1, high: null, lowClosed: true, highClosed: false },
]);

outside.toString(); // "(-inf,-1] U [1,inf)"
outside.containsValue(0); // false
outside.containsValue(2); // true

const middle = RationalIntervalSet.fromInterval(new RationalInterval(-2, 2));
outside.intersection(middle).toString(); // "[-2,-1] U [1,2]"
outside.hull().toString(); // "(-inf,inf)"

In component objects, low: null means -Infinity and high: null means +Infinity; infinite endpoints must be open. Construction sorts and merges components but preserves an omitted touching point, so [0,1) U (1,2] stays disconnected. Open single-point components normalize to the empty set.

The value is immutable and provides exact union, intersection, contains, containsValue, equals, and hull operations. A set converts back with toRationalInterval() only when it has exactly one bounded closed component. It is deliberately not a CoreNumber; proof-safe range arithmetic uses explicitly named functions whose result records account for undefined points:

import {
  rangeAdd,
  rangeDivide,
  rangeIntegerPower,
  rangeReciprocal,
} from "@ratmath/core";

rangeAdd(new RationalIntervalSet({ low: 1, high: 2 }), 3)
  .range.toString(); // "[4,5]"

const reciprocal = rangeReciprocal(
  new RationalIntervalSet({ low: -1, high: 1 }),
);
reciprocal.range.toString();       // "(-inf,-1] U [1,inf)"
reciprocal.domain.coverage;        // "partiallyDefined"
reciprocal.domain.exclusions[0].reason; // "divisionByZero"

rangeDivide(0, RationalIntervalSet.point(0)).range.isEmpty; // true
rangeIntegerPower(RationalIntervalSet.point(0), 0)
  .domain.coverage; // "noDefinedInputs"

The public primitives are rangeNegate, rangeAbsoluteValue, rangeAdd, rangeSubtract, rangeMultiply, rangeReciprocal, rangeDivide, and rangeIntegerPower. They compute the image of every mathematically defined input tuple. Undefined tuples add no value and are recorded with coverage allDefined, partiallyDefined, or noDefinedInputs; inability to compute a defined tuple is never disguised as an exclusion. 0^0 is undefined by default and may be explicitly changed with { zeroPowerZero: "one" }.

checkRangeOperationResult(record) independently recomputes the primitive claim and rejects changed ranges or domain coverage. The Core record contains mathematical facts only; RiX attaches its portable form as rangeEvidence metadata and applies report/throw policy at the language boundary.

Certified finite approximations

An embedded ? separates certified representation data from optional provisional data. The authoritative guarantee is always an exact rational enclosure:

const x = parseNumber("23.456?789");

x.candidate.toString(); // "23456789/1000000"
x.enclosure.toString(); // "2932/125:23457/1000"
x.add(new Rational(1, 2)); // another CertifiedApproximation

23.456? certifies the decimal prefix but does not claim the expansion is complete. 3.~7~15?1~292 does the same for a continued-fraction prefix. Bracket uncertainty can narrow a decimal cylinder, as in 23.456?789[+-12], and is validated against the certified prefix.

CertifiedApproximation is an uncertain scalar, not an interval collection. Arithmetic with exact scalars preserves that distinction. Explicitly mixing one with RationalInterval yields interval arithmetic. Core's possibleRelations(a, b) returns a mask composed from Relation.LESS, Relation.EQUAL, and Relation.GREATER; it never invents a Boolean answer for overlapping enclosures.

Derived values without an honest radix prefix use the parseable spelling candidate?[=low:high]; the exact bracket endpoints preserve the scalar's enclosure across string round trips.

A trailing ... from an ordinary formatter remains display-only truncation. It does not create an approximate value; parseable approximation values use ?. Use boundedDecimalApproximation(value, { fractionalDigits }) or boundedContinuedFractionApproximation(value, { maxTerms }) when reaching a work limit should deliberately return a certified numeric value.

Export formats

const value = parseRational("-9/4");

value.toString();                  // "-9/4"
value.toMixedString();             // "-2..1/4"
value.toRepeatingDecimal();        // "-2.25#0"
value.toContinuedFractionString(); // "-3.~1~3"
value.toContinuedFraction({ long: true }); // [-3n, 1n, 2n, 1n]

const interval = parseInterval("1.23[+0.5:-0.6]");

interval.toString();                 // "153/125:247/200"
interval.toMixedString();            // "1..28/125:1..47/200"
interval.toRepeatingDecimal();       // "1.224#0:1.235#0"
interval.relativeDecimalInterval();  // "1.23[+0.5:-0.6]"
interval.compactedDecimalInterval(); // a compact range when possible

Repeating-decimal and continued-fraction output without an ellipsis is exact and can be parsed again. toRepeatingDecimal() allows periods up to 30 digits by default and throws if a longer period would be required. Pass a larger limit for exact interchange, or choose an explicit over-limit policy:

new Rational(1, 97).toRepeatingDecimal(100);          // exact `#` period
new Rational(1, 97).toRepeatingDecimal(30, "trunc"); // visible `...` suffix
new Rational(1, 97).toRepeatingDecimal(30, "null");  // null

Continued fractions are canonical by default. { long: true } selects the alternative finite expansion ending in 1; convergents({ intermediates: true }) includes every intermediate convergent along each coefficient run. Coefficient and convergent arrays stop at Rational.DEFAULT_CF_LIMIT unless a larger maxTerms or maxCount is supplied. An incomplete toContinuedFractionString() ends in ~..., making the limited prefix visible and deliberately non-parseable as an exact value.

Rational.MAX_PERIOD_DIGITS sets the mutable global default (initially 30) for repeating-decimal output and decimal metadata; an explicit method limit still overrides it. The other mutable defaults are MAX_PERIOD_CHECK (period-length discovery), DEFAULT_DECIMAL_DIGITS (toDecimal()), DEFAULT_BASE_LIMIT (arbitrary-base radix output), DEFAULT_SCIENTIFIC_PRECISION, DEFAULT_PERIOD_MODULO_LIMIT, and DEFAULT_CF_LIMIT. Formatting and traversal defaults have explicit per-call overrides; MAX_PERIOD_CHECK is a mutable global guard on period-discovery work.

In truncated output, # still marks the start of the repeating section and the trailing ... says that only a prefix of its period is shown. Such output is informative but is not parseable as an exact value. # output without an ellipsis contains the complete period and round-trips exactly. toDecimal() is display-oriented and uses DEFAULT_DECIMAL_DIGITS (20 initially), which can also be overridden with toDecimal(maxDigits).

Intervals can work on a fixed denominator grid without enumerating fractions:

const range = parseInterval("1/3:2/3");
range.denominatorInterval(10).toString();              // "2/5:3/5"
range.randomRational(10, "error", () => 0).toString(); // "2/5"

Omitting the grid denominator uses the LCM of the endpoint denominators. "mid", "null", and "error" control what happens when a grid misses the interval. An injected random source must return values in [0, 1) and vary between calls when rejection sampling needs another candidate.

Fractions and bases

Rational always reduces to lowest terms. Fraction preserves the supplied numerator and denominator, which is useful for Farey and Stern–Brocot operations.

BaseSystem supplies arbitrary-base conversion and common presets:

import { BaseSystem, Rational } from "@ratmath/core";

new Rational(1, 3).toRepeatingBase(BaseSystem.BINARY); // "0.#01"
new Rational(255).toBase(BaseSystem.HEXADECIMAL);       // "ff"

Integer conversion also supports signed radices, balanced digits, and bijective digits through the optional BaseSystem constructor options:

const balanced = new BaseSystem("T01", "Balanced ternary", {
  radix: 3,
  digitOffset: -1,
});

balanced.fromDecimal(-5n); // "T11"

These nonstandard systems support integers and exact numerator/denominator formatting. Repeating fractional expansions require an ordinary positional system; check supportsPositionalFractions before requesting one.

The public API also includes Integer, Fraction, FractionInterval, RationalInterval, RationalIntervalSet, BaseSystem, and TypePromotion. TypeScript declarations ship with the package. isInteger, isRational, isRationalInterval, isRationalIntervalSet, isCertifiedApproximation, isFraction, isFractionInterval, isBaseSystem, and isCoreNumber are public type guards. Core values use tagged toJSON() output and can be restored with JSON.parse(text, reviveCoreValue).

Documentation

The complete manual has an overview, a page for every public class, number parsing details, an API index, and runnable examples:

Build the GitHub Pages site manually with:

npm run docs:build

This requires Quarto and writes the rendered site to docs/. Documentation is not rendered or published by CI.

License

MIT