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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@silyze/decimal

v1.0.0

Published

Decimal (fraction) number library

Downloads

28

Readme

@silyze/decimal

A tiny, dependency-free, exact rational arithmetic library for TypeScript/JavaScript backed by bigint. Decimal stores numbers as a fully reduced fraction N/D and gives you:

  • Exact add / sub / mul / div with automatic fraction reduction
  • Deterministic string formatting across radices (2–36)
  • Flexible parsing ("1/3", "1.25", "1e3", "0x1.fp2", "1_000.50", etc.)
  • Safe comparisons (eq/lt/gt, min/max)
  • Interoperability helpers (lossless→string, lossy→number/string on demand)

Lossy transcendental helpers (exp/log/sin/cos/tan) are included for convenience and use IEEE-754 Math.* under the hood.


Installation

npm i @silyze/decimal
# or
pnpm add @silyze/decimal
# or
yarn add @silyze/decimal

Quick start

import decimal, { Decimal } from "@silyze/decimal";

// Construct from literals
const a = Decimal.from("1/3");
const b = Decimal.from("2.5"); // base-10
const c = Decimal.from("0x1.fp2"); // hex-float = (1.9375) * 2^2 = 7.75

// Exact arithmetic
const sum = a.add(b); // 1/3 + 2.5 = 17/6
const mul = sum.mul("3/7"); // 17/6 * 3/7 = 17/14

// Deterministic formatting
sum.toString(); // "17/6" (non-terminating in base 10 → fraction form)
Decimal.from("1/8").toString(); // "0.125" (terminating → decimal form)
Decimal.from("42").toString(16); // "2a"
Decimal.from("42").toString(16, false, true); // "0x2a" (with radix prefix)

// Lossy formatting when you want a fixed-point string
sum.toString(10, true); // "2.83333333333333333333333333333333" (up to 34 frac digits)

// Coercion to number (only exact if denominator is 10^k)
+Decimal.from("1/4"); // 0.25 (exact)
+Decimal.from("1/3"); // 0.3333333333333333 (IEEE-754 rounding)

Why this library?

  • Exactness first. Most “decimal” libraries are either base-10 decimal floating point or big decimal approximations. Decimal stores a reduced rational fraction, preserving exactness for all rational operations.
  • Predictable I/O. Non-terminating expansions return "N/D" by default. You can opt into bounded fixed-point formatting via lossy = true.
  • Bigint-powered. No overflow in intermediate integer steps (limited by available memory/time).

Concepts & Behavior

Representation

  • Internals: two private bigint fields #n (numerator) and #d (denominator).
  • Always stored in lowest terms with a positive denominator.

Parsing (via Decimal.from(value, divisor = 1, radix = 0))

Accepts:

  • Numbers (finite only)

  • Bigints

  • Strings with:

    • Fractions: "A/B" (both sides can themselves be any accepted literal)

    • Decimal: "123", "1.25", "1e6", optional underscores for readability: "1_000.50"

    • Radix prefixes when radix = 0:

      • Hex "0x" / "0X", Octal "0o" / "0O", Binary "0b" / "0B"
    • Hex floats with binary exponent: "0x1.fp2" (i.e., mantissa base-16, exponent base-2)

  • Optional leading sign "+" | "-".

  • Optional divisor to scale inputs exactly (useful for minor units).

Examples:

Decimal.from("1/6").toString(); // "1/6"
Decimal.from("1e3").toString(); // "1000"
Decimal.from("0x1.fp2").toString(); // "7.75"
Decimal.from("1_000_000").toString(); // "1000000"
Decimal.from("1011", 1, 2).toString(); // "11" (binary literal)
Decimal.from("7f.8", 1, 16).toString(); // "127.5"
Decimal.from("123", 100).toString(); // "1.23"

Formatting (toString(radix = 10, lossy = false, withPrefix = false))

  • If the fractional expansion terminates in the given radix, returns a fixed-point string (e.g., 0.125).

  • Otherwise:

    • lossy = false → returns exact "N/D".
    • lossy = true → returns fixed-point string with up to 34 fractional digits.
  • withPrefix adds 0x for base-16 or 0b for base-2 when formatting integers.

Examples:

Decimal.from("1/8").toString(10); // "0.125"
Decimal.from("1/3").toString(10); // "1/3"
Decimal.from("1/3").toString(10, true); // "0.33333333333333333333333333333333"
Decimal.from("42").toString(16, false, true); // "0x2a"

Coercion & JSON

  • valueOf / Number(x) / unary +x:

    • If denominator is a power of ten → uses precise fixed-point conversion.
    • Otherwise → Number(n) / Number(d) (IEEE-754 rounding).
  • toJSON() returns toString(); non-terminating decimals serialize as "N/D" unless you format otherwise.

If you need always-fixed-point JSON, call x.toString(10, true) yourself.


API Reference

Types

export type NumericLike = Decimal | bigint | number | string;
export type decimal = Decimal; // alias

Construction

new Decimal(base: bigint, divisor: bigint); // internal; base/divisor
Decimal.from(value: NumericLike, divisor?: number | bigint, radix?: number): Decimal;
// Convenience factory (default export):
decimal(value: number | string, divisor?: number | bigint, radix?: number): decimal;
  • Throws:

    • RangeError("... divisor cannot be 0")
    • RangeError("non-finite number") for Number.NaNInfinity
    • SyntaxError for malformed strings (invalid digits, multiple slashes, etc.)

Arithmetic (exact)

add(num: NumericLike): Decimal;
sub(num: NumericLike): Decimal;
mul(num: NumericLike): Decimal;
div(num: NumericLike): Decimal; // throws on division by zero
negate(): Decimal;

All operations reduce the result to lowest terms.

Power & Elementary functions

static pow(base: NumericLike, exp: NumericLike): Decimal;
// Exact for integer exponents. For non-integer exponents falls back to Math.pow (lossy).

static exp(x: NumericLike): Decimal; // lossy (uses Math.exp)
static log(x: NumericLike, base?: NumericLike): Decimal; // lossy (uses Math.log), domain-checked
static ln(x: NumericLike): Decimal;  // alias for log(x)
static sin(x: NumericLike): Decimal; // lossy (uses Math.sin)
static cos(x: NumericLike): Decimal; // lossy (uses Math.cos)
static tan(x: NumericLike): Decimal; // lossy (uses Math.tan)
  • pow with negative integer exponents returns the exact reciprocal if defined.

Comparison & Utilities

static eq(a: NumericLike, b: NumericLike): boolean;
static lt(a: NumericLike, b: NumericLike): boolean;
static gt(a: NumericLike, b: NumericLike): boolean;

static min(a: NumericLike, b: NumericLike): Decimal; // returns one of the coerced Decimals
static max(a: NumericLike, b: NumericLike): Decimal;

static abs(x: NumericLike): Decimal;

String & Number conversion

toString(radix = 10, lossy = false, withPrefix = false): string;
valueOf(): number; // see coercion notes above
toJSON(): string;  // equals toString()
[Symbol.toPrimitive](hint: "number" | "string" | "default"): number | string;

Examples

Exact pipelines

const price = Decimal.from("19.99"); // exact 1999/100
const qty = Decimal.from(3);
const tax = Decimal.from("21"); // 21%
const total = price.mul(qty); // 59.97 → 5997/100
const gross = total.mul(Decimal.from("1/100")).mul(tax).add(total);
// gross = total * (1 + tax/100)
gross.toString(10, true); // "72.5637..." (fixed-point, 34 digits max)

Minor units (divisor)

// 12345 cents → 123.45
Decimal.from(12345n, 100n).toString(); // "123.45"

Binary/hex work

Decimal.from("0b101.01").toString(); // "5.25"
Decimal.from("0x1.fp2").toString(16, false); // "7.c" (terminating in hex)
Decimal.from("0x1/0x3").toString(16, true); // "0.5555555555555555..." (fixed-point in hex if desired)

Error handling

| Condition | Error type | Message (excerpt) | | ----------------------------------------- | ----------- | ----------------------------------------------- | | divisor === 0 in ctor/from | RangeError | divisor cannot be 0 | | Division by zero in div / "A/B" parse | RangeError | division by zero | | Non-finite number input | RangeError | non-finite number | | Invalid radix (not 0 or 2..36) | RangeError | radix must be an integer in [2, 36] | | Malformed string / invalid digit | SyntaxError | invalid mantissa / digit 'x' out of range ... | | Hex-float exponent invalid | SyntaxError | invalid hex-float exponent | | General powBigInt negative exponent | RangeError | powBigInt: negative exponent |


Performance notes

  • Arithmetic uses fraction cross-reduction via gcd to keep numerators/denominators small.
  • All integer ops are bigint; performance scales with operand size. If you repeatedly format with lossy = true, consider caching strings if profiling shows hotspots.

Interop tips

  • Always-exact JSON: keep the default toJSON() or explicitly serialize as "N/D" for non-terminating cases by using toString() with lossy = false.
  • Always-fixed JSON: serialize with x.toString(10, true) to force a fixed-point decimal with up to 34 fractional digits.
  • Numbers: use +x or Number(x) only if you accept IEEE-754 rounding (or if your denominator is a power of ten).

TypeScript support

  • Fully typed, ships with .d.ts.

  • Exported types:

    • NumericLike = Decimal | bigint | number | string
    • decimal alias
  • Default export is the convenience factory decimal(...).


FAQ

Q: Why does toString() sometimes return "N/D"? A: If the fraction does not have a terminating expansion in the chosen radix (e.g., 1/3 in base-10), the default is to preserve exactness as "N/D". Use toString(radix, true) for fixed-point output.

Q: Will valueOf() always be exact? A: Only when the denominator is a power of ten (10^k). Otherwise converting to number uses IEEE-754 and may round.

Q: Are transcendental functions exact? A: No. exp/log/sin/cos/tan delegate to Math.* (double precision). They are provided for convenience.