Floating Point Comparison with Incorrect Operator

Description

Floating Point Comparison with Incorrect Operator occurs when numeric calculations using floating-point values generate imprecise results due to rounding errors, and comparisons are performed using exact equality operators. Floating-point numbers have limited precision, and operations like addition, subtraction, multiplication, and division can introduce small errors. When two mathematically equal floating-point numbers have slightly different bit representations due to these errors, exact equality comparisons (==, !=) produce unexpected and incorrect results.

Risk

While primarily a correctness issue, floating-point comparison errors have security implications. Financial calculations with rounding errors can lead to incorrect charges or credits. Access control decisions based on thresholds may fail. Cryptographic operations using floating-point (though rare) could be compromised. Loop termination conditions may never be met, causing infinite loops and DoS. Boundary checks may fail, allowing out-of-bounds access. Game mechanics or gambling systems can be exploited. Scientific or safety-critical calculations may produce dangerous results.

Solution

Use epsilon-based comparison for floating-point equality checks. Compare the absolute difference to a small tolerance value. Use exact arithmetic types (Decimal, BigDecimal) for financial calculations. Consider integer arithmetic scaled appropriately. Use language-specific utilities for floating-point comparison (e.g., Math.ulp in Java). Be aware of accumulated errors in loops. Document the precision requirements of calculations. Use static analysis tools that detect floating-point comparison issues. Consider fixed-point arithmetic for embedded systems.

Common Consequences

ImpactDetails
OtherScope: Other

Reduce Reliability - Floating-point comparison errors cause incorrect program behavior.
AvailabilityScope: Availability

DoS: Infinite Loop - Loop termination based on equality may never occur due to precision errors.
IntegrityScope: Integrity

Unexpected State - Program state may be incorrect due to failed comparisons.

Example Code

Vulnerable Code

// Vulnerable: Direct equality comparison of floating-point values
public class VulnerableFinancialCalculator {

    // Vulnerable: Exact equality check
    public boolean hasZeroBalance(double balance) {
        // This may return false even for very small values like 0.0000000001
        return balance == 0.0;
    }

    // Vulnerable: Floating-point loop termination
    public double sumRange(double start, double end, double step) {
        double sum = 0.0;
        double current = start;

        // Vulnerable: May never terminate due to floating-point errors
        while (current != end) {  // Exact equality check!
            sum += current;
            current += step;
        }

        return sum;  // May run forever if current never exactly equals end
    }

    // Vulnerable: Threshold comparison
    public boolean isDiscountEligible(double purchaseAmount) {
        double threshold = 100.0;
        double discount = purchaseAmount * 0.1;  // 10% discount

        // Vulnerable: May fail due to floating-point precision
        // 99.9 * 0.1 might not exactly equal 9.99
        return discount == 9.99;
    }

    // Vulnerable: Financial calculation
    public void processTransaction(double amount) {
        double tax = amount * 0.07;
        double total = amount + tax;

        // Vulnerable: May fail even for correct amounts
        if (total != 107.0 && amount == 100.0) {
            throw new IllegalStateException("Calculation error");
        }
    }
}
# Vulnerable: Floating-point comparison errors in Python
class VulnerableCalculator:

    def check_result(self, a, b, expected):
        """Vulnerable: Direct equality check."""
        result = a + b
        # This fails: 0.1 + 0.2 == 0.3 returns False!
        if result == expected:
            return True
        return False

    def iterate_range(self, start, end, step):
        """Vulnerable: Floating-point loop termination."""
        values = []
        current = start

        # Vulnerable: May never terminate
        while current != end:  # Exact equality!
            values.append(current)
            current += step
            # 0.1 + 0.1 + 0.1 ... may never exactly equal 1.0

        return values

    def calculate_discount(self, price, discount_rate):
        """Vulnerable: Exact comparison for discounts."""
        discount = price * discount_rate

        # Vulnerable: 20.0 * 0.15 might not exactly equal 3.0
        if discount == 3.0:
            return "Standard discount"
        elif discount == 5.0:
            return "Premium discount"
        else:
            return "No matching discount"

    def verify_split(self, total, parts):
        """Vulnerable: Verifying equal split."""
        share = total / parts

        # Vulnerable: Sum of shares may not equal total
        reconstructed = share * parts
        if reconstructed == total:
            return True
        return False  # Often returns False!


# Demonstration of the problem:
# >>> 0.1 + 0.2
# 0.30000000000000004
# >>> 0.1 + 0.2 == 0.3
# False
// Vulnerable: C floating-point comparison
#include <stdio.h>

// Vulnerable: Exact equality for termination
int vulnerable_count_steps(float start, float end, float step) {
    int count = 0;
    float current = start;

    // Vulnerable: May never terminate
    while (current != end) {
        count++;
        current += step;

        // Safety check to prevent infinite loop
        if (count > 1000000) {
            printf("Warning: exceeded iteration limit\n");
            break;
        }
    }

    return count;
}

// Vulnerable: Access control based on floating-point
int vulnerable_check_access(float user_level, float required_level) {
    // Vulnerable: Exact comparison
    if (user_level == required_level) {
        return 1;  // Grant access
    }
    return 0;  // Deny access
}

// Vulnerable: Financial calculation
void vulnerable_calculate_interest(double principal, double rate) {
    double interest = principal * rate;
    double total = principal + interest;

    // Vulnerable: May fail due to precision
    if (principal == 1000.0 && rate == 0.05) {
        if (total != 1050.0) {  // Might not be exactly 1050.0
            printf("Calculation error!\n");
        }
    }
}
// Vulnerable: C# floating-point comparisons
public class VulnerablePriceCalculator
{
    // Vulnerable: Direct equality
    public bool AreEqual(double a, double b)
    {
        return a == b;  // May fail for mathematically equal values
    }

    // Vulnerable: Loop with floating-point termination
    public List<double> GenerateSequence(double start, double end, double step)
    {
        var sequence = new List<double>();
        double current = start;

        // Vulnerable: May not terminate
        while (current != end)
        {
            sequence.Add(current);
            current += step;
        }

        return sequence;
    }

    // Vulnerable: Price comparison
    public bool IsPriceValid(decimal price, decimal expected)
    {
        // Even decimal can have precision issues in some calculations
        return price == expected;
    }

    // Vulnerable: Boundary check
    public bool IsWithinBudget(double spending, double budget)
    {
        // Vulnerable: After many calculations, spending might be
        // 99.99999999999999 instead of 100.0
        return spending == budget || spending < budget;
    }
}

Fixed Code

// Fixed: Proper floating-point comparison
public class FixedFinancialCalculator {

    private static final double EPSILON = 1e-9;  // Tolerance for comparison

    // Fixed: Epsilon-based comparison
    public boolean hasZeroBalance(double balance) {
        return Math.abs(balance) < EPSILON;
    }

    // Fixed: Compare with tolerance
    public static boolean approximatelyEqual(double a, double b, double epsilon) {
        return Math.abs(a - b) < epsilon;
    }

    // Fixed: Relative epsilon for larger values
    public static boolean relativelyEqual(double a, double b, double relativeEpsilon) {
        double diff = Math.abs(a - b);
        double max = Math.max(Math.abs(a), Math.abs(b));

        if (max == 0) {
            return diff < relativeEpsilon;
        }

        return (diff / max) < relativeEpsilon;
    }

    // Fixed: Loop with proper termination
    public double sumRange(double start, double end, double step) {
        double sum = 0.0;
        double current = start;

        // Fixed: Use less-than comparison with epsilon
        while (current < end - EPSILON) {
            sum += current;
            current += step;
        }

        // Fixed: Alternative - use integer counting
        // int steps = (int) Math.round((end - start) / step);
        // for (int i = 0; i < steps; i++) { ... }

        return sum;
    }

    // Fixed: Use BigDecimal for financial calculations
    public boolean isDiscountEligible(double purchaseAmount) {
        BigDecimal amount = BigDecimal.valueOf(purchaseAmount);
        BigDecimal discountRate = new BigDecimal("0.1");
        BigDecimal expected = new BigDecimal("9.99");

        BigDecimal discount = amount.multiply(discountRate)
            .setScale(2, RoundingMode.HALF_UP);

        return discount.compareTo(expected) == 0;
    }

    // Fixed: Financial calculations with BigDecimal
    public void processTransaction(BigDecimal amount) {
        BigDecimal taxRate = new BigDecimal("0.07");
        BigDecimal tax = amount.multiply(taxRate)
            .setScale(2, RoundingMode.HALF_UP);
        BigDecimal total = amount.add(tax);

        BigDecimal expectedTotal = new BigDecimal("107.00");
        BigDecimal expectedAmount = new BigDecimal("100.00");

        if (amount.compareTo(expectedAmount) == 0 &&
            total.compareTo(expectedTotal) != 0) {
            throw new IllegalStateException("Calculation error");
        }
    }
}
# Fixed: Proper floating-point comparison in Python
import math
from decimal import Decimal, ROUND_HALF_UP


class FixedCalculator:

    EPSILON = 1e-9  # Default tolerance

    def approximately_equal(self, a: float, b: float,
                           epsilon: float = EPSILON) -> bool:
        """Compare floats with tolerance."""
        return abs(a - b) < epsilon

    def relatively_equal(self, a: float, b: float,
                        rel_tol: float = 1e-9) -> bool:
        """Compare floats with relative tolerance."""
        # Use Python's built-in for this
        return math.isclose(a, b, rel_tol=rel_tol, abs_tol=1e-12)

    def check_result(self, a: float, b: float, expected: float) -> bool:
        """Fixed: Use math.isclose for comparison."""
        result = a + b
        return math.isclose(result, expected, rel_tol=1e-9)

    def iterate_range(self, start: float, end: float, step: float) -> list:
        """Fixed: Integer-based iteration."""
        # Calculate number of steps
        steps = int(round((end - start) / step))
        values = []

        for i in range(steps):
            value = start + i * step
            values.append(value)

        return values

    def calculate_discount_fixed(self, price: float,
                                 discount_rate: float) -> str:
        """Fixed: Use Decimal for financial calculations."""
        price_d = Decimal(str(price))
        rate_d = Decimal(str(discount_rate))

        discount = (price_d * rate_d).quantize(
            Decimal('0.01'),
            rounding=ROUND_HALF_UP
        )

        if discount == Decimal('3.00'):
            return "Standard discount"
        elif discount == Decimal('5.00'):
            return "Premium discount"
        else:
            return f"Discount: {discount}"

    def verify_split(self, total: float, parts: int) -> bool:
        """Fixed: Use epsilon comparison."""
        share = total / parts
        reconstructed = share * parts
        return math.isclose(reconstructed, total, rel_tol=1e-9)


# Usage demonstration:
calc = FixedCalculator()
print(calc.check_result(0.1, 0.2, 0.3))  # True (was False before!)
// Fixed: C floating-point comparison with epsilon
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include <float.h>

#define EPSILON 1e-9

// Fixed: Epsilon-based equality
bool approximately_equal(double a, double b, double epsilon) {
    return fabs(a - b) < epsilon;
}

// Fixed: Relative epsilon for varying magnitudes
bool relatively_equal(double a, double b, double rel_epsilon) {
    double diff = fabs(a - b);
    double largest = fmax(fabs(a), fabs(b));

    if (largest == 0.0) {
        return diff < rel_epsilon;
    }

    return (diff / largest) < rel_epsilon;
}

// Fixed: Proper loop termination
int fixed_count_steps(float start, float end, float step) {
    // Calculate steps using integer
    int expected_steps = (int)roundf((end - start) / step);
    return expected_steps;
}

// Alternative: Less-than comparison
double fixed_sum_range(double start, double end, double step) {
    double sum = 0.0;
    double current = start;

    // Fixed: Use less-than with epsilon buffer
    while (current < end - EPSILON) {
        sum += current;
        current += step;
    }

    return sum;
}

// Fixed: Access control with tolerance
int fixed_check_access(float user_level, float required_level) {
    // Fixed: Epsilon comparison
    if (fabs(user_level - required_level) < FLT_EPSILON) {
        return 1;  // Grant access
    }
    return 0;  // Deny access
}

// Fixed: Use integer arithmetic for money
typedef long long cents_t;

cents_t to_cents(double dollars) {
    return (cents_t)round(dollars * 100.0);
}

void fixed_calculate_interest(cents_t principal_cents, double rate) {
    // Fixed: Interest calculation in cents (integer)
    cents_t interest_cents = (cents_t)round(principal_cents * rate);
    cents_t total_cents = principal_cents + interest_cents;

    // Safe integer comparison
    if (principal_cents == 100000 && total_cents != 105000) {  // $1000.00 and $1050.00
        printf("Calculation error!\n");
    }
}
// Fixed: C# floating-point comparisons
public class FixedPriceCalculator
{
    private const double Epsilon = 1e-9;

    // Fixed: Epsilon-based equality
    public bool AreEqual(double a, double b, double epsilon = Epsilon)
    {
        return Math.Abs(a - b) < epsilon;
    }

    // Fixed: Use Math.Approximately for Unity, or custom implementation
    public bool AreApproximatelyEqual(double a, double b, double tolerance)
    {
        return Math.Abs(a - b) <= tolerance * Math.Max(Math.Abs(a), Math.Abs(b));
    }

    // Fixed: Integer-based iteration
    public List<double> GenerateSequence(double start, double end, double step)
    {
        var sequence = new List<double>();

        // Calculate number of steps
        int steps = (int)Math.Round((end - start) / step);

        for (int i = 0; i < steps; i++)
        {
            sequence.Add(start + i * step);
        }

        return sequence;
    }

    // Fixed: Use decimal for money
    public bool IsPriceValid(decimal price, decimal expected)
    {
        // Decimal is exact for decimal fractions
        return price == expected;
    }

    // Fixed: Budget check with tolerance
    public bool IsWithinBudget(double spending, double budget)
    {
        // Fixed: Use less-than-or-equal with epsilon
        return spending < budget + Epsilon;
    }

    // Fixed: Financial calculations with decimal
    public decimal CalculateTax(decimal amount, decimal rate)
    {
        return Math.Round(amount * rate, 2, MidpointRounding.AwayFromZero);
    }
}

CVE Examples

Floating-point comparison errors have contributed to vulnerabilities in financial systems, access control, and loop-based calculations, though specific CVEs typically describe the resulting impact rather than this specific weakness.


  • CWE-697: Incorrect Comparison (parent)
  • CWE-682: Incorrect Calculation (related)
  • CWE-190: Integer Overflow or Wraparound (related for overflow issues)

References

  1. MITRE Corporation. "CWE-1077: Floating Point Comparison with Incorrect Operator." https://cwe.mitre.org/data/definitions/1077.html
  2. Goldberg, David. "What Every Computer Scientist Should Know About Floating-Point Arithmetic."
  3. IEEE 754 Standard for Floating-Point Arithmetic.