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

interval64

v0.1.0

Published

Rigorous binary64 interval arithmetic with proof-directed, correctly rounded bounds.

Readme

interval64

Rigorous binary64 interval arithmetic with proof-directed, correctly rounded bounds.

import { Interval } from 'interval64'
const step = 0.1
let naive = 0
let rigorous = Interval.of(0)
for (let i = 0; i < 10; i++) {
    naive += step
    rigorous = rigorous.add(Interval.of(step))
}
// naive = 0.9999999999999999; rigorous = [0.9999999999999998, 1.0000000000000007]

The ordinary result silently picks one rounded path. The interval records every real result consistent with the binary64 inputs and operations. Each primitive endpoint is the correctly directed rounding of its exact endpoint expression; normal interval dependency can still widen a longer computation.

Zero runtime dependencies. ESM and TypeScript declarations are included.

Why this exists

JavaScript has no directed rounding mode. The incumbent interval-arithmetic works around that by moving every result one representable number outward. That encloses the answer, but loses a ULP even when the operation was exact.

interval64 asks an error-free transform whether rounding crossed the true value and nudges only when the proof says it did. On 50,000 seeded interval pairs drawn by raw binary64 bit pattern across the full exponent range:

| operation | strictly tighter than [email protected] | rate | invalid incumbent overflow results | |---|---:|---:|---:| | add | 29,922 / 50,000 | 59.84% | 0 | | sub | 29,813 / 50,000 | 59.63% | 0 | | mul | 31,988 / 49,853 | 64.16% | 147 | | div | 16,983 / 49,862 | 34.06% | 138 | | sqrt | 21,941 / 37,563 | 58.41% | 0 |

“Invalid incumbent overflow” means its result collapsed a non-singleton real range to [-Infinity, -Infinity] or [Infinity, Infinity]; those cases are reported, but excluded from the subset denominator because the incumbent did not return an enclosure. For every remaining defined result, interval64's answer was a subset of the incumbent's.

A small worked receipt makes the cost of unconditional nudging concrete:

let x = Interval.of(0)
for (let i = 0; i < 1_000; i++) x = x.add(Interval.of(1))
x // [1000, 1000]

// [email protected]: [999.9999999999252, 1000.0000000000749]

Every addition there is exact. interval64 proves that and keeps all the digits; the incumbent discards roughly eleven decimal digits of interval width.

What “correct” means

An Interval(lo, hi) denotes the closed set of real values between its bounds, with infinite endpoints used for unbounded sets. Every operation is the natural set extension of the corresponding real operation:

  • Containment: if x ∈ X and y ∈ Y, then every defined x ∘ y is in X.op(Y).
  • Correctly rounded primitive bounds: for addition, subtraction, multiplication, division, and square root, each endpoint is the closest binary64 value on the required side of the exact endpoint — across the full finite domain, including the overflow-adjacent windows near ±MAX_VALUE (an adversarial-review find; see DESIGN.md).
  • No false uncertainty: exact operations such as 1 + 2 stay [3, 3].
  • Explicit absence: EMPTY is an empty set, not a NaN-bearing interval. NaN inputs throw.

The proof, overflow cases, and independent oracle architecture are in DESIGN.md.

API

Intervals are immutable. Operations return new intervals (or the shared empty value where specified).

const x = Interval.of(2)          // [2, 2]
const y = Interval.of(-1, 4)      // [-1, 4]

Interval.EMPTY                    // [+Infinity, -Infinity]
Interval.ENTIRE                   // [-Infinity, +Infinity]

x.add(y)
x.sub(y)
x.mul(y)
x.div(y)                          // hull if y contains zero
x.divSplit(y)                     // preserves the 0–2 connected components
x.neg()
x.abs()
x.sqrt()                          // intersects the input with [0, +Infinity]
x.powInt(7)                       // repeated squaring, parity-aware
x.min(y)
x.max(y)
x.intersect(y)
x.hull(y)

x.contains(2)                     // number or Interval
x.strictlyContains(2)
x.width()
x.rad()                           // outward upper bound on the radius
x.mid()                           // approximate convenience, not a bound
x.isEmpty()
x.isEntire()
x.eq(y)

x / [0, 0] is EMPTY: there is no real quotient. A divisor that merely contains zero excludes the zero point and retains the quotients for every nonzero divisor value. divSplit returns the two disconnected rays when they remain disconnected; div returns their hull. Thus [0, 0] / [-1, 1] is [0, 0], while [1, 2] / [-1, 1] splits into two rays and has ENTIRE as its hull. For an unbounded divisor, -0 and +0 bounds retain which side approaches the excluded zero limit, allowing divSplit to keep the two components apart.

powInt(0) is [1, 1] for every nonempty interval; empty propagation takes priority, so EMPTY.powInt(0) is EMPTY. Negative exponents use set-based division. A non-safe-integer exponent throws.

width() and rad() return outward-rounded upper bounds and 0 for EMPTY; rad() halves before subtracting so wide finite intervals do not overflow prematurely. mid() is deliberately only a non-rigorous sampling convenience: it uses an overflow-safe floating midpoint, returns 0 for ENTIRE, the corresponding infinity for one-sided unbounded intervals, and NaN for EMPTY.

The low-level exports are:

nextUp(x)
nextDown(x)
twoSum(a, b)                       // [rounded sum, exact residual]
twoProduct(a, b)                   // [product, residual/proxy, residualExact, signReliable]

The last two flags expose the one remaining inexact case: a near-underflow residual may be too small to represent but still carries a proven sign. Both overflow windows resolve to exact residuals via power-of-two rescaling, so only non-finite inputs or products clear both flags.

Performance receipts

Measured on Node 24.13.1, Apple M5 Max, 2026-07-11. The machine was under heavy background load (load averages 24.12 / 24.21 / 26.84), so treat these as reproducible order-of-magnitude receipts, not clean-room records. cyclebench interleaves candidates to reduce drift. Each call processes arrays of 1,000 random intervals; the table divides the call time by 1,000.

| operation | interval64 ns/op | interval-arithmetic ns/op | interval64 / incumbent | |---|---:|---:|---:| | add | 101 | 289 | 0.35× | | mul | 207 | 206 | 1.00× | | div | 506 | 192 | 2.64× |

Addition benefits from an inlined bit-twiddling path; multiplication tied in this run. Division is the tradeoff: proving the quotient side requires a product transform and exact residual comparison, and cost 2.64× the incumbent. The package targets tighter proof-backed bounds, not universal speed. Run npm run bench for both the speed and tightness tables.

Limits

Dekker splitting multiplies each operand by 2^27 + 1, which overflows for magnitudes above MAX_VALUE / (2^27 + 1) ≈ 2^997; and even with finite splits, a partial product can overflow when |a·b| sits within ~2^-26 relative of 2^1024. Both windows are resolved exactly by rescaling one operand by 2^-64 (a power of two neither rounds nor loses the residual), so correctly rounded bounds hold across the entire finite domain. The second window previously broke containment and is now regression-pinned. Near underflow, the residual is recovered as a same-sign proxy after 2^54 scaling: bounds there are correctly rounded, but a subnormal true residual has no binary64 representation, which is the one place the machinery leans on a sign rather than an exact value.

As with every ordinary interval package, repeated variables introduce the dependency problem: x.sub(x) generally encloses more than zero. This package tightens floating-point rounding; it does not perform symbolic correlation.

Family

exact-sum is the sibling package for correctly rounded reductions. It shares the same Knuth/Dekker/Shewchuk error-free-transform mathematics. interval64 is implemented standalone and retains zero runtime dependencies.

Verification

The seeded suite performs two million containment checks with numbers generated from raw binary64 bit patterns—not scaled Math.random()—covering subnormals, signed zeros, the full exponent range, and near-overflow values. Independent BigInt dyadic-rational oracles require bit-identical directed bounds for addition, subtraction, and every product in the exact Dekker region. Division and square root are checked by independently comparing exact BigInt products.

Dedicated batteries cover EMPTY/ENTIRE, zero-straddling div and divSplit, negative and zero integer powers, signed zero, infinities, NaN rejection, and explicit products near 2^1000. The incumbent cross-check runs on the same full-exponent generator and prints strict-tightness counts.

CI runs test and build on Node 18, 22, and 24.

Install

npm install interval64

License

MIT © Xyra Sinclair