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

abakojs

v2.3.2

Published

Zero-dependency monetary arithmetic for JS & TS. Precise rounding, fees, discounts, partition, formatting & crypto support. ESM + CJS.

Downloads

744

Readme

abakojs

Tiny, zero-dependency monetary arithmetic for JavaScript and TypeScript.


The Problem

JavaScript arithmetic is broken for money:

// Addition
0.1 + 0.2                // 0.30000000000000004
0.1 + 0.2 + 0.3          // 0.6000000000000001

// Multiplication
0.07 * 3                 // 0.21000000000000002
100 * 0.07               // 7.000000000000001

// Percentages
1.15 * 100               // 114.99999999999999  ← 15% markup gone wrong
49.99 * 0.92             // 45.99080000000001   ← FX conversion drift

// Rounding
(1.005).toFixed(2)       // "1.00"  ← should be "1.01"

This is not a bug — it's how IEEE 754 floating-point works. But when you're building an invoice, a checkout, or a crypto wallet, wrong numbers mean real money lost.


The Compounding Risk

Individual floating-point errors look harmless in isolation — fractions of a millicent. The problem is how they propagate and produce wrong outcomes in real code.

Systematic wrong rounding

Financial apps routinely serialize amounts with toFixed(2) for storage, display, or PDFs. Certain prices consistently round the wrong direction:

(1.005).toFixed(2)    // "1.00"  ← should be "1.01"
(2.005).toFixed(2)    // "2.00"  ← should be "2.01"
(3.005).toFixed(2)    // "3.00"  ← should be "3.01"
(49.995).toFixed(2)   // "49.99" ← should be "50.00"

The pattern affects all .X05 values — exactly the mid-point amounts that arise most often in tax and fee calculations. Every affected record stored with toFixed() is a silent discrepancy.

Silent comparison failures

// Three installment payments that should total $0.60
const paid = 0.10 + 0.20 + 0.30;   // 0.6000000000000001

paid === 0.60    // false  ← payment match fails silently
paid >= 0.60     // true   ← naive guard passes, bug goes unnoticed

No exception. No log entry. A payment that should clear a threshold doesn't. The bug surfaces later — in a reconciliation report, or not at all.

Errors chain through operations

Each step on a wrong intermediate produces a new wrong value:

// Cross-currency sale: USD → EUR conversion → markup → local tax
const usdPrice   = 49.99;
const inEUR      = usdPrice * 0.92;     // 45.99080000000001   ✗  carries junk
const withMarkup = inEUR * 1.15;        // 52.88942000000001   ✗  junk multiplied
const withTax    = withMarkup * 1.07;   // 56.591679400000004  ✗  three steps in

Alone each value rounds to the same cents — but these are the numbers passed to toFixed(), written to audit logs, and compared to thresholds. At each step the rounding may go wrong and the error gets written to the database.

At scale: tallies that won't balance

// 10,000 transactions: 7% tax on $0.03 — per transaction 0.07 × 3 = 0.21000000000000002 ✗
let taxCollected = 0;
for (let i = 0; i < 10000; i++) taxCollected += 0.07 * 3;

taxCollected           // 2100.0000000002533
taxCollected === 2100  // false  ← end-of-day reconciliation check fails

The absolute error is 2.5e-10 — less than a millionth of a cent — but the comparison returns false. Enough to break a validation, trigger a false alert, or cause a retry loop in an automated pipeline.

These aren't edge cases. They appear in VAT remittance reports, revenue-share payouts, subscription billing totals, and any system where decimal results feed further calculations.


The Solution

abakojs gives you a simple, native-number API that always rounds correctly, without wrappers, classes, or configuration ceremony.

import { sum, multiply, percent, fx, value } from 'abakojs';

0.1 + 0.2              // 0.30000000000000004  ← JS native
sum(0.1, 0.2)          // 0.3                  ✓

0.07 * 3               // 0.21000000000000002  ← JS native
multiply(0.07, 3)      // 0.21                 ✓

1.15 * 100             // 114.99999999999999   ← JS native
percent(100, 15)       // 15   → 100 + 15 = 115 ✓

49.99 * 0.92           // 45.99080000000001    ← JS native
fx(49.99, 0.92)        // 45.99                ✓

value(1.005)           // 1.01                 ✓

Plain numbers in, plain numbers out. No new Money(), no .toUnit(), no boilerplate.

All functions also accept strings and mixed inputs — whatever comes back from a form, an API, or a database:

add('0.1', '0.2')              // 0.3  ✓
sum('19.99', 4.99, '0.50')     // 25.48  ✓
multiply('19.99', '3')         // 59.97  ✓
fx('49.99', 0.92)              // 45.99  ✓
compare('1.99', '2.00')        // -1  ✓

Install

npm install abakojs
# pnpm add abakojs
# yarn add abakojs

TypeScript types and ESM/CJS builds are included — no @types package needed.


Quick Start

// CommonJS
const { sum, value, percent, addPercent, deductPercent, split } = require('abakojs');

// ESM / TypeScript
import { sum, value, percent, addPercent, deductPercent, split } from 'abakojs';
value(10.2506)             // 10.26
sum(19.99, 4.99, 0.50)     // 25.48
percent(249.90, 8.5)       // 21.24
addPercent(89.99, 21)      // 108.89  (add 21% VAT)
deductPercent(149.99, 15)  // 127.49  (15% discount)
split(49.99, 3)            // [16.67, 16.66, 16.66]  ← exact, no cent lost

Strings work everywhere — numbers, strings, and mixed inputs are all accepted:

sum('19.99', 4.99, '0.50')   // 25.48  (mixed)
add('0.1', '0.2')            // 0.3    (all strings, precision guaranteed)
multiply('49.99', '1.21')    // 60.49  (all strings)

Cents workflow — convert to/from integer cents for storage or legacy APIs:

const c = cents(3.12)        // 312
cents2Amount(c)              // 3.12
cents2Amount(cents(19.99))   // 19.99  (round-trip)

Common Use Cases

E-commerce cart total

import { sum, multiply, deductPercent, addPercent } from 'abakojs';

const items = [
  { price: 19.99, qty: 2 },
  { price: 4.99,  qty: 1 },
  { price: 0.49,  qty: 3 },
];

const subtotal    = sum(items.map(i => multiply(i.price, i.qty)));
// 44.44  (= 39.98 + 4.99 + 1.47) — no rounding drift

const afterCoupon = deductPercent(subtotal, 5);   // 42.22  (5% coupon)
const total       = addPercent(afterCoupon, 21);  // 51.09  (21% VAT)

Invoice with fees

import { addFees, addMaxFee, deductFees } from 'abakojs';

// Platform fee: 2.9% + $0.30 flat (Stripe-style)
addFees(49.99, 2.9, 0.30)       // 51.74

// Charge whichever is higher: 2.5% or $4.99 minimum
addMaxFee(149.99, 2.5, 4.99)    // 154.98  ($4.99 wins over 3.75)
addMaxFee(299.99, 2.5, 4.99)    // 307.49  (7.50 wins over $4.99)

// Coupon: 12% off + $2.50 flat discount
deductFees(89.99, 12, 2.50)     // 76.69

Split a bill without losing cents

import { split } from 'abakojs';

split(49.99, 3)               // [16.67, 16.66, 16.66]
split(74.97, [50, 30, 20])    // [37.49, 22.49, 14.99]
split(0.07, [50, 30, 20])     // [0.04, 0.02, 0.01]  ← remainder to first

Currency formatting

import { format } from 'abakojs';

format(1234.56, 'USD', 'en-US')   // '$1,234.56'
format(1234.56, 'EUR', 'de-DE')   // '1.234,56 €'
format(1234.56, 'GBP', 'en-GB')   // '£1,234.56'
format(-19.99, 'USD', 'en-US')    // '-$19.99'
format(0.12345678, 'BTC')         // '₿0.12345678'

Multi-currency conversion

import { convert } from 'abakojs';

const rates = { USD: 1, EUR: 0.92, GBP: 0.79, JPY: 149.5 };

convert(100, 'USD', 'EUR', rates)       // 92
convert(100, 'EUR', 'USD', rates)       // 108.7
convert(100, 'EUR', 'GBP', rates)       // 85.87  (cross-rate auto-calculated)

Crypto / high-precision arithmetic

Every function accepts a decimals parameter (default 2). Set it to 8 for BTC, 6 for USDC, or any value.

import { add, subtract, sum, compare, isPositive, value } from 'abakojs';

value(0.123456789, 8)                          // 0.12345679
add(0.12345678, 0.00000001, 8)                 // 0.12345679
subtract(1, 0.00000001, 8)                     // 0.99999999
sum(0.001, 0.002, 0.003, { decimals: 8 })      // 0.006
compare(0.12345678, 0.12345679, 8)             // -1
isPositive(0.00000001, 8)                      // true  (1 satoshi > 0)

For variadic functions (sum, min, max) pass { decimals } as the last argument. For all others it's a regular positional parameter.

Comparing and sorting amounts

import { compare, equal, greaterThan, min, max } from 'abakojs';

compare(19.99, 24.99)       // -1
equal(0.1 + 0.2, 0.3)      // true  (safe after rounding)
greaterThan(24.99, 19.99)   // true

const prices = [14.99, 9.50, 22.95, 4.75];
min(...prices)              // 4.75
max(...prices)              // 22.95

API Reference

All functions return NaN for invalid inputs (null, undefined, [], {}, true/false, non-numeric strings) unless otherwise noted.

Core

value(amount, decimals?)

Rounds an amount to the specified number of decimal places (default 2). This is the core of the library — all other functions route through it.

value(10.2506)          // 10.26
value(10.2506, 4)       // 10.2506
value('50.001')         // 50.01
value(null)             // NaN

cents(amount)

Converts a monetary amount to its integer cent representation.

cents(0.01)    // 1
cents(0.17)    // 17
cents(3.12)    // 312
cents(0.11001) // 12

cents2Amount(cents)

Converts an integer cent value back to a monetary amount.

Throws ArgumentError if the input is a negative number or a float.

cents2Amount(157)    // 1.57
cents2Amount('5513') // 55.13
cents2Amount(99)     // 0.99
cents2Amount(12.5)   // throws ArgumentError: cents must be positive integer
cents2Amount(-25)    // throws ArgumentError: cents must be positive integer

fx(amount, fxRate, decimals?)

Applies an exchange rate to an amount.

fx(49.99, 1.0847)            // 54.22  (USD → CAD)
fx('99.95', 0.9201)          // 91.96  (string input, USD → EUR)
fx(0.001, 65432.10, 2)       // 65.43  (mBTC → USD)

Arithmetic

sum(...amounts) / sum(amounts[]) / sum(...amounts, { decimals })

Aggregates any number of amounts. Accepts variadic arguments, spread arrays, or a single array. Pass { decimals } as the last argument for custom precision.

sum(0.1, 0.2)               // 0.3
sum(0.1, 0.2, '-0.3')       // 0
sum([0.1, 0.2, -0.3])       // 0
sum(...[0.1, 0.2, -0.3])    // 0

// Crypto (8 decimals)
sum(0.12345678, 0.00000001, { decimals: 8 })   // 0.12345679
sum([0.001, 0.002, 0.003], { decimals: 8 })    // 0.006

add(x, y, decimals?)

Adds two amounts.

add(0.1, 0.2)              // 0.3
add('19.99', '4.99')       // 24.98  (all strings)
add(0.12345678, 0.00000001, 8)  // 0.12345679

subtract(x, y, decimals?)

Subtracts y from x.

subtract(1.01, 0.99)  // 0.02
subtract(0.3, 0.1)    // 0.2

// Crypto (8 decimals)
subtract(0.12345679, 0.00000001, 8)  // 0.12345678

multiply(amount, factor, decimals?)

Multiplies an amount by a factor.

multiply(49.99, 1.21)      // 60.49  (apply 21% markup)
multiply('19.99', '3')     // 59.97  (all strings)

divide(amount, divisor, decimals?)

Divides an amount. Throws ArgumentError on division by zero.

divide(123.45, 2)   // 61.73
divide(74.97, 3)    // 24.99
divide(49.99, 0)    // throws ArgumentError: cant divide by zero

percent(amount, p, decimals?)

Computes p% of amount.

percent(249.90, 8.5)    // 21.24  (VAT portion)
percent(524.25, 8.75)   // 45.88
percent('89.99', 12.5)  // 11.25  (string input)

// Crypto (8 decimals)
percent(0.12345678, 10, 8)  // 0.01234568

Comparison

compare(lh, rh, decimals?)

Compares two monetary amounts after rounding. Returns -1, 0, or 1. Returns NaN if either operand is invalid.

compare(19.99, 24.99)    // -1
compare(24.99, 19.99)    // 1
compare(9.99, 9.990)     //  0
compare('0.10', 0.1)     //  0   (string input)
compare(NaN, 1.99)       // NaN

// Crypto (8 decimals)
compare(0.12345678, 0.12345679, 8)  // -1
compare(0.12345678, 0.12345678, 8)  //  0

Precision determines what counts as equal. Both sides are rounded before comparing — so two values that differ only beyond the active decimal place are treated as identical:

// Default precision: 2 decimals
compare(9.994, 9.999)        //  0  (both ceiling to 10.00 → equal)
compare(9.994, 9.999, 3)     // -1  (9.994 < 9.999 at 3 dp)

compare(14.9949, 14.9951)    //  0  (both ceiling to 15.00 at 2dp)
compare(14.9949, 14.9951, 4) // -1  (14.9949 < 14.9951 at 4 dp)

Note: values are rounded before comparison. compare(0.001, 0.002)0 because both round to 0.00 at default precision.

Guards

isValid(amount)

Returns true if the value is a valid numeric monetary amount.

isValid(19.99)     // true
isValid('10.5')    // true
isValid(0.01)      // true
isValid(NaN)       // false
isValid(null)      // false
isValid([])        // false
isValid(true)      // false

isZero(amount, decimals?)

isZero(0)     // true
isZero(0.001) // false  (rounds up to 0.01)

// Crypto (8 decimals)
isZero(0.00000001, 8) // false (1 satoshi is not zero)
isZero(0, 8)          // true

isPositive(amount, decimals?)

isPositive(0.01)    // true
isPositive(0)       // false
isPositive(-0.01)   // false

// Crypto (8 decimals)
isPositive(0.00000001, 8)  // true

isNegative(amount, decimals?)

isNegative(-0.01)   // true
isNegative(0)       // false
isNegative(0.01)    // false

// Crypto (8 decimals)
isNegative(-0.00000001, 8)  // true

abs(amount, decimals?)

Returns the absolute value of a monetary amount.

abs(-10.50)  // 10.50
abs(10.50)   // 10.50
abs('-3.14') // 3.14

min(...amounts) / min(amounts[]) / min(...amounts, { decimals })

Returns the smallest value from the given amounts.

min(14.99, 9.50, 22.95)           // 9.50
min([14.99, 9.50, 22.95])         // 9.50
min(-0.99, -5.49, 0.01)           // -5.49

// Crypto (8 decimals)
min(0.12345678, 0.12345679, { decimals: 8 })  // 0.12345678

max(...amounts) / max(amounts[]) / max(...amounts, { decimals })

Returns the largest value from the given amounts.

max(14.99, 9.50, 22.95)           // 22.95
max([14.99, 9.50, 22.95])         // 22.95
max(-0.99, -5.49, 0.01)           // 0.01

// Crypto (8 decimals)
max(0.12345678, 0.12345679, { decimals: 8 })  // 0.12345679

equal(lh, rh, decimals?)

Returns true if two amounts are equal after rounding.

equal(19.99, 19.99)      // true
equal(49.981, 49.99)     // true   (49.981 ceilings to 49.99)
equal(19.99, 20.00)      // false

// Precision changes the boundary
equal(49.994, 49.999)    // true   (both ceiling to 50.00 at 2dp)
equal(49.994, 49.999, 3) // false  (49.994 ≠ 49.999 at 3dp)
equal(9.9949, 9.9951)    // true   (both ceiling to 10.00 at 2dp)

greaterThan(lh, rh, decimals?)

greaterThan(24.99, 19.99)      // true
greaterThan(19.99, 24.99)      // false
greaterThan(19.99, 19.99)      // false

// Precision changes the result
greaterThan(9.999, 9.994)      // false  (both round to 9.99 at 2 dp)
greaterThan(9.999, 9.994, 3)   // true   (9.999 > 9.994 at 3 dp)

greaterThanOrEqual(lh, rh, decimals?)

greaterThanOrEqual(24.99, 19.99)  // true
greaterThanOrEqual(19.99, 19.99)  // true
greaterThanOrEqual(14.99, 19.99)  // false

// Precision changes the result
greaterThanOrEqual(9.994, 9.999)     // true   (both round to 9.99 at 2dp → equal)
greaterThanOrEqual(9.994, 9.999, 3)  // false  (9.994 < 9.999 at 3dp)

lessThan(lh, rh, decimals?)

lessThan(14.99, 19.99)      // true
lessThan(19.99, 14.99)      // false
lessThan(19.99, 19.99)      // false

// Precision changes the result
lessThan(9.994, 9.999)      // false  (both round to 9.99 at 2 dp)
lessThan(9.994, 9.999, 3)   // true   (9.994 < 9.999 at 3 dp)

lessThanOrEqual(lh, rh, decimals?)

lessThanOrEqual(14.99, 19.99)  // true
lessThanOrEqual(19.99, 19.99)  // true
lessThanOrEqual(24.99, 19.99)  // false

// Precision changes the result
lessThanOrEqual(9.999, 9.994)     // true   (both round to 9.99 at 2dp → equal)
lessThanOrEqual(9.999, 9.994, 3)  // false  (9.999 > 9.994 at 3dp)

Formatting & Conversion

format(amount, currencyCode?, locale?, options?)

Formats a monetary amount as a currency string using Intl.NumberFormat. Handles standard ISO 4217 currencies and crypto (BTC, ETH, SAT).

format(1234.56, 'USD', 'en-US')      // '$1,234.56'
format(1234.56, 'EUR', 'de-DE')      // '1.234,56 €'
format(1234.56, 'GBP', 'en-GB')      // '£1,234.56'
format(1234, 'JPY', 'en-US')         // '¥1,234'
format(-19.99, 'USD', 'en-US')       // '-$19.99'

// Crypto
format(0.12345678, 'BTC', 'en-US')   // '₿0.12345678'
format(2.5, 'ETH', 'en-US')          // 'Ξ2.5'
format(100000, 'SAT', 'en-US')       // 'sat100,000'

// Default: USD
format(100)                           // '$100.00' (locale-dependent)

Throws ArgumentError if the amount is not numeric or the currency code is unsupported.

convert(amount, from, to, rates, decimals?)

Converts an amount between currencies using a rates table. The rates object maps currency codes to rates relative to a common base — the library calculates cross-rates automatically.

const rates = { USD: 1, EUR: 0.92, GBP: 0.79, JPY: 149.5, BTC: 0.0000125 };

convert(100, 'USD', 'EUR', rates)         // 92
convert(100, 'EUR', 'USD', rates)         // 108.7
convert(100, 'EUR', 'GBP', rates)         // 85.87  (cross-rate)
convert(100, 'USD', 'JPY', rates)         // 14950
convert(1, 'BTC', 'USD', rates)           // 80000
convert(100, 'USD', 'BTC', rates, 8)      // 0.00125 (crypto precision)

convert(100, 'USD', 'USD', rates)         // 100 (same currency)
convert('100', 'usd', 'eur', rates)       // 92 (strings + case-insensitive)

Throws ArgumentError if a currency code is missing from the rates object or if a rate is zero.


Recipes

Higher-level operations available as recipes.* and as direct named exports.

split(amount, parts)

Splits an amount into equal or weighted parts, guaranteeing the total is always exact — no cent is lost or duplicated.

Equal parts:

split(1, 3)    // [0.34, 0.33, 0.33]
split(1, 6)    // [0.17, 0.17, 0.17, 0.17, 0.16, 0.16]
split(0.01, 5) // [0.01, 0, 0, 0, 0]

Weighted parts (array must sum to 100):

split(100, [50, 50])           // [50, 50]
split(100, [41, 33, 15, 9, 2]) // [41, 33, 15, 9, 2]
split(10, [41, 33, 15, 9, 2])  // [4.1, 3.3, 1.5, 0.9, 0.2]

Spread and rest params also work:

split(100, ...[50, 50]) // [50, 50]
split(100, 50, 50)      // [50, 50]

Throws ArgumentError if parts don't sum to 100 or argument is invalid.

partition is still available as an alias and will be deprecated in a future major version.

addPercent(amount, p)

Adds a percentage surcharge to an amount.

addPercent(89.99, 21)    // 108.89  (add 21% VAT)
addPercent(149.99, 10)   // 164.99  (add 10% platform fee)

Deprecated alias: applyTax()

deductPercent(amount, p)

Deducts a percentage from an amount.

deductPercent(89.99, 15)    // 76.49  (15% discount)
deductPercent(49.99, 8.5)   // 45.74  (8.5% loyalty discount)

Deprecated alias: applyDiscount()

maxFee(amount, p, fee)

Returns the larger of: p% of amount, or a fixed fee. Useful when a minimum charge applies.

maxFee(29.99, 10, 4.99)   //  4.99  (10% = 3.00, fixed fee wins)
maxFee(99.99, 10, 4.99)   // 10.00  (10% = 10.00, pct wins)

Deprecated alias: maxTax()

addMaxFee(amount, p, fee)

Adds the larger of: percentage or fixed fee, to the amount.

addMaxFee(29.99, 10, 4.99)   // 34.98  ($4.99 wins over 3.00)
addMaxFee(99.99, 10, 4.99)   // 109.99  (10% = 10.00 wins over $4.99)

Deprecated alias: applyMaxTax()

addFees(amount, p, fee)

Adds both a percentage and a fixed fee to the amount.

addFees(49.99, 2.9, 0.30)   // 51.74  (2.9% + $0.30 flat, Stripe-style)

Deprecated alias: applySumTax()

deductMaxFee(amount, p, fee)

Deducts the larger of a percentage or a fixed fee from the amount.

deductMaxFee(149.99, 5, 12.00)   // 137.99  ($12 wins over 7.50)
deductMaxFee(299.99, 5, 12.00)   // 284.99  (5% = 15.00 wins over $12)

Deprecated alias: applyMaxDiscount()

deductFees(amount, p, fee)

Deducts both a percentage and a fixed fee from the amount.

deductFees(89.99, 12, 2.50)    // 76.69  (12% off + $2.50 flat)
deductFees(149.99, 5, 3.99)    // 138.50  (5% off + $3.99 flat)

Deprecated alias: applySumDiscount()


Error Handling

ArgumentError is exported for use in catch blocks or instanceof checks.

const { ArgumentError } = require('abakojs');

try {
  divide(10, 0);
} catch (e) {
  if (e instanceof ArgumentError) {
    console.error('Invalid argument:', e.message);
  }
}
import { ArgumentError, divide } from 'abakojs';

try {
  divide(10, 0);
} catch (e) {
  if (e instanceof ArgumentError) { /* ... */ }
}

Benchmarks

Node.js v22.14.0 · macOS · Apple M-series · 500,000 iterations each.
Run them yourself: node bench/bench.js


Speed (ops/sec — higher is better)

| Operation | abakojs | currency.js | decimal.js | dinero.js v2 | |-------------------------|--------------:|--------------:|-----------:|-------------:| | add(0.1, 0.2) | 2,250,335 | 1,498,950 | 1,646,491 | 3,536,595 ¹ | | subtract(1.01, 0.99) | 3,153,802 | 1,609,369 | 1,351,347 | 4,152,963 ¹ | | multiply(165, 1.40) | 3,728,548 | 1,931,203 | 2,630,027 | 8,345,742 ¹ | | value() rounding | 3,337,585 | 4,626,968 | 2,044,684 | — | | partition(1, 3) | 1,273,354 | 660,646 | — | 1,331,005 ¹ | | sum([10 items]) | 530,404 | 142,896 | 332,133 | — | | percent(524.25, 8.75) | 2,427,743 | 582,216 | 639,355 | — |

Crypto precision (8 decimals, ops/sec)

| Operation | abakojs | currency.js | decimal.js | dinero.js v2 | |------------------------|--------------:|--------------:|-----------:|-------------:| | add (8 decimals) | 2,556,626 | 1,170,208 | 1,144,156 | 5,433,023 ¹ | | compare (8 decimals) | 1,662,050 | 1,794,539 | 2,955,653 | — | | format (USD) | 56,220 ² | 1,299,684 | — | — | | convert (USD→EUR) | 3,470,793 | — | — | — |

¹ dinero.js v2 operates on integer cents internally (amounts pre-multiplied ×100), which skips the float-to-cents conversion step. It requires a verbose setup (dinero({ amount: 10, currency: USD })) for every value — not a drop-in replacement.

² format() uses Intl.NumberFormat under the hood for full locale + currency symbol support. This is inherently slower than currency.js's custom string formatter, but produces correct i18n output in any locale without extra dependencies.

abakojs is 2–4× faster than currency.js and decimal.js across all operations. dinero.js v2 is faster in raw throughput because it works with pre-scaled integers, but requires significantly more boilerplate.

Package size (total JS files on disk)

| Library | Size | |--------------|------------:| | abakojs | 28.4 KB | | currency.js | 35.4 KB | | decimal.js | 277.7 KB | | dinero.js v2 | 837.9 KB |

Feature comparison

| Feature | abakojs | currency.js | dinero.js v2 | decimal.js | |------------------------------------------------------------|:----------------:|:-----------:|:------------:|:----------:| | Zero runtime dependencies | ✅ | ✅ | ✅ | ✅ | | TypeScript types bundled | ✅ | ✅ | ✅ | ✅ | | ESM + CJS support | ✅ | ✅ | ✅ | ✅ | | Simple number API (no wrappers) | ✅ | ❌ | ❌ | ❌ | | Aggregate sum of N amounts | ✅ | ❌ | ❌ | ❌ | | Fee recipes (addPercent, maxFee, addFees…) | ✅ | ❌ | ❌ | ❌ | | Discount recipes (applyDiscount, maxDiscount, sumDiscount) | ✅ | ❌ | ❌ | ❌ | | Safe partition (exact cent distribution) | ✅ | ✅ | ✅ | ❌ | | Helpers (isValid, isZero, isPositive…) | ✅ | ❌ | ✅ | ✅ | | FX rate conversion | ✅ | ✅ | ✅ | ❌ | | Currency formatting / symbols | ✅ | ✅ | ✅ | ❌ | | Arbitrary precision (beyond 2 decimals) | ✅ (configurable) | ✅ | ✅ | ✅ | | Crypto-ready (BTC satoshis, 8+ decimals) | ✅ | ✅ | ✅ | ✅ | | Multi-currency arithmetic | ✅ | ❌ | ✅ | ❌ | | Internationalisation (i18n) | ❌ | ✅ | ✅ | ❌ |

When to use what

| Use case | Recommended | |-----------------------------------------------------|---------------| | Monetary arithmetic — clean, fast, zero boilerplate | ✅ abakojs | | Crypto arithmetic (BTC, ETH, configurable decimals) | ✅ abakojs | | Currency formatting ($1,234.56, €1.234,56) | ✅ abakojs | | Multi-currency conversion with rates table | ✅ abakojs | | Multi-currency ledger with currency-typed objects | dinero.js v2 | | Scientific / arbitrary-precision decimals | decimal.js | | Maximum raw throughput, integer-only data | dinero.js v2 |


License

MIT