Skip to main content

Cálculos com JavaScript - Por que não usar operações nativas?

· 5 min read
Bruno Carneiro
Fundador da @TautornTech

Muitos desenvolvedores JavaScript cometem um erro comum: fazer cálculos matemáticos diretamente com as operações nativas da linguagem. Isso pode causar problemas sérios de precisão, especialmente em aplicações financeiras ou científicas.

Por que JavaScript não é Bom para Cálculos?

JavaScript usa o padrão IEEE 754 para números de ponto flutuante, que pode causar imprecisões em operações matemáticas simples. Este padrão representa números decimais em binário, o que gera problemas de arredondamento.

O Problema Técnico

O IEEE 754 representa números decimais como frações binárias. Muitos números decimais simples (como 0.1) não podem ser representados exatamente em binário, causando erros de precisão.

Por exemplo:

  • 0.1 em binário é uma dízima periódica: 0.0001100110011001100110011...
  • O JavaScript trunca essa representação, causando imprecisões

Impacto em Aplicações

  • Aplicações financeiras: Erros de centavos podem se acumular
  • Cálculos científicos: Precisão é crítica
  • Comparações: 0.1 + 0.2 === 0.3 retorna false
  • Arredondamentos: Resultados inesperados em operações simples

Referência: IEEE 754 Standard - Padrão internacional para aritmética de ponto flutuante

Exemplos de Problemas

// Problema 1: Soma simples
console.log(0.1 + 0.2); // 0.30000000000000004 (não 0.3!)

// Problema 2: Multiplicação
console.log(0.1 * 3); // 0.30000000000000004

// Problema 3: Cálculos financeiros
const preco = 19.90;
const quantidade = 3;
const total = preco * quantidade;
console.log(total); // 59.699999999999996 (deveria ser 59.70)

// Problema 4: Comparações
console.log(0.1 + 0.2 === 0.3); // false!

Soluções: Bibliotecas Especializadas

1. BigNumber.js

A biblioteca mais popular para cálculos precisos em JavaScript.

Instalação

npm install bignumber.js

Uso Básico

import BigNumber from 'bignumber.js';

// Configurar precisão decimal
BigNumber.config({ DECIMAL_PLACES: 2 });

// Exemplos de uso
const a = new BigNumber(0.1);
const b = new BigNumber(0.2);
const soma = a.plus(b);
console.log(soma.toString()); // "0.3"

// Cálculos financeiros
const preco = new BigNumber(19.90);
const quantidade = new BigNumber(3);
const total = preco.times(quantidade);
console.log(total.toString()); // "59.7"

// Comparações precisas
const resultado = new BigNumber(0.1).plus(0.2);
console.log(resultado.equals(0.3)); // true

Operações Disponíveis

const num1 = new BigNumber(10);
const num2 = new BigNumber(3);

// Operações básicas
console.log(num1.plus(num2).toString()); // "13" (soma)
console.log(num1.minus(num2).toString()); // "7" (subtração)
console.log(num1.times(num2).toString()); // "30" (multiplicação)
console.log(num1.div(num2).toString()); // "3.3333333333333333333" (divisão)

// Operações avançadas
console.log(num1.pow(2).toString()); // "100" (potência)
console.log(num1.sqrt().toString()); // "3.1622776601683793320" (raiz quadrada)
console.log(num1.mod(num2).toString()); // "1" (módulo)

2. Math.js

Biblioteca mais completa para matemática computacional.

Instalação

npm install mathjs

Uso Básico

import { create, all } from 'mathjs';

const math = create(all);

// Configurar precisão
math.config({
number: 'BigNumber',
precision: 64
});

// Exemplos
const resultado = math.evaluate('0.1 + 0.2');
console.log(resultado.toString()); // "0.3"

// Expressões complexas
const calculo = math.evaluate('(19.90 * 3) + (5.50 * 2)');
console.log(calculo.toString()); // "70.7"

3. Decimal.js

Alternativa mais leve ao BigNumber.js.

Instalação

npm install decimal.js

Uso

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"

Quando Usar Cada Biblioteca

BigNumber.js

  • ✅ Cálculos financeiros
  • ✅ Precisão decimal controlada
  • ✅ API simples e intuitiva
  • ✅ Boa performance

Math.js

  • ✅ Expressões matemáticas complexas
  • ✅ Múltiplos tipos de dados
  • ✅ Funções matemáticas avançadas
  • ❌ Mais pesada

Decimal.js

  • ✅ Alternativa leve ao BigNumber.js
  • ✅ API similar
  • ✅ Boa performance
  • ❌ Menos recursos

Utilitário Singleton para Cálculos

Para evitar repetir código e garantir consistência, é recomendado criar um utilitário singleton que centralize todas as operações matemáticas.

Implementação do Singleton

import BigNumber from 'bignumber.js';

class MathUtils {
constructor() {
if (MathUtils.instance) {
return MathUtils.instance;
}

// Configurar BigNumber.js
BigNumber.config({
DECIMAL_PLACES: 2,
ROUNDING_MODE: BigNumber.ROUND_HALF_UP
});

this.BigNumber = BigNumber;
MathUtils.instance = this;
}

// Operações básicas
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();
}

// Operações financeiras
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();
}

// Comparações
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);
}

// Formatação
formatCurrency(value, currency = 'BRL') {
const formatted = new BigNumber(value).toFixed(2);
return new Intl.NumberFormat('pt-BR', {
style: 'currency',
currency: currency
}).format(formatted);
}

// Validação
isValidNumber(value) {
return new BigNumber(value).isFinite();
}
}

// Exportar instância única
export default new MathUtils();

Uso do Utilitário

import MathUtils from './utils/MathUtils.js';

// Operações básicas
console.log(MathUtils.add(0.1, 0.2)); // "0.3"
console.log(MathUtils.multiply(19.90, 3)); // "59.7"

// Cálculos financeiros
const preco = 100;
const desconto = MathUtils.calculateDiscount(preco, 10);
console.log(desconto); // "90"

const comTaxa = MathUtils.calculateTax(preco, 5);
console.log(comTaxa); // "105"

// Formatação
console.log(MathUtils.formatCurrency(59.7)); // "R$ 59,70"

// Comparações
console.log(MathUtils.isEqual(0.1 + 0.2, 0.3)); // true
console.log(MathUtils.isGreaterThan(10, 5)); // true

Vantagens do Singleton

  • Consistência: Mesma configuração em toda aplicação
  • Reutilização: Não precisa importar BigNumber.js em cada arquivo
  • Manutenção: Mudanças centralizadas
  • Performance: Uma única instância configurada
  • API Limpa: Métodos com nomes descritivos

Exemplo Prático: Calculadora de Preços

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()
};
}
}

// Uso
const calc = new CalculadoraPrecos();
const resultado = calc.calcularTotal(19.90, 3, 10);
console.log(resultado);
// {
// subtotal: "59.7",
// desconto: "5.97",
// total: "53.73"
// }

Conclusão

Nunca faça cálculos financeiros ou que precisem de precisão usando as operações nativas do JavaScript. Sempre use bibliotecas especializadas como:

  • BigNumber.js - Para cálculos financeiros e precisão decimal
  • Math.js - Para expressões matemáticas complexas
  • Decimal.js - Alternativa leve ao BigNumber.js

Essas bibliotecas garantem precisão e evitam os problemas comuns de arredondamento que podem causar bugs sérios em produção.

Referências