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