decimal.js vs big.js vs bignumber.js: Arbitrary Precision in JavaScript (2026)
TL;DR
decimal.js is the most feature-rich arbitrary precision library — supports trigonometric functions, logarithms, modular arithmetic, and is the basis for Decimal128 in databases. big.js is the smallest and simplest — 6KB, covers the most common use cases (add, subtract, multiply, divide with configurable precision), no trig. bignumber.js sits between them — same author as big.js and decimal.js, broader base support (binary, octal, hex), more features than big.js, lighter than decimal.js. For financial calculations: big.js (simple, fast, focused). For scientific computing with trig/log: decimal.js. For general-purpose arbitrary precision with base conversion: bignumber.js.
Key Takeaways
- decimal.js: ~10M weekly downloads — most features, trig/log, Decimal128-compatible
- big.js: ~35M weekly downloads — smallest (6KB), focused on decimal arithmetic
- bignumber.js: ~25M weekly downloads — middle ground, arbitrary base support
- All three solve the
0.1 + 0.2 !== 0.3floating point problem - For money: reach for dinero.js or integer arithmetic first — arbitrary precision is usually overkill
- All three are from the same author (MikeMcl) — similar APIs, different feature sets
Why Arbitrary Precision?
// JavaScript floating point failures:
0.1 + 0.2 // 0.30000000000000004
(0.1 + 0.2) === 0.3 // false
1.005.toFixed(2) // "1.00" (not "1.01")
9007199254740992 + 1 // 9007199254740992 (MAX_SAFE_INTEGER overflow)
Math.sqrt(2) // 1.4142135623730951 (limited precision)
// When you need exact arithmetic:
// - Financial calculations beyond cents
// - Scientific computing requiring many significant digits
// - Cryptography (large integer arithmetic)
// - Arbitrary-precision constants (π, e, etc.)
// - Tax calculations with complex rounding rules
big.js
big.js — small, focused decimal arithmetic:
Basic usage
import Big from "big.js"
// Create:
const price = new Big("19.99") // Always pass strings for exact values
const tax = new Big("0.08")
// Arithmetic:
const taxAmount = price.times(tax)
console.log(taxAmount.toFixed(2)) // "1.60" (not 1.5992000000000002)
console.log(price.plus(taxAmount).toFixed(2)) // "21.59"
// Chaining:
const total = new Big("19.99")
.times("1.08") // 8% tax included
.round(2) // Round to 2 decimal places
.toFixed(2) // "21.59"
// Comparisons:
const a = new Big("10.1")
const b = new Big("10.10")
a.eq(b) // true (equal)
a.gt(b) // false (not greater than)
a.lt(b) // false (not less than)
a.gte(b) // true (greater than or equal)
Configuring precision and rounding
import Big from "big.js"
// Global settings (affects all Big instances):
Big.DP = 20 // Decimal places for division (default: 20)
Big.RM = 1 // Rounding mode (0=down, 1=half-up, 2=half-even, 3=up)
// Per-calculation control:
const result = new Big("10").div("3").toFixed(5)
// "3.33333"
// Round to specific places:
new Big("1.555").round(2) // Big("1.56") — half-up
new Big("1.555").round(2, 0) // Big("1.55") — round down
new Big("1.555").round(2, 2) // Big("1.56") — half-even (banker's)
// Convert to number (use only when exact precision no longer needed):
const price = new Big("19.99")
Number(price) // 19.99
price.toNumber() // 19.99
// Format:
new Big("1234567.89").toFixed(2) // "1234567.89"
new Big("0.000001").toExponential(2) // "1.00e-6"
Financial use case
import Big from "big.js"
interface LineItem {
description: string
unitPrice: string // Store as string to avoid floating point
quantity: number
}
function calculateInvoice(items: LineItem[], taxRate: string) {
const subtotal = items.reduce(
(sum, item) => sum.plus(new Big(item.unitPrice).times(item.quantity)),
new Big("0")
)
const tax = subtotal.times(new Big(taxRate)).round(2, Big.roundHalfUp)
const total = subtotal.plus(tax)
return {
subtotal: subtotal.toFixed(2),
tax: tax.toFixed(2),
total: total.toFixed(2),
}
}
const invoice = calculateInvoice(
[
{ description: "Plan A", unitPrice: "99.99", quantity: 12 },
{ description: "Add-on", unitPrice: "19.99", quantity: 3 },
],
"0.09" // 9% tax
)
// { subtotal: "1259.85", tax: "113.39", total: "1373.24" }
decimal.js
decimal.js — full-featured arbitrary precision:
Basic usage
import Decimal from "decimal.js"
// Create:
const x = new Decimal("1.23456789012345678901234567890")
// Same arithmetic operations as big.js, plus:
// Trigonometric functions:
Decimal.sin(new Decimal("1.5707963267948966")) // sin(π/2) ≈ 1
Decimal.cos(new Decimal("0")) // cos(0) = 1
Decimal.tan(new Decimal("0.7853981633974484")) // tan(π/4) ≈ 1
// Logarithms:
Decimal.ln(new Decimal("2.718281828459045")) // ln(e) ≈ 1
Decimal.log10(new Decimal("100")) // 2
Decimal.log2(new Decimal("8")) // 3
// Power and square root:
new Decimal("2").pow(new Decimal("0.5")) // √2 = 1.4142135623...
Decimal.sqrt(new Decimal("2")) // Same result
new Decimal("e").exp() // e^e
High precision constants
import Decimal from "decimal.js"
// Set precision to 50 significant digits:
Decimal.set({ precision: 50 })
// Compute π to 50 digits:
Decimal.acos(-1).toString()
// "3.1415926535897932384626433832795028841971693993751"
// Euler's number e to 50 digits:
Decimal.exp(1).toString()
// "2.7182818284590452353602874713526624977572470936999"
// Golden ratio:
new Decimal(1).plus(Decimal.sqrt(5)).div(2).toString()
// "1.6180339887498948482045868343656381177203091798058"
Configuration
import Decimal from "decimal.js"
// Configure globally:
Decimal.set({
precision: 20, // Significant digits (default: 20)
rounding: 4, // Half-up (default: 4)
// Rounding modes:
// 0 = round down (truncate)
// 1 = round towards zero
// 2 = round towards +Infinity
// 3 = round towards -Infinity
// 4 = half up (round half away from zero)
// 5 = half even (banker's rounding)
// 6 = half down
// 7 = half towards zero
// 8 = Euclid's rule
toExpNeg: -7, // Use exponential notation below this exponent
toExpPos: 21, // Use exponential notation above this exponent
})
// Or create a scoped Decimal constructor:
const D = Decimal.clone({ precision: 50, rounding: 5 })
const pi = D.acos(-1) // π to 50 digits with banker's rounding
bignumber.js
bignumber.js — general-purpose, multi-base:
Basic usage
import BigNumber from "bignumber.js"
// Like big.js and decimal.js but with arbitrary base support:
const x = new BigNumber("1234567890.123456789")
// Standard arithmetic:
x.plus("0.000000001").toFixed() // "1234567890.123456790"
x.times("2").toFixed() // "2469135780.246913578"
// More formatting options:
x.toFormat(2) // "1,234,567,890.12" (with grouping)
x.toFormat(2, { groupSeparator: ".", decimalSeparator: "," })
// "1.234.567.890,12" (European format)
Multi-base conversion
import BigNumber from "bignumber.js"
// Arbitrary base support (2-36):
const decimal = new BigNumber(255)
decimal.toString(16) // "ff" (hex)
decimal.toString(2) // "11111111" (binary)
decimal.toString(8) // "377" (octal)
// Convert from arbitrary base:
new BigNumber("ff", 16).toNumber() // 255
new BigNumber("11111111", 2).toNumber() // 255
// Hex with padding:
new BigNumber("FF8800", 16).toString() // "16744448"
// Useful for: cryptographic big integers, color codes, encoding systems
Comparison with big.js
// big.js vs bignumber.js vs decimal.js
// (all from same author, MikeMcl)
// big.js:
// + Smallest (6KB)
// + Simple API
// - No trig/log, no base conversion, limited rounding modes
// Use for: financial calculations, simple decimal math
// bignumber.js:
// + More features than big.js (base conversion, more rounding modes)
// + Better formatting options (toFormat with locales)
// - Bigger than big.js (~20KB)
// Use for: general-purpose, need base conversion or better formatting
// decimal.js:
// + Most features (trig, log, exp, all rounding modes)
// + Decimal128 compatible for database storage
// - Largest (~30KB)
// Use for: scientific computing, complex math, Decimal128 databases
Feature Comparison
| Feature | big.js | bignumber.js | decimal.js |
|---|---|---|---|
| Bundle size | ~6KB | ~20KB | ~30KB |
| Decimal precision | ✅ | ✅ | ✅ |
| Trig/log functions | ❌ | ❌ | ✅ |
| Arbitrary base | ❌ | ✅ (2-36) | ❌ |
| Formatting (toFormat) | ❌ | ✅ | ✅ |
| Rounding modes | 4 | 9 | 9 |
| Decimal128 compat | ❌ | ❌ | ✅ |
| TypeScript | ✅ | ✅ | ✅ |
| ESM | ✅ | ✅ | ✅ |
| Browser | ✅ | ✅ | ✅ |
When to Use Each
Choose big.js if:
- Financial calculations requiring exact decimal arithmetic
- You want the smallest possible bundle for browser use
- Simple add/subtract/multiply/divide with configurable rounding
- API simplicity over feature richness
Choose bignumber.js if:
- You need base conversion (binary/hex/octal ↔ decimal)
- Better locale-aware number formatting with
toFormat() - More rounding modes than big.js provides
- General-purpose arbitrary precision without trig
Choose decimal.js if:
- Scientific computing requiring trigonometric or logarithmic functions
- You need a Decimal128-compatible value for MongoDB or PostgreSQL
- Complex mathematical operations beyond basic arithmetic
- Maximum precision (up to ~1e9 significant digits)
Stick with native numbers if:
// For most web apps, native numbers are fine:
const price = 19.99
const tax = Math.round(price * 0.08 * 100) / 100 // = 1.60 (rounded to cents)
const total = Math.round((price + tax) * 100) / 100 // = 21.59
// Integer cents approach covers 99% of financial use cases:
const priceInCents = 1999
const taxInCents = Math.round(priceInCents * 0.08) // 160
const totalInCents = priceInCents + taxInCents // 2159
// Use arbitrary precision only when:
// 1. Precision beyond 15 significant digits is required
// 2. Many chained operations that compound floating point errors
// 3. Scientific/crypto applications needing exact arbitrary precision
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on big.js v6.x, bignumber.js v9.x, and decimal.js v10.x. All three libraries are maintained by the same author (MikeMcl).