Mathematics in JavaScript can be tricky, especially when dealing with financial applications like DeFi. Precision errors, floating-point quirks, and handling large numbers are all challenges developers must address. In this post, we'll cover key math issues in JavaScript, best practices for handling numbers in DeFi, and tools like bignumber.js
, BigInt
, and viem
utilities.
The Basics: Floating-Point Math in JavaScript
JavaScript uses IEEE 754 floating-point arithmetic, which introduces quirks when performing arithmetic operations. This can lead to unexpected results:
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.3 - 0.1); // 0.19999999999999998
Why Is This a Problem in DeFi?
DeFi applications require precise calculations, especially when dealing with token balances, staking rewards, and interest rates. Small precision errors can lead to significant discrepancies in financial transactions.
Handling Precision: Decimals in DeFi
Most ERC-20 tokens and DeFi protocols use fixed decimal places instead of floating-point numbers. Token balances are usually represented in their smallest unit (e.g., wei for ETH, satoshis for BTC) and later formatted for human readability.
Example: Converting Token Amounts
const tokenDecimals = 18;
const rawBalance = BigInt("1000000000000000000"); // 1 ETH in wei
const formattedBalance = Number(rawBalance) / 10 ** tokenDecimals;
console.log(formattedBalance); // 1.0
While using JavaScript's Number
type for such conversions works, it is prone to floating-point precision errors. A better approach is to use a Big Number library like bignumber.js
, which ensures robustness, consistency, and improved readability when dealing with precise financial calculations.
Why bignumber.js
?
- Arbitrary precision: Prevents floating-point errors.
- Performance optimized: Faster than alternatives like
decimal.js
. - Compact: Smaller than other libraries.
- Well-maintained: Reliable and actively supported.
When to Consider Alternatives:
- If only basic decimal math is needed,
big.js
is smaller and faster. - For advanced math functions,
decimal.js
offers more capabilities. - If working with large integers only, native
BigInt
is a viable alternative.
Example: 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"
Using bignumber.js
ensures predictable behavior and avoids floating-point inaccuracies.
Math on EVM: Using Viem's Utilities
When working with Ethereum Virtual Machine (EVM) transactions, Viem provides utilities for handling numeric conversions: