Skip to content

Repository files navigation

cent

Arbitrary-precision currency library for TypeScript/JavaScript

cent is a next-generation monetary math library designed to handle currencies with fixed precision, no matter how large the values or how many decimal places are required.

This makes cent a good choice for accounting, F/X, trading, and cryptocurrency applications.

Why cent?

The problem

Popular libraries like dinero.js are built on JavaScript's Number type, which has fundamental limitations:

  • Precision Loss: JavaScript's Number can only safely represent integers up to Number.MAX_SAFE_INTEGER (2⁵³ - 1)
  • Floating Point Errors: Floating point arithmetic can introduce rounding errors
  • Limited Scale: Struggles with high-precision assets like cryptocurrencies. Neither assets on Bitcoin (8 decimals), Solana (9 decimals), or Ethereum (18 decimals) fit into a JS Number.

The solution

cent solves these problems with:

  • 🔢 Arbitrary Precision: Uses BigInt for unlimited precision arithmetic
  • 💰 Multi-Asset Support: Handles traditional currencies, cryptocurrencies, and custom assets
  • 🧮 Exact Mathematics: Fxied point math that guarantees exacy results, with options for non-fixed calculations.
  • 🌍 Comprehensive Currency Database: Built-in support for 180+ world currencies
  • 🎯 Type Safety: Full TypeScript support with strict type checking
  • 🔄 Immutable: All operations return new instances, preventing accidental mutations
  • ✨ Ergonomic API: Clean factory functions for creating numbers from strings

Quick start 💰

import{Money,Price}from'@thesis/cent'// Creationconstusd=Money("$100.50")constbtc=Money("0.5 BTC")// Arithmeticconsttotal=usd.add(Money("$25.25"))// $125.75// Conversion with precision preservationconstprice=newPrice(Money("$50,000"),Money("1 BTC"))constconverted=usd.convert(price)// Exact BTC amount// Allocation and distributionconst[first,second,third]=usd.allocate([1,2,1])// [$25.13, $50.25, $25.12]const[a,b,c]=usd.distribute(3)// [$33.50, $33.50, $33.50]// Formattingusd.toString({locale: "en-US",compact: true})// "$100.50"btc.toString({preferredUnit: "satoshi"})// "50,000,000 sat"

Core utils

Money() and the Money class

The Money() factory function makes working with currencies simple:

import{Money}from'@thesis/cent'// Parse currency symbols with auto-detectionconstusd=Money('$1,234.56')// US Dollar: $1,234.56consteur=Money('€1.234,56')// Euro (EU format): €1,234.56constgbp=Money('£999.99')// British Pound: £999.99constjpy=Money('¥50,000')// Japanese Yen: ¥50,000// Parse currency codes (case insensitive)constdollars=Money('USD 100.50')consteuros=Money('100.50 EUR')// Parse cryptocurrency main unitsconstbitcoin=Money('₿2.5')// Bitcoin: 2.5 BTCconstethereum=Money('ETH 10.123456')// Ethereum: 10.123456 ETH// Parse cryptocurrency sub-unitsconstsatoshis=Money('1000 sat')// 1000 satoshis = 0.00001000 BTCconstwei=Money('1000000 wei')// 1000000 wei = 0.000000000001 ETHconstgwei=Money('50 gwei')// 50 gwei = 0.00000005 ETH// Parse with fractional unit symbolsconstsats=Money('§10000')// 10000 satoshis = 0.0001 BTCconstcents=Money('¢50')// 50 cents = $0.50constpence=Money('p75')// 75 pence = £0.75// Supports negative amountsconstdebt=Money('-$500.25')constrefund=Money('€-123.45')// Financial precision - allows sub-cent amountsconstprecise=Money('$100.12345')// 5 decimal places preservedconstmicroYen=Money('¥1000.001')// Sub-yen precision

A note on symbol priority: When symbols are shared (like $ for multiple currencies), the most traded currency takes priority based on global trading volume: $ → USD, £ → GBP, ¥ → JPY. Use explicit currency codes for other currencies: AUD 100, CAD 50.

The Money class provides safe monetary operations with automatic precision handling:

import{Money,EUR,USD}from'@thesis/cent'// Create money instancesconsteuros=newMoney({asset: EUR,amount: {amount: 50025n,decimals: 2n}// €500.25})constdollars=newMoney({asset: USD,amount: {amount: 100000n,decimals: 2n}// $1,000.00})// Basic arithmetic (same currency only)constsum=euros.add("€250.50")console.log(sum.toString())// "€750.75"// Multiplication and divisionconstdoubled=euros.multiply("2")consthalf=euros.divide("2")// Only works with factors of 2 and 5// Comparisonsconsole.log(euros.greaterThan(dollars))// Error: Different currenciesconsole.log(euros.isPositive())// trueconsole.log(euros.equals(euros))// true// Sorting arrays using compare methodconstamounts=[Money("$100"),Money("$50"),Money("$200")]constsorted=amounts.sort((a,b)=>a.compare(b))console.log(sorted.map(m=>m.toString()))// ["$50.00", "$100.00", "$200.00"]// Formatting optionsconsole.log(euros.toString({locale: 'de-DE'}))// "500,25 €"console.log(euros.toString({compact: true}))// "€500"// Fractional unit symbol formattingconstbtc=Money("0.01 BTC")console.log(btc.toString({preferredUnit: "sat"}))// "1,000,000 sats"console.log(btc.toString({preferredUnit: "sat",preferFractionalSymbol: true}))// "§1,000,000"console.log(btc.toString({preferredUnit: "sat",preferFractionalSymbol: true,compact: true}))// "§1M"// Allocation and distributionconstbudget=Money("$1000")// Allocate proportionally by ratiosconst[marketing,development,operations]=budget.allocate([2,5,3])// Results: [$200, $500, $300] (2:5:3 ratio)// Distribute evenlyconst[alice,bob,charlie]=budget.distribute(3)// Results: [$333.34, $333.33, $333.33] (remainder to first)// Handle fractional units separatelyconstprecise=Money("$100.00015")constparts=precise.distribute(3,{distributeFractionalUnits: false})// Results: [$33.33, $33.33, $33.34, $0.00015] (change separated)

Math utilities 🧮

cent comes with two flavors of arbitrary-precision math utilities.

FixedPointNumber is appropriate for financial applications that require keeping track of "cents" or other fractional units of a currency. By disallowing arbitrary division, fixed-point numbers make it difficult to lose track of a fractional unit.

RationalNumber is appropriate for wider arbitrary-precision math applications.

Examples

import{FixedPoint,Rational}from'@thesis/cent'// FixedPoint - Perfect for decimal numbersconstprice=FixedPoint('1255.50')// Auto-detects 2 decimalsconstrate=FixedPoint('0.875')// Auto-detects 3 decimals// Percentage strings are automatically converted to decimalsconstpercentage=FixedPoint('51.5%')// Becomes 0.515 (auto-detects 3 decimals)consttax=FixedPoint('8.25%')// Becomes 0.0825 (auto-detects 4 decimals)// Arithmetic operations with automatic precision handlingconstproduct=price.multiply("0.875")console.log(product.toString())// "1098.5625"// Use percentage parsing in calculationsconsttotalWithTax=price.multiply(FixedPoint('8.25%'))console.log(totalWithTax.toString())// "103.5788" (8.25% of $1255.50)// Precise division (only multiples of 2 and 5)consthalf=price.divide("2")constfifth=price.divide("5")consttenth=price.divide("10")// Comparison operationsconsole.log(price.greaterThan("0.875"))// trueconsole.log(price.lessThanOrEqual("0.875"))// false// Also supports original constructor for explicit controlconstexplicit=newFixedPointNumber(125550n,2n)// Same as FixedPoint('1255.50')// Rational - fractions and exact arithmetic// Create from fraction stringsconstoneThird=Rational('1/3')consttwoFifths=Rational('2/5')// Create from decimal strings (auto-converted to fractions)constquarter=Rational('0.25')// Becomes 1/4constdecimal=Rational('0.125')// Becomes 1/8// Create directly from bigint numerator and denominatorconstpi=Rational(22n,7n)// 22/7 approximation of πconstoneThird=Rational(1n,3n)// 1/3// Exact arithmeticconstsum=oneThird.add("2/5")// (1/3) + (2/5) = 11/15console.log(sum.toString())// "11/15"constproduct=oneThird.multiply("2/5")// (1/3) * (2/5) = 2/15console.log(product.toString())// "2/15"// Automatic simplificationconstsimplified=Rational('6/9')console.log(simplified.toString())// "2/3"// Also supports original constructorconstexplicit=newRationalNumber({p: 1n,q: 10n})// Same as Rational('1/10')// Seamless conversion between typesconstrational=Rational('3/8')constdecimalStr=rational.toDecimalString()// "0.375"constfixedPoint=FixedPoint(decimalStr)// Auto-detects 3 decimalsconsole.log(fixedPoint.toString())// "0.375"

Prices and Exchange Rates

cent includes Price and ExchangeRate classes for representing price ratios between assets with mathematical operations.

ExchangeRate has base/quote currency semantics, time-based operations, and everything you'd expect in a fintech app. It's appropriate rates retrieved from outside services like exchanges.

import{ExchangeRate,Money,USD,EUR,BTC,JPY}from'@thesis/cent'// 1. Individual arguments with auto-timestampingconstusdEur=newExchangeRate(USD,EUR,"1.08")// 1.08 EUR per USDconsole.log(usdEur.toString())// "1.08 €/$"// 2. Individual arguments with custom timestamp and sourceconstbtcUsd=newExchangeRate(BTC,USD,"50000","1640995200",// 2022-01-01 timestamp{name: "Coinbase",priority: 1,reliability: 0.95})consteurJpy=newExchangeRate({baseCurrency: EUR,quoteCurrency: JPY,rate: "162.50",timestamp: "1640995200",source: {name: "ECB",priority: 1,reliability: 0.99}})console.log(usdEur.baseCurrency.code)// "USD" (1 USD costs...)console.log(usdEur.quoteCurrency.code)// "EUR" (...1.08 EUR)// Rate inversion - swap base and quoteconsteurUsd=usdEur.invert()// 1 EUR = 0.925925... USDconsole.log(eurUsd.toString())// "0.925925925925925926 $/@"// Cross-currency calculations via multiplication// EUR/USD × USD/JPY = EUR/JPY (USD cancels out)consteurUsdRate=newExchangeRate(EUR,USD,"1.0842")constusdJpyRate=newExchangeRate(USD,JPY,"149.85")consteurJpyCalculated=eurUsdRate.multiply(usdJpyRate)console.log(eurJpyCalculated.baseCurrency.code)// "EUR"console.log(eurJpyCalculated.quoteCurrency.code)// "JPY"console.log(eurJpyCalculated.rate.toString())// "162.4673" (1.0842 × 149.85)// Currency conversion with exchange ratesconstdollars=Money("$100.00")consteuros=usdEur.convert(dollars)console.log(euros.toString())// "€108.00"// Reverse conversion (automatic direction detection)constbackConverted=usdEur.convert(euros)console.log(backConverted.toString())// "$100.00"// Automatic direction detection - same rate works both waysconstrate=newExchangeRate(USD,EUR,"1.08")// 1 USD = 1.08 EURconstusd100=Money("$100")consteur108=rate.convert(usd100)// USD → EUR: $100 → €108console.log(eur108.toString())// "€108.00"constconvertBack=rate.convert(eur108)// EUR → USD: €108 → $100console.log(convertBack.toString())// "$100.00"// Works with any amount and either currency in the rateconstmoreEuros=Money("€540")// €540constconvertedDollars=rate.convert(moreEuros)// €540 ÷ 1.08 = $500console.log(convertedDollars.toString())// "$500.00"// Exchange rate averaging for multiple sourcesconstrate1=newExchangeRate(USD,EUR,"1.07")constrate2=newExchangeRate(USD,EUR,"1.09")constaveraged=newExchangeRate(ExchangeRate.average([rate1,rate2]))console.log(averaged.rate.toString())// "1.080" (average of 1.07 and 1.09)// Time-based operationsconstcurrentRate=newExchangeRate(USD,EUR,"1.08")console.log(currentRate.isStale(300000))// false (less than 5 minutes old)// Formatting optionsconsole.log(usdEur.toString())// "1.08 €/$" (symbol format)console.log(usdEur.toString({format: "code"}))// "1.08 EUR/USD" (code format)console.log(usdEur.toString({format: "ratio"}))// "1 USD = 1.08 EUR" (ratio format)// JSON serialization with BigInt supportconstserialized=usdEur.toJSON()constrestored=ExchangeRate.fromJSON(serialized)console.log(restored.equals(usdEur))// true// create bid/ask spreads for tradingconstrate=newExchangeRate(USD,EUR,"1.2000")// apply spread using decimal string (2% spread)const{ bid, ask, mid }=rate.spread("0.02")// "2%" also worksconsole.log(bid.rate.toString())// "1.1880" (1.2000 - 1% of 1.2000)console.log(ask.rate.toString())// "1.2120" (1.2000 + 1% of 1.2000)console.log(mid.rate.toString())// "1.2000" (original rate)

Price is appropriate for arbitrary price pairs, covering the edge cases where ExchangeRate might not be appropriate. It's easier to construct a new Price and do math with it.

import{Price,USD,EUR,BTC,JPY}from'@thesis/cent'// Define custom assets (for demonstration purposes)constAPPLE={name: 'Apple',code: 'APPLE',decimals: 0n,symbol: '🍎'}constORANGE={name: 'Orange',code: 'ORANGE',decimals: 0n,symbol: '🍊'}// Create price ratiosconstusdPerApple=newPrice(Money("$5.00"),// $5.00{asset: APPLE,amount: {amount: 1n,decimals: 0n}}// 1 apple (custom asset))constapplesPerBtc=newPrice({asset: APPLE,amount: {amount: 10000n,decimals: 0n}},// 10,000 applesMoney("1 BTC")// 1.00000000 BTC)// Price-to-Price multiplication (assets must share a common unit)// $5/apple × 10,000 apples/BTC = $50,000/BTC// while this is fun, remember that there might not be apple-to-BTC liquidity 😉constusdPerBtc=usdPerApple.multiply(applesPerBtc)console.log(usdPerBtc.amounts[0].amount.amount)// 5000000n ($50,000.00)// Price-to-Price division// $50,000/BTC ÷ $5/apple = 10,000 apples/BTCconstcalculatedApplesPerBtc=usdPerBtc.divide(usdPerApple)// Scalar operations (multiply/divide by numbers)constdoubledPrice=usdPerApple.multiply("2")// $10.00/appleconsthalfPrice=usdPerApple.divide("2")// $2.50/apple// Convert to mathematical ratioconstratio=usdPerApple.asRatio()// RationalNumber: 500/1// Price operations validate shared assetstry{constorangesPerBtc=newPrice({asset: ORANGE,amount: {amount: 5000n,decimals: 0n}},Money("1 BTC"))usdPerApple.multiply(orangesPerBtc)// Error: no shared asset!}catch(error){console.log(error.message)// "Cannot multiply prices: no shared asset found between US Dollar/Apple and Orange/Bitcoin"}

Price Ranges

cent includes a PriceRange class for representing and manipulating price ranges with precision. Perfect for e-commerce filters, pricing strategies, and financial analysis.

import{PriceRange,Money,USD,EUR}from'@thesis/cent'// Create ranges from stringsconstrange1=PriceRange("$50 - $100")constrange2=PriceRange("$50-100")// Compact formatconstrange3=PriceRange("€25 - €75")// Create from Money instancesconstrange4=PriceRange(Money("$50"),Money("$100"))// Mixed creationconstrange5=PriceRange("$50",Money("$100"))console.log(range1.min.toString())// "$50.00"console.log(range1.max.toString())// "$100.00"console.log(range1.span.toString())// "$50.00" (difference)console.log(range1.midpoint.toString())// "$75.00" (precise midpoint)// Range operations and queriesconsole.log(range1.contains(Money("$75")))// trueconsole.log(range1.contains("$25"))// falseconsole.log(range1.isAbove(Money("$40")))// true (entire range above $40)console.log(range1.isBelow(Money("$120")))// true (entire range below $120)// Range mathematicsconstrange6=PriceRange("$80 - $150")console.log(range1.overlaps(range6))// trueconstintersection=range1.intersect(range6)console.log(intersection?.toString())// "$80.00 - $100.00"constunion=range1.union(range6)console.log(union.toString())// "$50.00 - $150.00"// Split ranges into equal partsconstparts=range1.split(3)console.log(parts[0].toString())// "$50.00 - $66.67"console.log(parts[1].toString())// "$66.67 - $83.33"console.log(parts[2].toString())// "$83.33 - $100.00"// Static factory methods for common patternsconstunderRange=PriceRange.under(Money("$100"))// "$0.00 - $100.00"constoverRange=PriceRange.over(Money("$50"),Money("$500"))// "$50.00 - $500.00"constaroundRange=PriceRange.around(Money("$100"),"10%")// "$90.00 - $110.00"// Create price buckets for filtersconstbuckets=PriceRange.createBuckets(Money("$0"),Money("$500"),5)buckets.forEach((bucket,i)=>{console.log(`Bucket ${i+1}: ${bucket.toString()}`)})// Bucket 1: $0.00 - $100.00// Bucket 2: $100.00 - $200.00// Bucket 3: $200.00 - $300.00// Bucket 4: $300.00 - $400.00// Bucket 5: $400.00 - $500.00// Display formatting optionsconsole.log(range1.toString())// "$50.00 - $100.00" (default)console.log(range1.toString({format: "compact"}))// "$50-100"console.log(range1.toString({format: "from"}))// "From $50.00"console.log(range1.toString({format: "upTo"}))// "Up to $100.00"console.log(range1.toString({format: "range"}))// "$50.00 to $100.00"console.log(range1.toString({format: "between"}))// "Between $50.00 and $100.00"// Localized formattingconsteurRange=PriceRange("€50 - €100")console.log(eurRange.toString({locale: "de-DE"}))// "50,00 € - 100,00 €"// Large ranges with compact notationconstlargeRange=PriceRange("$1000000 - $5000000")console.log(largeRange.toString({compact: true}))// "$1M - $5M"// Currency conversionconstexchangeRate=newExchangeRate(USD,EUR,"0.85")constconvertedRange=range1.convert(exchangeRate)console.log(convertedRange.toString())// "€42.50 - €85.00"// E-commerce product filteringconstproducts=[{name: "Budget Widget",price: Money("$45")},{name: "Standard Widget",price: Money("$75")},{name: "Premium Widget",price: Money("$125")},{name: "Deluxe Widget",price: Money("$95")}]constpriceFilter=PriceRange("$50 - $100")constaffordableProducts=products.filter(product=>priceFilter.contains(product.price))console.log(affordableProducts.map(p=>p.name))// ["Standard Widget", "Deluxe Widget"]// JSON serialization for APIs and storageconstserialized=range1.toJSON()console.log(JSON.stringify(serialized))constrestored=PriceRange.fromJSON(serialized)console.log(restored.equals(range1))// true// Cryptocurrency ranges with full precisionconstbtcRange=PriceRange("₿0.001 - ₿0.01")console.log(btcRange.contains(Money("₿0.005")))// trueconsole.log(btcRange.toString({preferredUnit: "sat"}))// "100,000 sats - 1,000,000 sats"

Other features

Currency support

cent includes comprehensive currency metadata for accurate formatting:

import{USD,EUR,BTC,ETH,JPY}from'@thesis/cent'// Traditional currenciesconsole.log(USD.decimals)// 2nconsole.log(USD.symbol)// "$"console.log(USD.fractionalUnit)// "cent"// Cryptocurrencies with high precisionconsole.log(BTC.decimals)// 8nconsole.log(BTC.fractionalUnit)// Complex object with multiple units// Currencies with no decimalsconsole.log(JPY.decimals)// 0n

JSON Serialization

Safe serialization for APIs and storage:

constmoney=Money("$1,234,567,890,123.45")// serialize (BigInt becomes string)constjson=money.toJSON()console.log(JSON.stringify(json))// {"asset":{"name":"United States dollar","code":"USD","decimals":"2","symbol":"$"},"amount":"1234567890123.45"}// Deserializeconstrestored=Money.fromJSON(json)console.log(restored.equals(money))// true// FixedPointNumber also serializes as decimal strings preserving trailing zerosconstfp=FixedPoint("12.34500")console.log(JSON.stringify(fp))// "12.34500"constrestoredFp=FixedPointNumber.fromJSON("12.34500")console.log(restoredFp.equals(fp))// true

Precision handling

cent automatically handles different precisions:

// different decimal places are automatically normalizedconstfp1=FixedPoint("10.0")// 1 decimalconstfp2=FixedPoint("5.00")// 2 decimalsconstsum=fp1.add("5.00")// Normalized to 2 decimalsconsole.log(sum.toString())// "15.00"

Safe division

Unlike floating-point arithmetic, cent ensures exact division results:

constnumber=FixedPoint("100")// 100console.log(number.divide("2").toString())// "50.0"console.log(number.divide("4").toString())// "25.00"console.log(number.divide("5").toString())// "20.0"console.log(number.divide("10").toString())// "10.0"// throws an exception (3 cannot be represented exactly in decimal)try{number.divide("3")}catch(error){console.log(error.message)// "divisor must be composed only of factors of 2 and 5"}

If you need division that would break out of what's possible to represent in fixed point, you can mix FixedPointNumber and RationalNumber.

Rational("1/3").multiply(FixedPoint("100"))

Use cases

FinTech

// handle large transfers with perfect precisionconstwireTransfer=Money("$9,999,999,999.99")constfee=wireTransfer.multiply("0.005")// 0.5% feeconstafterFee=wireTransfer.subtract(fee)

Cryptocurrencies

// handle Bitcoin with satoshi and sub-satoshi precisionconstsatoshiAmount=Money("1 BTC")console.log(satoshiAmount.toString({preferredUnit: 'satoshi'}))// "100,000,000 satoshis"satoshiAmount.equals(Money("100000000 sat"))// true// ethereum with wei precision (18 decimals)constweiAmount=Money("1 ETH")// Also supports original constructor for explicit controlconstexplicit=newMoney({asset: ETH,amount: {amount: 1000000000000000000n,decimals: 18n}// Same as Money("1 ETH")})weiAmount.equals(Money("Ξ1.0"))// true

Accounting & bookkeeping

// Allocate amounts without losing precisionconstrevenue=Money("$12,345.67")// Proportional allocation by department budgetsconst[marketing,engineering,sales,operations]=revenue.allocate([2,5,2,1])// Results: [$2,469.13, $6,172.84, $2,469.13, $1,234.57] (2:5:2:1 ratio)// Even distribution among team membersconstbonus=Money("$10,000")const[alice,bob,charlie]=bonus.distribute(3)// Results: [$3,333.34, $3,333.33, $3,333.33] (remainder to first recipient)// Handle fractional units for precision accountingconstpreciseAmount=Money("$1,000.00123")// High-precision amountconstparts=preciseAmount.allocate([1,1,1],{distributeFractionalUnits: false})// Results: [$333.33, $333.33, $333.34, $0.00123]// Main allocations clean, fractional $0.00123 can go to a separate ledger// Traditional concretization for currency sub-unitsconst[main,change]=preciseAmount.concretize()console.log(main.toString())// "$1,000.00" (standard currency precision)console.log(change.toString())// "$0.00123" (sub-unit precision)

API reference

Factory Functions

Money() - Parse currency strings with intelligent format detection

  • Money(str) - Parse currency strings with symbols, codes, and crypto units
    • Currency symbols: Money('$100.50'), Money('€1.234,56'), Money('£999')
    • Currency codes: Money('USD 100'), Money('100.50 EUR') (case insensitive)
    • Crypto main units: Money('₿2.5'), Money('ETH 10.123456')
    • Crypto sub-units: Money('1000 sat'), Money('50 gwei'), Money('1000000 wei')
    • Negative amounts: Money('-$500'), Money('$-123.45')
    • Sub-unit precision: Money('$100.12345') (preserves exact precision)
  • Money(balance) - Create from AssetAmount object (original constructor)

FixedPoint() - Create fixed-point numbers with ease

  • FixedPoint(str) - Parse decimal string, auto-detect precision (e.g., FixedPoint('123.45'))
  • FixedPoint(percentage) - Parse percentage string, auto-convert to decimal (e.g., FixedPoint('51.5%')0.515)
  • FixedPoint(fixedPoint) - Copy/clone existing FixedPoint object (e.g., FixedPoint(existing))
  • FixedPoint(amount, decimals) - Create from bigint values (e.g., FixedPoint(12345n, 2n))

Rational() - Create rational numbers from strings, bigints, or objects

  • Rational(str) - Parse fraction (e.g., Rational('22/7')) or decimal (e.g., Rational('0.125'))
  • Rational(p, q) - Create from bigint numerator and denominator (e.g., Rational(22n, 7n))
  • Rational(ratio) - Create from Ratio object (e.g., Rational({ p: 1n, q: 3n }))

PriceRange() - Create price ranges with intelligent parsing

  • PriceRange(str) - Parse range strings (e.g., PriceRange('$50 - $100'), PriceRange('$50-100'))
  • PriceRange(min, max) - Create from Money instances or strings (e.g., PriceRange(Money('$50'), '$100'))

Money

Arithmetic Operations (add/subtract accept Money objects or currency strings):

  • add(other) - Add money amounts (same currency)
  • subtract(other) - Subtract money amounts (same currency)
  • multiply(scalar) - Multiply by number, FixedPoint, or string
  • absolute() - Get absolute value
  • negate() - Flip sign (multiply by -1)

Allocation & Distribution:

  • allocate(ratios, options?) - Split proportionally by ratios with optional fractional unit separation
  • distribute(parts, options?) - Split evenly into N parts with optional fractional unit separation
  • concretize() - Split into concrete amount and change

Comparison Methods (accept Money objects or currency strings):

  • compare(other) - Compare values: returns -1 if less, 0 if equal, 1 if greater
  • equals(other) - Check equality
  • lessThan(other) - Less than comparison
  • greaterThan(other) - Greater than comparison
  • lessThanOrEqual(other) - Less than or equal comparison
  • greaterThanOrEqual(other) - Greater than or equal comparison
  • max(other | others[]) - Return maximum value
  • min(other | others[]) - Return minimum value

State Checks:

  • isZero() - Check if amount is zero
  • isPositive() - Check if amount is positive
  • isNegative() - Check if amount is negative
  • hasChange() - Check if has fractional part
  • hasSubUnits() - Check if has sub-units beyond currency precision

Conversion & Formatting:

  • convert(price) - Convert to another currency using price/exchange rate
  • toString(options) - Format for display with locale, precision, and unit options
  • toJSON() - Serialize to JSON
  • fromJSON(json) - Deserialize from JSON

FixedPointNumber

Arithmetic Operations (accepts FixedPoint objects or string arguments):

  • add(other) - Addition
  • subtract(other) - Subtraction
  • multiply(other) - Multiplication
  • divide(other) - Safe division
  • normalize(target) - Change decimal precision

Comparison Methods (accepts FixedPoint objects or string arguments):

  • equals(other) - Equality check
  • greaterThan(other) - Greater than comparison
  • lessThan(other) - Less than comparison
  • max(other | others[]) - Return maximum value
  • min(other | others[]) - Return minimum value

Utility Methods:

  • toString() - DecimalString representation
  • parseString(str, decimals) - Parse from string with explicit decimals
  • fromDecimalString(str) - Parse from DecimalString with auto-detected decimals

RationalNumber

Arithmetic Operations (accepts Ratio objects, fraction strings, or decimal strings):

  • add(other) - Exact addition
  • subtract(other) - Exact subtraction
  • multiply(other) - Exact multiplication
  • divide(other) - Exact division

Comparison Methods (accepts Ratio objects, fraction strings, or decimal strings):

  • equals(other) - Equality check
  • greaterThan(other) - Greater than comparison
  • lessThan(other) - Less than comparison
  • max(other | others[]) - Return maximum value
  • min(other | others[]) - Return minimum value

Utility Methods:

  • simplify() - Reduce to lowest terms
  • toString() - Convert to simplified "p/q" string format
  • toDecimalString(precision?) - Convert to DecimalString (default 50 digits)
  • toFixedPoint() - Convert to decimal (when possible)

Price

  • multiply(scalar | Price) - Scalar multiplication or Price-to-Price multiplication
  • divide(scalar | Price) - Scalar division or Price-to-Price division
  • asRatio() - Convert to RationalNumber ratio
  • invert() - Swap numerator and denominator
  • equals(other) - Check equality (including time for timed prices)
  • toExchangeRate(options?) - Convert to ExchangeRate with configurable precision and base currency selection

ExchangeRate

Constructor Overloads:

  • new ExchangeRate(data) - Create from ExchangeRateData object
  • new ExchangeRate(baseCurrency, quoteCurrency, rate, timestamp?, source?) - Create from individual arguments

Exchange Rate Specific:

  • multiply(scalar | ExchangeRate) - Scalar multiplication or cross-currency rate calculation
  • divide(scalar | ExchangeRate) - Scalar division or rate division
  • invert() - Swap base and quote currencies (1/rate)
  • convert(money) - Convert Money between currencies (automatic direction detection)
  • isStale(thresholdMs) - Check if rate is older than threshold
  • toString(options?) - Format as "rate quote/base" with symbol, code, or ratio formats
  • toJSON() - Serialize to JSON with BigInt string conversion
  • fromJSON(json) - Deserialize from JSON
  • average(rates[]) - Static method to average multiple rates
  • fromPrice(price, options) - Static method to create ExchangeRate from Price with configurable precision

PriceRange

Properties:

  • min - Minimum price (Money instance)
  • max - Maximum price (Money instance)
  • span - Difference between max and min (Money instance)
  • midpoint - Precise midpoint of the range (Money instance)
  • isEmpty - True if min equals max
  • currency - Currency of the range

Range Operations:

  • contains(price) - Check if price is within range (inclusive)
  • isAbove(price) - Check if entire range is above a price
  • isBelow(price) - Check if entire range is below a price
  • overlaps(other) - Check if ranges overlap
  • intersect(other) - Get intersection range (or null)
  • union(other) - Get union range
  • split(parts) - Split into N equal parts

Conversion & Formatting:

  • convert(exchangeRate) - Convert to different currency
  • toString(options?) - Format for display with multiple format styles
  • toJSON(options?) - Serialize to JSON
  • fromJSON(json) - Deserialize from JSON (static)
  • equals(other) - Check equality

Static Factory Methods:

  • under(max) - Create range from zero to max
  • over(min, max) - Create range from min to max
  • between(min, max) - Alias for constructor
  • around(basePrice, percentage) - Create range around price with margin
  • createBuckets(min, max, count) - Create N equal price buckets

Comparison with dinero.js

Featurecentdinero.js
PrecisionArbitrary (BigInt)Limited (Number)
Max ValueUnlimited~9 quadrillion
Crypto SupportNative (8-18 decimals)Limited
Allocation/DistributionAdvanced with fractional unit separationBasic
Exact DivisionGuaranteed*No
Type SafetyFull TypeScriptPartial
ImmutabilityYesYes
PerformanceExcellentGood

*For divisors composed of factors 2 and 5 only

About

Arbitrary-precision currency library for TypeScript/Javascript

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages