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

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
retornafalse
- 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.