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
| Impact | Details |
|---|---|
| Other | Scope: Other Reduce Reliability - Floating-point comparison errors cause incorrect program behavior. |
| Availability | Scope: Availability DoS: Infinite Loop - Loop termination based on equality may never occur due to precision errors. |
| Integrity | Scope: 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.
Related CWEs
- CWE-697: Incorrect Comparison (parent)
- CWE-682: Incorrect Calculation (related)
- CWE-190: Integer Overflow or Wraparound (related for overflow issues)
References
- MITRE Corporation. "CWE-1077: Floating Point Comparison with Incorrect Operator." https://cwe.mitre.org/data/definitions/1077.html
- Goldberg, David. "What Every Computer Scientist Should Know About Floating-Point Arithmetic."
- IEEE 754 Standard for Floating-Point Arithmetic.