Skip to main content

Calculations with JavaScript - Why Not Use Native Operations?

· 5 min read
Bruno Carneiro
Fundador da @TautornTech

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.1 in 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.3 returns false
  • 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.

References​