Currency Conversion

Exchange-rate math — conversions, cross-rates, multi-currency totals — without floating-point drift.

Currency Conversion

Exchange rates are decimals, and money is precision-sensitive — exactly where native floating-point bites. Pass amounts and rates as strings and let @wzo/calc keep every cent exact.

Basic Conversion

// USD → CNY at 7.2345
const 
usd
= 250.75
const
rate
= 7.2345
calc
(`${
usd
} * ${
rate
}`, {
_fmt
: {
decimals
: 2,
thousands
: true,
rounding
: 'round' } })
// 250.75 * 7.2345 = 1814.050875 → "1,814.05"

Why It Matters

A single conversion can already drift in native JS:

19.99 * 1.1 // 21.989000000000004  ← native float
fmt
('19.99 * 1.1', {
decimals
: 2,
rounding
: 'round' }) // "21.99" ✅ exact

Cross-Rate via a Base Currency

No direct EUR→JPY rate? Compose through USD by chaining the two rates:

// EUR → JPY via USD: ×1.08 (EUR→USD), ×150 (USD→JPY)
const 
eur
= 500
chainMul
(
eur
, 1.08, 150)({
decimals
: 0,
thousands
: true })
// 500 * 1.08 * 150 = "81,000"

Multi-Currency Portfolio

Convert each holding to a common currency, then total with calcSum (high-precision, no large-number drift):

const 
holdings
= [
{
ccy
: 'EUR',
amount
: 1200,
toUsd
: 1.08 },
{
ccy
: 'GBP',
amount
: 800,
toUsd
: 1.27 },
{
ccy
: 'JPY',
amount
: 150000,
toUsd
: 0.0067 },
] // Value each holding in USD (string-returning, exact) const
inUsd
=
holdings
.
map
(
h
=> ({
...
h
,
usd
:
mulStr
(
String
(
h
.
amount
),
String
(
h
.
toUsd
)),
})) // EUR 1296 · GBP 1016 · JPY 1005
fmt
(
calcSum
('usd',
inUsd
), {
decimals
: 2,
thousands
: true }) // "3,317.00"

Inverse Rate

The reverse rate is a repeating decimal — control how many places you keep:

// USD→CNY is 7.2345, so CNY→USD = 1 / 7.2345
rawDiv
('1', '7.2345', 6) // "0.138227"
Always pass rates and amounts as strings ('7.2345', not 7.2345 after arithmetic). A rate written as a number literal is fine, but a rate produced by an earlier floating-point calculation is already corrupted before it reaches the library — see Caveats.