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

compact-money

v1.0.0

Published

Compact monetary amounts into structured financial units with optional currency metadata.

Readme

Compact Money

Compact monetary amounts into structured { amount, unit, currency? } data. Dependency-free at runtime, with TypeScript types and ESM/CommonJS builds. The companion Dart/Flutter package uses the same numeric policy and shared test examples.

Installation

npm install compact-money

Requires Node.js 22 or newer for supported Node usage. The runtime is also browser-compatible and has no Node-specific imports. Bundlers can tree-shake it.

Usage

import { compact } from 'compact-money';

compact({ amount: 80_000, currency: 'VND' });
// { amount: 80, unit: 'k', currency: 'VND' }

const { amount, unit, currency } = compact({ amount: 30_487, currency: 'VND' });
// amount = 30487; unit = ''; currency = 'VND'

compact({ amount: 8_123_456, currency: 'VND' });
// { amount: 8123456, unit: '', currency: 'VND' }

compact({ amount: 8_123_000, currency: 'VND' });
// { amount: 8123, unit: 'k', currency: 'VND' }

compact({ amount: 8_123_000, currency: 'VND', decimals: 3 });
// { amount: 8.123, unit: 'M', currency: 'VND' }

compact({ amount: 8_123_456, allowRounding: true, decimals: 1 });
// { amount: 8.1, unit: 'M' }

compact({ amount: 30_487, currency: 'VND', allowRounding: true });
// { amount: 30.5, unit: 'k', currency: 'VND' }

CommonJS: const { compact } = require('compact-money');

API

import {
  compact,
  compactAmount,
  type CompactOptions,
  type CompactResult,
  type CompactUnit,
} from 'compact-money';

The exported types and primary signature are:

type CompactUnit = '' | 'k' | 'M' | 'B' | 'T';
interface CompactOptions {
  readonly amount: number;
  readonly currency?: string;
  readonly allowRounding?: boolean; // default: false
  readonly decimals?: number; // integer 0–100; default 0 without rounding
}
interface CompactResult {
  readonly amount: number;
  readonly unit: CompactUnit;
  readonly currency?: string;
}
// compact(options: CompactOptions): CompactResult

compactAmount is the exact same function as compact. Inputs are never mutated. Omitted currency (including an explicit undefined from JavaScript) produces no currency property. Invalid options, non-number or non-finite amounts, invalid currency, non-boolean rounding flags and invalid decimal limits throw TypeError. Strings are never coerced to numbers. NaN and both infinities are rejected.

Units and rounding

Units are selected using the absolute input and preserve its sign. Without rounding, the largest unit that fits within decimals is selected, defaulting to zero decimal places. With rounding, the largest unit based on magnitude is selected first. Currency never participates in the calculation.

| Symbol | Divisor | Dart unit | | ------ | ----------------: | ---------------------- | | "" | 1 | CompactUnit.none | | k | 1,000 | CompactUnit.thousand | | M | 1,000,000 | CompactUnit.million | | B | 1,000,000,000 | CompactUnit.billion | | T | 1,000,000,000,000 | CompactUnit.trillion |

Rounding is disabled by default. Set allowRounding: true to enable half-to-even rounding. If decimals is supplied, it sets the maximum decimal places. Otherwise, the existing financial policy uses the absolute normalized amount:

| Magnitude | Maximum decimal places | | ------------- | ---------------------: | | Less than 10 | 2 | | Less than 100 | 1 | | 100 or more | 0 |

This yields approximately three significant digits in the usual compact range, not a general significant-digit formatter: small values can round to zero, and amounts above 999 T retain more than three digits.

Midpoints use round-half-to-even (banker's rounding). At zero decimal places, 2.5 rounds to 2 and 3.5 to 4; at two places, 1.245 rounds to 1.24 and 1.255 to 1.26. These examples describe the rounding primitive; with allowRounding: true, compact chooses decimal places using the table when decimals is omitted (so an input of 2.5 remains 2.5). The implementation explicitly compares the fractional part with one half; it does not delegate midpoint decisions to string formatting. A tolerance of twice machine epsilon times the scaled magnitude, capped at 0.0000001, treats tiny binary midpoint errors as ties. Values within that tolerance can therefore round as a midpoint.

With rounding enabled, if rounding reaches 1,000, the unit is promoted and rounding applied again: 999,999 becomes 1 M; 999,999,999 becomes 1 B; 999,999,999,999 becomes 1 T. The same applies from base units to k. T is the ceiling: 1,500,000,000,000,000 becomes 1,500 T. Negative values use the same rules symmetrically. Zero always uses the base unit, and negative zero is normalized to positive zero.

| Input | Rounded amount | Unit | | ----------: | -------------: | ---- | | 800 | 800 | "" | | 30,487 | 30.5 | k | | 80,000 | 80 | k | | 812,345 | 812 | k | | 8,123,456 | 8.12 | M | | 81,234,567 | 81.2 | M | | 812,345,678 | 812 | M | | -30,487 | -30.5 | k |

Decimal places without rounding

decimals is an optional integer from 0 to 100. When rounding is disabled, its default is 0. It limits unit selection, not the original amount's precision: the library tries smaller units until the normalized amount fits exactly within the requested decimal places. If no compact unit fits, the original amount is returned unchanged with the base unit, even if it has more decimal places. There is no truncation, rounding, or padding with trailing zeroes.

| Input | decimals | Amount | Unit | | --------: | ----------: | -------: | ---- | | 8,123,456 | omitted (0) | 8123456 | "" | | 8,123,000 | omitted (0) | 8123 | k | | 8,000,000 | omitted (0) | 8 | M | | 8,123,000 | 2 | 8123 | k | | 8,123,000 | 3 | 8.123 | M | | 8,123,456 | 3 | 8123.456 | k | | 8,123,456 | 6 | 8.123456 | M | | 1.23456 | 2 | 1.23456 | "" |

For comparison, allowRounding: true, decimals: 2 converts 8,123,456 to 8.12 M. allowRounding: true, decimals: 0 converts it to 8 M. Negative values behave symmetrically in both modes.

Exact unit eligibility is determined from the input number's shortest decimal representation, including scientific notation and trailing zeroes. This avoids mistaking a division artifact for intentional precision. The returned amount is still a numeric value and remains subject to floating-point limitations.

Currency and presentation

Currency is optional metadata. There is no ISO 4217 lookup or currency database. VND, LAK, IDR, KRW, JPY, EUR, USD, CZK, CSK, USDC, USDT, BTC, ETH, CORE and custom identifiers all work. Empty and whitespace-only strings are rejected; valid identifiers are preserved exactly, including case and surrounding whitespace.

The application decides how to show the separate values: 30.5k VND, VND 30.5k, ₫30.5k or 30.5k ₫. This library does not choose currency symbols, locale, symbol placement, thousands separators or decimal separators.

Numeric precision

IEEE-754 binary floating point cannot exactly represent every decimal fraction. Normalization can introduce small errors even when rounding is disabled. The midpoint tolerance mitigates common errors but does not provide exact decimal arithmetic. Values too large to retain a fractional part are returned without additional rounding. Finite values above the T range remain supported.

This package provides display-oriented numeric representations, not accounting or bookkeeping arithmetic. Use an appropriate exact-decimal approach for those calculations before passing a display amount to this package. Numeric results do not retain formatting such as trailing decimal zeroes.

JavaScript integers beyond Number.MAX_SAFE_INTEGER (2⁵³ − 1) can already have lost precision before this library receives them. BigInt is not accepted.

Development and releases

npm install
npm run check
npm run test:package
npm pack --dry-run

Tests use Node’s built-in node:test runner and node:assert/strict. TypeScript tests compile into the ignored .test-dist/ directory before execution. test:coverage uses Node’s built-in V8 coverage with 100% line, branch and function thresholds for the compiled library source. No separate test framework is required.

Individual scripts include build, test, test:coverage, lint, format, format:check and typecheck. Package smoke tests install the tarball and check ESM, CommonJS and both TypeScript declaration entry points. See CONTRIBUTING.md for release setup and fixture maintenance.

License

CORE License, matching the CORE libraries. npm metadata uses SEE LICENSE IN LICENSE for this custom license.