Calculations with JavaScript - Why Not Use Native Operations?

Many JavaScript developers make a common mistake: doing mathematical calculations directly with the language's native operations. This can cause serious precision problems, especially in financial or scientific applications.
Why Is JavaScript Not Good for Calculations?
JavaScript uses the IEEE 754 standard for floating-point numbers, which can cause imprecision in simple mathematical operations. This standard represents decimal numbers in binary, which generates rounding problems.
The Technical Problem
IEEE 754 represents decimal numbers as binary fractions. Many simple decimal numbers (like 0.1) cannot be represented exactly in binary, causing precision errors.
For example:
0.1in binary is a repeating decimal:0.0001100110011001100110011...- JavaScript truncates this representation, causing imprecision
Impact on Applications
- Financial applications: Cent-level errors can accumulate
- Scientific calculations: Precision is critical
- Comparisons:
0.1 + 0.2 === 0.3returnsfalse - Rounding: Unexpected results in simple operations
Reference: IEEE 754 Standard - International standard for floating-point arithmetic
Problem Examples
// Problem 1: Simple addition
console.log(0.1 + 0.2); // 0.30000000000000004 (not 0.3!)
// Problem 2: Multiplication
console.log(0.1 * 3); // 0.30000000000000004
// Problem 3: Financial calculations
const preco = 19.90;
const quantidade = 3;
const total = preco * quantidade;
console.log(total); // 59.699999999999996 (should be 59.70)
// Problem 4: Comparisons
console.log(0.1 + 0.2 === 0.3); // false!
Solutions: Specialized Libraries
1. BigNumber.js
The most popular library for precise calculations in JavaScript.
Installation
npm install bignumber.js
Basic Usage
import BigNumber from 'bignumber.js';
// Configure decimal precision
BigNumber.config({ DECIMAL_PLACES: 2 });
// Usage examples
const a = new BigNumber(0.1);
const b = new BigNumber(0.2);
const soma = a.plus(b);
console.log(soma.toString()); // "0.3"
// Financial calculations
const preco = new BigNumber(19.90);
const quantidade = new BigNumber(3);
const total = preco.times(quantidade);
console.log(total.toString()); // "59.7"
// Precise comparisons
const resultado = new BigNumber(0.1).plus(0.2);
console.log(resultado.equals(0.3)); // true
Available Operations
const num1 = new BigNumber(10);
const num2 = new BigNumber(3);
// Basic operations
console.log(num1.plus(num2).toString()); // "13" (addition)
console.log(num1.minus(num2).toString()); // "7" (subtraction)
console.log(num1.times(num2).toString()); // "30" (multiplication)
console.log(num1.div(num2).toString()); // "3.3333333333333333333" (division)
// Advanced operations
console.log(num1.pow(2).toString()); // "100" (power)
console.log(num1.sqrt().toString()); // "3.1622776601683793320" (square root)
console.log(num1.mod(num2).toString()); // "1" (modulo)
2. Math.js
A more complete library for computational mathematics.
Installation
npm install mathjs
Basic Usage
import { create, all } from 'mathjs';
const math = create(all);
// Configure precision
math.config({
number: 'BigNumber',
precision: 64
});
// Examples
const resultado = math.evaluate('0.1 + 0.2');
console.log(resultado.toString()); // "0.3"
// Complex expressions
const calculo = math.evaluate('(19.90 * 3) + (5.50 * 2)');
console.log(calculo.toString()); // "70.7"
3. Decimal.js
A lighter alternative to BigNumber.js.
Installation
npm install decimal.js
Usage
import Decimal from 'decimal.js';
const a = new Decimal(0.1);
const b = new Decimal(0.2);
const soma = a.plus(b);
console.log(soma.toString()); // "0.3"
When to Use Each Library
BigNumber.js
- ✅ Financial calculations
- ✅ Controlled decimal precision
- ✅ Simple and intuitive API
- ✅ Good performance
Math.js
- ✅ Complex mathematical expressions
- ✅ Multiple data types
- ✅ Advanced mathematical functions
- ❌ Heavier
Decimal.js
- ✅ Lighter alternative to BigNumber.js
- ✅ Similar API
- ✅ Good performance
- ❌ Fewer features
Singleton Utility for Calculations
To avoid repeating code and ensure consistency, it is recommended to create a singleton utility that centralizes all mathematical operations.
Singleton Implementation
import BigNumber from 'bignumber.js';
class MathUtils {
constructor() {
if (MathUtils.instance) {
return MathUtils.instance;
}
// Configure BigNumber.js
BigNumber.config({
DECIMAL_PLACES: 2,
ROUNDING_MODE: BigNumber.ROUND_HALF_UP
});
this.BigNumber = BigNumber;
MathUtils.instance = this;
}
// Basic operations
add(a, b) {
return new BigNumber(a).plus(b).toString();
}
subtract(a, b) {
return new BigNumber(a).minus(b).toString();
}
multiply(a, b) {
return new BigNumber(a).times(b).toString();
}
divide(a, b) {
return new BigNumber(a).div(b).toString();
}
// Financial operations
calculatePercentage(value, percentage) {
return new BigNumber(value).times(percentage).div(100).toString();
}
calculateDiscount(price, discountPercentage) {
const discount = this.calculatePercentage(price, discountPercentage);
return new BigNumber(price).minus(discount).toString();
}
calculateTax(price, taxPercentage) {
const tax = this.calculatePercentage(price, taxPercentage);
return new BigNumber(price).plus(tax).toString();
}
// Comparisons
isEqual(a, b) {
return new BigNumber(a).equals(b);
}
isGreaterThan(a, b) {
return new BigNumber(a).isGreaterThan(b);
}
isLessThan(a, b) {
return new BigNumber(a).isLessThan(b);
}
// Formatting
formatCurrency(value, currency = 'BRL') {
const formatted = new BigNumber(value).toFixed(2);
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: currency
}).format(formatted);
}
// Validation
isValidNumber(value) {
return new BigNumber(value).isFinite();
}
}
// Export single instance
export default new MathUtils();
Using the Utility
import MathUtils from './utils/MathUtils.js';
// Basic operations
console.log(MathUtils.add(0.1, 0.2)); // "0.3"
console.log(MathUtils.multiply(19.90, 3)); // "59.7"
// Financial calculations
const preco = 100;
const desconto = MathUtils.calculateDiscount(preco, 10);
console.log(desconto); // "90"
const comTaxa = MathUtils.calculateTax(preco, 5);
console.log(comTaxa); // "105"
// Formatting
console.log(MathUtils.formatCurrency(59.7)); // "R$ 59,70"
// Comparisons
console.log(MathUtils.isEqual(0.1 + 0.2, 0.3)); // true
console.log(MathUtils.isGreaterThan(10, 5)); // true
Singleton Advantages
- ✅ Consistency: Same configuration throughout the application
- ✅ Reusability: No need to import BigNumber.js in every file
- ✅ Maintainability: Centralized changes
- ✅ Performance: A single configured instance
- ✅ Clean API: Methods with descriptive names
Practical Example: Price Calculator
import BigNumber from 'bignumber.js';
class CalculadoraPrecos {
constructor() {
BigNumber.config({ DECIMAL_PLACES: 2 });
}
calcularTotal(preco, quantidade, desconto = 0) {
const precoBN = new BigNumber(preco);
const quantidadeBN = new BigNumber(quantidade);
const descontoBN = new BigNumber(desconto);
const subtotal = precoBN.times(quantidadeBN);
const valorDesconto = subtotal.times(descontoBN.div(100));
const total = subtotal.minus(valorDesconto);
return {
subtotal: subtotal.toString(),
desconto: valorDesconto.toString(),
total: total.toString()
};
}
}
// Usage
const calc = new CalculadoraPrecos();
const resultado = calc.calcularTotal(19.90, 3, 10);
console.log(resultado);
// {
// subtotal: "59.7",
// desconto: "5.97",
// total: "53.73"
// }
Conclusion
Never do financial calculations or precision-sensitive calculations using JavaScript's native operations. Always use specialized libraries such as:
- BigNumber.js - For financial calculations and decimal precision
- Math.js - For complex mathematical expressions
- Decimal.js - Lighter alternative to BigNumber.js
These libraries ensure precision and avoid the common rounding problems that can cause serious bugs in production.
