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

@slnknrr/num-im

v1.0.0

Published

Numbers as text, in any base, without lying. Exact string-first arithmetic past 2^53 — bases 2–36 or a custom alphabet, canonical forms, modular arithmetic, number theory, bit metrics, and regex validators.

Readme

num-im

Numbers as text, in any base, without lying.

num-im treats a number as a string of digits, not an IEEE-754 float. A value's value is base-independent — the base is only notation — so internally every number is carried as an exact reduced rational (a BigInt numerator over a BigInt denominator). That one decision buys exactness past 2⁵³, arbitrary bases (2–36 or your own alphabet), and exactly one canonical string per value. + − × ÷, base conversion, comparison, and modular arithmetic are all exact and base-agnostic; strings are parsed on the way in and rendered on the way out.

Where Number quietly rounds, drops digits past 2⁵³, and only speaks base 10, num-im stays exact and speaks every base — and when a division genuinely doesn't terminate, it tells you by stopping at a precision you set instead of pretending.

import { numim as n } from '@slnknrr/num-im';

n.add('9007199254740992', '1');        // '9007199254740993' — 2⁵³+1, exact where + starts lying
n.mul('ff', 'ff', { base: 16 });       // 'fe01' — arithmetic native to any base
n.rebase('255', { from: 10, to: 2 });  // '11111111' — same value, different notation
n.re(16).test('00f');                  // false — a leading zero is a costume, not a number
n.powmod('2', '100', '1000000007');    // '976371285' — the RSA/DH primitive, exact
n.fib('100');                          // '354224848179261915075' — overflows a double by 15 digits

What it is

  • String-first, exact past 2⁵³. IEEE-754 forgets; digit-strings never do. Public input and output are strings (and BigInt/number where they fit).
  • Any base 2–36, or a custom alphabet. A single "digit" may be a set of glyphs, so base > 10 is case-insensitive by construction (a and A are one digit) — not by a later .toLowerCase() tax.
  • Canonical, enforced — not suggested. No leading zeros, one radix point, one representation per value. "007" is not a number, it's a costume.
  • base is first-class. It governs how strings are read and written, and nothing else. Add in hex, compare across bases, convert losslessly — the value underneath never changes.
  • Exact rational core. Values are reduced BigInt fractions { s, n, d }, so add/sub/mul/div are exact; non-terminating division truncates at a precision you choose (default 20), never silently.
  • Pure ESM, dependency-free, synchronous. One file, immutable, tree-shakeable, typed (hand-written .d.ts). Node ≥ 20.

What it is NOT

  • Not a faster Number or BigInt. A hot loop of native + on doubles will out-run rationals every time, and should. num-im wins where the answer must be exact, in a non-decimal base, or in a canonical, validatable form — the places Number lies. See Exactness, not speed.
  • Not constant-time crypto. powmod/modinv are correct, not hardened: powmod is square-and-multiply and leaks timing on the exponent. Fine for math and public values; do not use it on secret exponents.
  • Not a decimal/units/money type. There is no currency locale engine — fmt does grouping, fixed fraction, and sign style; anything richer is your layer.
  • Not mutating, not async. No method touches its input, and there is not a promise anywhere. You get back a string, an array, a number, or a lazy iterator.

Install

npm install @slnknrr/num-im
import { numim } from '@slnknrr/num-im';                          // the API — a class of static methods
import { numim, intre, uintre, floatre, ufloatre } from '@slnknrr/num-im';

There is no default exportnumim is a named export (a class you call statically, never new). Requirements: Node ≥ 20, ESM only ("type": "module" or import).


Core conventions

These are load-bearing. Learn them once; they apply everywhere.

The validator IS the grammar. re(base, options) builds an anchored regex that defines what a number is in that base; every predicate (isnum, ishex, isint, …) is nothing but re(...).test(str). One regex, one truth — "a number" stops being a vibe.

n.re(2, { sign: false, float: false }).test('101'); // true  — unsigned binary integer
n.re(16).test('-1a.f0');                             // true  — signed hex with a fraction

Comparisons are tri-state: -1 / 0 / 1, not booleans. cmp is the primitive; ge/le are opinions about its sign. eq goes further — it distinguishes value from written system:

n.eq('42', '42');    //  1  — equal value, same system
n.eq('0', '0.0');    // -1  — equal value, but one was written with a fraction
n.eq('42', '43');    //  0  — unequal

Values are strings; bases are options. Almost every method takes { base } (default 10). String operands are read in that base and results are rendered in it — including shift counts, digit indices, and field widths (shl, not, clz, clo, fits, popcnt's digit, …), which are themselves numbers in that base. A base may be a radix, a custom alphabet, or an array with glyph-sets ([['a','A']]) — the same spec re accepts — and counts honor all three. A numeric argument like 8 always means decimal 8, never 0x8; only strings are read in-base.

Exact until it can't be, then honest. A division that doesn't terminate stops at options.precision (default 20) and truncates — it does not silently round. Ask for { precision: 0 } and you get the truncated integer, not a dangling point.

Fail-fast. A bad digit, a division by zero, a non-integer where an integer is required, a value that overflows a field — each throws at the call site (SyntaxError / RangeError / TypeError), never returns a quiet wrong answer.


Bases & alphabets

A base argument is resolved once per call into an alphabet. Three forms work out of the box:

| Form | Meaning | Example | |---|---|---| | number 2–36 | standard radix; digits 0-9a-z, case-insensitive above 10 | n.re(16) | | string | custom alphabet, one glyph per digit, in value order | n.add('α','β',{base:'αβγ'}) | | Array | custom alphabet; an entry may be a glyph-set (aliases for one digit) | n.re(['0','1',['a','A']]) |

n.re(['0', '1', ['a', 'A']]).test('1A0'); // true — base-3 whose third digit is 'a' OR 'A'

The glyphs - (sign), . (radix point) and \ (escape) are reserved and rejected from custom alphabets.


Exactness, not speed

num-im is a correctness tool, not a throughput tool, and it says so out loud. It carries BigInt rationals; for eager, in-range, base-10 math, Number and BigInt are a compiled C++ path and they win — deservedly.

Reach for num-im when the native idiom is forced to lie or can't answer at all:

  • Past 2⁵³9007199254740992 + 1 is ...993 here and ...992 in a double.
  • A non-decimal base — arithmetic, comparison, and validation native to base 2/16/36 or a custom alphabet, not "convert → do in base 10 → convert back".
  • A canonical, comparable, validatable form — one string per value, a generated regex that is the grammar, and tri-state equality that separates value from representation.

If none of those bite, use Number. That is not a weakness of the library; it is the library knowing its job.


API

105 static methods across 15 groups. Every method takes a trailing options object; options.base defaults to 10. Also exported: the base-10 fast-path regexes intre / uintre / floatre / ufloatre — hand-written equivalents of re(10, …), useful as a differential-test oracle for the generator.

1 · Regex generation — the validator is the grammar

| Method | Returns | |---|---| | re(base=10, {sign, float}?) | anchored regex for a canonical number; sign/float: true require, false forbid, default optional | | reint(base=10) | integers only | | refloat(base=10) | must carry a fractional part |

2 · Equality & order — tri-state, cross-base, no float math

| Method | Returns | |---|---| | cmp(a, b) | three-way compare: -1 / 0 / 1 | | eq(a, b, {sign}?) | 1 equal value & written system · -1 equal value only · 0 unequal | | ne(a, b) | true if provably different in value | | ge(a, b) / le(a, b) | cmp ≥ 0 / cmp ≤ 0 |

3 · Bit-weight order — compare by how it packs, not how big it is

| Method | Returns | |---|---| | cmpbw(a, b) | order by a signed bit-count of the integer part (sign = is-power-of-two) | | eqbw nebw gebw lebw | the boolean opinions on cmpbw |

4 · Lazy arithmetic — generators, one digit per next()

| Method | Yields | |---|---| | *ladd *lsub *lmul | result digits, least-significant first | | *ldiv | quotient digits, most-significant first |

5 · Immediate arithmetic — exact past 2⁵³ and across bases

| Method | Returns | |---|---| | add sub mul div | + − × ÷; non-terminating div stops at precision (default 20) | | neg inc dec | sign flip · ±1 (never -0) | | rem(a, b) | truncated remainder, sign of the dividend (C %) | | mod(a, b) | Euclidean modulo, result in [0, |b|) | | divrem(a, b) | { q, r } in one pass | | pow(a, k) | non-negative integer exponent, exact | | isqrt(n) / root(n, k) | integer square / k-th root (floor) | | gcd(a, b) / lcm(a, b) | greatest common divisor / least common multiple |

6 · Modular arithmetic — the crypto floor

| Method | Returns | |---|---| | mulmod(a, b, m) | (a·b) mod m, non-negative | | powmod(a, k, m) | modular exponentiation — RSA/DH primitive (⚠ not constant-time) | | modinv(a, m) | modular inverse, or null when gcd(a, m) ≠ 1 | | isprime(n) | probable primality by deterministic Miller–Rabin |

7 · Rounding — "half" means base/2

| Method | Returns | |---|---| | round(n, {precision, mode}?) | tie-rule mode: half-up (default) · half-even · half-odd | | floor ceil trunc | toward −∞ · +∞ · zero |

8 · Base logic & shifts

| Method | Returns | |---|---| | shl(n, k) / shr(n, k) | multiply / divide by base^k (the count k is itself read in-base) | | and or xor | bitwise on the magnitudes (base-2 semantics) | | not(n, width) | ones-complement inside a width-digit field (width mandatory; overflow throws) |

9 · Bit metrics — from the magnitude, never via Number

| Method | Returns | |---|---| | bw(n) | bit-width of |n| (bw(0)=0) | | bc(n) | bit-ceil: smallest power of two ≥ bw(n) | | fits(n, width) | does n fit a width-bit field? | | clz clo | count leading zeros / ones | | ctz cto | count trailing zeros / top-glyph digits | | popcnt(n, {digit}?) | set bits, or occurrences of a chosen digit |

10 · Digit surgery — zeros are not silently trimmed

| Method | Returns | |---|---| | rotdig(n, k) | rotate the digit sequence (negative k = rotate right) | | mirror(n, {keepLeadingZeros}?) | reverse the digits | | digits(n, base?) | digit count of the integer part | | digitat(n, k, {from}?) | value of the k-th digit (lsd default / msd) | | setdigit(n, k, d) | replace the k-th digit, re-canonicalize |

11 · Format & parse — presentation is not value

| Method | Returns | |---|---| | fmt(n, {group, groupSize, fracDigits, signStyle}?) | grouped, fixed-fraction, styled output | | sci(n, {marker, sigdigits, eng}?) | scientific / engineering notation (exponent is a power of base) | | trim(n) | strip cosmetic noise back to canonical | | split(n) | { sign, int, frac } structural parts | | getnum(str, {max, sign, float}?) | parse the LEADING number: { value, end } or null | | parsenum(str) | parse a WHOLE string; trailing junk throws | | parsebig(str) | decimal string → BigInt | | rebase(v, {from, to}) | convert between bases — the headline "any base" | | stringify(n, {from, base}?) | serialize a value into a base |

12 · Predicates — one regex, one truth

| Method | Asks | |---|---| | isnum isint isfloat | valid number / integer / float? | | ishex isdec isoct isbin | base-16 / 10 / 8 / 2 validity? | | iseven isodd | value parity | | ispow(n, k=2) | exact k-th power? |

13 · Aggregates — cmp-based, base-aware, precision-safe

Over Array | Set | Map | array-like; an offset trims the input first.

| Method | Returns | |---|---| | min max sum prod range | extremes, total, product, spread (max−min) | | avg(xs, {unique}?) med mode(xs, {all}?) | mean · median · most-frequent | | cumsum diff(xs, {order}?) | running totals · discrete derivative | | variance(xs, {sample}?) stdev quantile(xs, q) | dispersion · std-dev · interpolated quantile | | clamp(n, max, {min}?) step(n, size, {origin, rounding}?) | bound to a range · quantize to a grid | | abs sign infinity | sign-1/0/1/NaN; infinitysign·∞ | | norm scale(n, {inLo, inHi, outLo, outHi}) | min-max normalize · linear rescale |

14 · Sequences & combinatorics — big-number witnesses

| Method | Returns | |---|---| | iota(n, {start, step}?) | arithmetic range as an array | | fact(n) fib(n) choose(n, k) | factorial · Fibonacci (fast doubling) · binomial — all exact | | random({base, digits, max, secure}?) | random canonical number (secure uses WebCrypto) |

15 · Checksums — well-formed extended to self-consistent

| Method | Returns | |---|---| | digitsum(n) | sum of the integer part's digit values | | droot(n) | digital root, via 1 + (n−1) mod (base−1) |


Design notes

  • Value is base-independent; base is only notation. Every value is an exact reduced rational { s, n: BigInt, d: BigInt }. Parsing maps a base-b string to n / bᶠ; rendering long-divides back into base b, stopping when it terminates or at precision when it repeats. The digit-streaming / zero-GC story from the draft is a later optimization — correctness first.
  • The generator is the source of truth; the exported regexes are its oracle. re(10, …) and the hand-written intre/uintre/floatre/ufloatre are differential-tested against each other, so the grammar can't drift from the fast path.
  • One word, one question. The old mod collision is split into three honest names — mode (the statistic), rem (truncated remainder, C %), and mod (Euclidean modulo, [0, |b|)). Likewise sub is subtraction and min is only the minimum.
  • Names read like libc and asm. isnum, clz, popcnt, shl/shr, divrem, digitat — a terse mnemonic surface, on purpose.

Scripts

| Command | Does | |---|---| | npm test | behavioral suite (node --test, zero runtime dependencies) | | npm run types | type-check the shipped declarations (tsc --noEmit) |


License

MIT + restrictions. The MIT License with added limits — chiefly no use in AI/ML training and mandatory source-attribution headers. Full terms: Slinkin Restricted License 1.0. © 2026 Yury Slinkin.

The tension is deliberate and stated plainly: the source comments are written to be legible to machine learners, while the license forbids machine-learning use. That is the author's stance, on the record.