The Precision Problem with Floating-Point Numbers
TypescriptGo

The Precision Problem with Floating-Point Numbers


Floating-point numbers are typically represented using the IEEE 754 standard. In this format, a floating-point number is divided into three parts: a sign bit, an exponent, and a fraction (or significant). This representation allows a wide range of values, but not all decimal numbers can be expressed exactly in binary.

For example, the decimal number 0.1 cannot be precisely represented as a binary floating-point number. Instead, it is stored as an approximation:

0.1 (decimal) ≈ 0.0001100110011001100110011001100110011...(binary repeating)

This repeating binary pattern is truncated to fit into the finite bits allocated by the IEEE 754 format (e.g., 64 bits for double). As a result, the stored value is slightly off from the exact value of 0.1, introducing a rounding error.

Below is a simplified diagram of a 64-bit IEEE 754 floating-point number:

| Sign (1 bit) | Exponent (11 bits) | Fraction (52 bits) |

These small inaccuracies become apparent when performing arithmetic operations, especially when they accumulate over many calculations.

Alternatives

To avoid precision issues caused by floating-point arithmetic, consider the following common alternatives:

  • Use integers to represent fixed-point values: store it as an integer. For example, store $10.23 as 1023 cents. BigInts and int64 are helpful in this approach.

  • Use third-party libraries that implement decimal arithmetic:

    • JavaScript: Use Decimal.js or Dinero.js for precise calculations.

    • Python: Use the built-in decimal.Decimal module for accurate decimal handling.

    • Go: Use shopspring/decimal as alternative.

  • Use fixed-point decimal types provided by some languages: Some languages offer native support for fixed-point decimal types that ensure precision in decimal arithmetic.

    • C# / .NET: Use the decimal type, which is designed specifically for financial and monetary calculations.
  • Use precise types in databases: When storing monetary or precision-sensitive data in a database, use DECIMAL(p, s) or NUMERIC(p, s) types instead of floating-point types like FLOAT or DOUBLE. These types maintain exact decimal representations and are supported in PostgreSQL, MySQL, and other relational databases.