Math in JavaScript becomes a real constraint in DeFi, where floating-point quirks, precision errors, and integers that don't fit in Number all show up in token balances. I handle it with bignumber.js, native BigInt, and viem utilities, depending on the kind of number.

Floating-point math in JavaScript
JavaScript uses IEEE 754 floating-point arithmetic, which produces surprising results for ordinary decimal operations:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.3 - 0.1); // 0.19999999999999998Why rounding errors matter in DeFi
DeFi applications need precise calculations for token balances, staking rewards, and interest rates, and a small rounding error repeated across many transactions becomes a discrepancy someone can see in their balance or exploit.
Fixed decimals in token amounts
Most ERC-20 tokens and DeFi protocols use fixed decimal places instead of floating-point numbers. Balances are stored in their smallest unit, such as wei for ETH or satoshis for BTC, and formatted for people only at display time.
Converting token amounts
const tokenDecimals = 18;
const rawBalance = BigInt("1000000000000000000"); // 1 ETH in wei
const formattedBalance = Number(rawBalance) / 10 ** tokenDecimals;
console.log(formattedBalance); // 1.0Converting through Number works for this value, but it reintroduces floating-point error as soon as the amounts get large or fractional. A big number library like bignumber.js keeps decimal math consistent and readable when the amounts are money.
Why I use bignumber.js
It covers what decimal money math needs:
- Arbitrary precision avoids floating-point errors.
- Performance is better than alternatives like
decimal.js. - Size is smaller than the other full-featured libraries.
- Maintenance is active and the library is widely used.
When an alternative fits better
Other libraries win in narrower cases:
big.jsis smaller and faster when you only need basic decimal math.decimal.jshas more capabilities when you need advanced math functions.- Native
BigIntis enough when you only work with large integers.
Safe arithmetic with bignumber.js
import BigNumber from "bignumber.js";
const a = new BigNumber("0.1");
const b = new BigNumber("0.2");
const sum = a.plus(b);
console.log(sum.toString()); // "0.3"The result is exact because bignumber.js works in decimal strings instead of binary floating point.
EVM amounts with viem utilities
For Ethereum transactions I keep amounts as bigint end to end and use viem's parseUnits and formatUnits at the edges, so conversion between human input and the token's smallest unit happens once, without passing through Number:
import { formatUnits, parseUnits } from "viem";
const amount = parseUnits("1.5", 18); // 1500000000000000000n
const display = formatUnits(amount, 18); // "1.5"