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

js-precision

v0.3.0

Published

Frontend high-precision decimal library powered by Rust + WebAssembly with strict-by-default immutable APIs.

Readme

js-precision

中文 | English

面向前端业务的高精度十进制计算库,基于 Rust + WebAssembly,底层使用 rust_decimal

A frontend-oriented high-precision decimal library powered by Rust + WebAssembly, built on top of rust_decimal.

npm license

npm i js-precision

中文

开发自测

# Rust 单测 / 集成测试
npm run test:rust

# wasm 运行测试(Node 环境)
npm run test:wasm:node

快速上手

import { Precise, RoundingMode } from "js-precision";

const result = new Precise("0.1")
  .add("0.2")
  .div("3")
  .withPrecision(8)
  .withRounding(RoundingMode.HalfUp)
  .toFixed();

console.log(result); // "0.10000000"

strict 模式下,非法输入会抛错而不是静默兜底。所有运算返回新实例,不修改原值。

核心设计

| 特性 | 说明 | | --- | --- | | 严格输入 | 非法字符串 / NaN / Infinity 直接抛错 | | 不可变链式 | add/sub/mul/div 均返回新 Precise 实例 | | 精度可配置 | withPrecision(n) 设置默认输出位数(0–28) | | 舍入可选 | Trunk(截断)/ HalfUp(四舍五入)/ HalfEven(银行家舍入) | | 宽松模式 | *Loose 系列:非法输入按 01 兜底,不抛错 |

构造

// strict(推荐)
new Precise("123.45")
Precise.fromString("1e-7")
Precise.fromNumber(1.5)          // ⚠️ 已有浮点误差无法修复,金融场景优先用字符串

// loose(容错场景)
Precise.fromStringLoose("bad")   // → 0
Precise.fromNumberLoose(NaN)      // → 0

// 分转元
Precise.fromCents("12345")       // → 123.45(scale 默认 2)
Precise.fromCents("12345", 3)    // → 12.345
Precise.fromCentsLoose("bad")    // → 0,不抛错

四则运算

const base = new Precise("100");

// strict(推荐)
base.add("20")       // 120
base.sub("5")        // 95
base.mul("1.1")      // 110
base.div("3")        // 33.333...  div("0") 会抛错

// 语义别名(strict)
base.plus("20")
base.minus("5")
base.times("1.1")
base.dividedBy("3")

// loose(非法输入不抛错)
base.addLoose("abc")  // 加 0 → 100
base.subLoose("abc")  // 减 0 → 100
base.mulLoose("abc")  // 乘 1 → 100  (注意:不是乘 0)
base.divLoose("0")    // 除数为 0 → 返回原值 100

链式调用不改变原值:

const a = new Precise("10");
const b = a.add("5"); // b = 15
a.toString();          // 仍然是 "10"

比较

const p = new Precise("10.5");

p.eq("10.5")  // true
p.lt("11")    // true
p.lte("10.5") // true
p.gt("10")    // true
p.gte("10.5") // true

比较基于高精度 Decimal,非法参数同样抛错。

聚合

const values = ["1.2", 2.3, "3.4"];

Precise.sum(values).toString()                    // "6.9"
Precise.avg(values).withPrecision(4).toFixed()   // "2.3000"
Precise.min(values).toString()                    // "1.2"
Precise.max(values).toString()                    // "3.4"

空数组行为: sum / avg / min / max 传入空数组均会抛错。

精度与输出

const amount = new Precise("1234567.891")
  .withPrecision(2)
  .withRounding(RoundingMode.HalfUp);

amount.toFixed()                                    // "1234567.89"
amount.toDisplay(2, true)                           // "1,234,567.89"
amount.toDisplayWith({ grouping: true })            // "1,234,567.89"(使用实例精度)
amount.toDisplayWith({ grouping: true, separator: "_" }) // "1_234_567.89"
amount.toString()                                   // "1234567.891"(原始值,不补零)
amount.toNumber()                                   // 1234567.891(可能丢精度)
amount.toCents()                                    // "123456789"(元转分,scale 默认 2)

toFixed vs toString

| 方法 | 补零 | 受 withPrecision 影响 | | --- | --- | --- | | toFixed() | ✅ | ✅ | | toString() | ❌ | ❌ |

舍入模式:

| 模式 | 说明 | 1.235 保留 2 位 | | --- | --- | --- | | RoundingMode.Trunk | 向 0 截断(默认) | 1.23 | | RoundingMode.HalfUp | 四舍五入 | 1.24 | | RoundingMode.HalfEven | 银行家舍入 | 1.24(4 为偶数) |

完整示例:订单结算

import { Precise, RoundingMode } from "js-precision";

const items = [
  { qty: "2", unitPrice: "199.90" },
  { qty: "1", unitPrice: "88.50" },
  { qty: "3", unitPrice: "12.34" },
];

try {
  // 行小计求和
  const subtotal = Precise.sum(
    items.map((it) => new Precise(it.qty).mul(it.unitPrice).toString())
  );

  // 九五折 + 6% 税
  const payable = subtotal
    .mul("0.95")
    .mul("1.06")
    .withPrecision(2)
    .withRounding(RoundingMode.HalfUp);

  console.log(payable.toDisplayWith({ grouping: true })); // "533.78"
} catch (err) {
  console.error("计算失败:", err);
}

错误处理

所有 strict API 在以下情况抛错:

  • 非法字符串("abc""""1.2.3"
  • 非有限数(NaNInfinity
  • 除以零(div("0")
  • 空数组聚合(sum/avg/min/max([])
  • fromCents 传入非整数
try {
  new Precise(userInput).div(rate).withPrecision(2).toFixed();
} catch (err) {
  // 统一在边界捕获,内部链式无需逐步判断
}

v1 升级说明见 MIGRATION_V1_TO_V2.md


English

Local Validation

# Rust unit/integration tests
npm run test:rust

# wasm runtime tests (Node)
npm run test:wasm:node

Quick Start

import { Precise, RoundingMode } from "js-precision";

const result = new Precise("0.1")
  .add("0.2")
  .div("3")
  .withPrecision(8)
  .withRounding(RoundingMode.HalfUp)
  .toFixed();

console.log(result); // "0.10000000"

In strict mode, invalid input throws instead of silently falling back. All arithmetic methods return new instances (immutable style).

Design Highlights

| Feature | Description | | --- | --- | | Strict Input | Invalid strings / NaN / Infinity throw errors | | Immutable Chaining | add/sub/mul/div always return new Precise instances | | Configurable Precision | withPrecision(n) controls default output scale (0–28) | | Selectable Rounding | Trunk / HalfUp / HalfEven | | Loose Mode | *Loose APIs use explicit fallback (0 or 1) without throwing |

Construction

// strict (recommended)
new Precise("123.45")
Precise.fromString("1e-7")
Precise.fromNumber(1.5) // binary floating-point precision is already decided in JS

// loose
Precise.fromStringLoose("bad") // -> 0
Precise.fromNumberLoose(NaN)    // -> 0

// cents -> decimal
Precise.fromCents("12345")      // -> 123.45 (default scale = 2)
Precise.fromCents("12345", 3)   // -> 12.345
Precise.fromCentsLoose("bad")   // -> 0

Arithmetic

const base = new Precise("100");

// strict
base.add("20")
base.sub("5")
base.mul("1.1")
base.div("3")      // div("0") throws

// strict aliases
base.plus("20")
base.minus("5")
base.times("1.1")
base.dividedBy("3")

// loose
base.addLoose("abc") // add 0
base.subLoose("abc") // sub 0
base.mulLoose("abc") // mul 1 (keeps original value)
base.divLoose("0")   // keep original value

Immutable behavior:

const a = new Precise("10");
const b = a.add("5");
a.toString(); // still "10"

Comparison

const p = new Precise("10.5");

p.eq("10.5")
p.lt("11")
p.lte("10.5")
p.gt("10")
p.gte("10.5")

All comparisons are based on high-precision decimal values and validate inputs strictly.

Aggregation

const values = ["1.2", 2.3, "3.4"];

Precise.sum(values).toString()                   // "6.9"
Precise.avg(values).withPrecision(4).toFixed()  // "2.3000"
Precise.min(values).toString()                   // "1.2"
Precise.max(values).toString()                   // "3.4"

Empty arrays: sum/avg/min/max([]) all throw.

Precision and Output

const amount = new Precise("1234567.891")
  .withPrecision(2)
  .withRounding(RoundingMode.HalfUp);

amount.toFixed()                                    // "1234567.89"
amount.toDisplay(2, true)                           // "1,234,567.89"
amount.toDisplayWith({ grouping: true })            // "1,234,567.89"
amount.toDisplayWith({ grouping: true, separator: "_" }) // "1_234_567.89"
amount.toString()                                   // "1234567.891"
amount.toNumber()                                   // 1234567.891 (precision may be lost)
amount.toCents()                                    // "123456789"

toFixed vs toString:

| Method | Zero-padding | Affected by withPrecision | | --- | --- | --- | | toFixed() | ✅ | ✅ | | toString() | ❌ | ❌ |

Rounding modes:

| Mode | Description | 1.235 with 2 digits | | --- | --- | --- | | RoundingMode.Trunk | Truncate toward 0 (default) | 1.23 | | RoundingMode.HalfUp | Round half away from 0 | 1.24 | | RoundingMode.HalfEven | Bankers rounding | 1.24 |

Full Example: Order Settlement

import { Precise, RoundingMode } from "js-precision";

const items = [
  { qty: "2", unitPrice: "199.90" },
  { qty: "1", unitPrice: "88.50" },
  { qty: "3", unitPrice: "12.34" },
];

try {
  const subtotal = Precise.sum(
    items.map((it) => new Precise(it.qty).mul(it.unitPrice).toString())
  );

  const payable = subtotal
    .mul("0.95")
    .mul("1.06")
    .withPrecision(2)
    .withRounding(RoundingMode.HalfUp);

  console.log(payable.toDisplayWith({ grouping: true }));
} catch (err) {
  console.error("calculation failed:", err);
}

Error Handling

Strict APIs throw on:

  • Invalid decimal strings ("abc", "", "1.2.3")
  • Non-finite numbers (NaN, Infinity)
  • Division by zero (div("0"))
  • Empty array aggregations (sum/avg/min/max([]))
  • Non-integer input for fromCents
try {
  new Precise(userInput).div(rate).withPrecision(2).toFixed();
} catch (err) {
  // Catch once at boundaries
}

For v1 migration details, see MIGRATION_V1_TO_V2.md.