# Math in JavaScript and DeFi

Canonical: https://gaboesquivel.com/blog/2025-01-math-js-defi

**Published:** 2025-01-19
**Summary:** Floating-point quirks, precision, and BigInt choices I use when JavaScript math has to hold up in DeFi contracts and onchain apps.
**Categories:** Web3, DeFi, Backend, Fullstack
**Technologies:** JavaScript, TypeScript, Ethereum, Solidity

Math in [JavaScript](https://gaboesquivel.com/tech/javascript) becomes a real constraint in [DeFi](https://gaboesquivel.com/web3), 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:

```js
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.3 - 0.1); // 0.19999999999999998
```

### Why 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

```js
const tokenDecimals = 18;
const rawBalance = BigInt("1000000000000000000"); // 1 ETH in wei
const formattedBalance = Number(rawBalance) / 10 ** tokenDecimals;
console.log(formattedBalance); // 1.0
```

Converting 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.js` is smaller and faster when you only need basic decimal math.
- `decimal.js` has more capabilities when you need advanced math functions.
- Native **`BigInt`** is enough when you only work with large integers.

### Safe arithmetic with `bignumber.js`

```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](https://gaboesquivel.com/tech/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`:

```ts
import { formatUnits, parseUnits } from "viem";

const amount = parseUnits("1.5", 18); // 1500000000000000000n
const display = formatUnits(amount, 18); // "1.5"
```