Gleitkommavergleich mit falschem Operator

Beschreibung

Gleitkommavergleich mit falschem Operator tritt auf, wenn numerische Berechnungen mit Gleitkommawerten ungenaue Ergebnisse aufgrund von Rundungsfehlern erzeugen und Vergleiche mit exakten Gleichheitsoperatoren durchgeführt werden. Gleitkommazahlen haben begrenzte Präzision, und Operationen wie Addition, Subtraktion, Multiplikation und Division können kleine Fehler einführen. Wenn zwei mathematisch gleiche Gleitkommazahlen aufgrund dieser Fehler leicht unterschiedliche Bitdarstellungen haben, liefern exakte Gleichheitsvergleiche (==, !=) unerwartete und falsche Ergebnisse.

Risiko

Obwohl hauptsächlich ein Korrektheitsproblem, haben Gleitkommavergleichsfehler Sicherheitsimplikationen. Finanzberechnungen mit Rundungsfehlern können zu falschen Gebühren oder Gutschriften führen. Zugriffskontrollentscheidungen basierend auf Schwellenwerten können fehlschlagen. Kryptographische Operationen mit Gleitkomma (obwohl selten) könnten kompromittiert werden. Schleifenabbruchbedingungen werden möglicherweise nie erfüllt, was Endlosschleifen und DoS verursacht. Grenzprüfungen können fehlschlagen und Out-of-Bounds-Zugriffe ermöglichen. Spielmechaniken oder Glücksspielsysteme können ausgenutzt werden. Wissenschaftliche oder sicherheitskritische Berechnungen können gefährliche Ergebnisse liefern.

Lösung

Verwenden Sie Epsilon-basierten Vergleich für Gleitkomma-Gleichheitsprüfungen. Vergleichen Sie die absolute Differenz mit einem kleinen Toleranzwert. Verwenden Sie exakte Arithmetiktypen (Decimal, BigDecimal) für Finanzberechnungen. Erwägen Sie Integer-Arithmetik mit entsprechender Skalierung. Verwenden Sie sprachspezifische Hilfsmittel für Gleitkommavergleiche (z.B. Math.ulp in Java). Seien Sie sich akkumulierter Fehler in Schleifen bewusst. Dokumentieren Sie die Präzisionsanforderungen von Berechnungen. Verwenden Sie statische Analysetools, die Gleitkommavergleichsprobleme erkennen. Erwägen Sie Festkomma-Arithmetik für eingebettete Systeme.

Häufige Auswirkungen

AuswirkungDetails
AndereBereich: Ändere

Reduzierte Zuverlässigkeit - Gleitkommavergleichsfehler verursachen falsches Programmverhalten.
VerfügbarkeitBereich: Verfügbarkeit

DoS: Endlosschleife - Schleifenabbruch basierend auf Gleichheit kann aufgrund von Präzisionsfehlern nie eintreten.
IntegritätBereich: Integrität

Unerwarteter Zustand - Programmzustand kann aufgrund fehlgeschlagener Vergleiche falsch sein.

Beispielcode

Anfälliger Code

// Anfällig: Direkter Gleichheitsvergleich von Gleitkommawerten
public class VulnerableFinancialCalculator {

    // Anfällig: Exakte Gleichheitsprüfung
    public boolean hasZeroBalance(double balance) {
        // Dies kann false zurückgeben sogar für sehr kleine Werte wie 0.0000000001
        return balance == 0.0;
    }

    // Anfällig: Gleitkomma-Schleifenabbruch
    public double sumRange(double start, double end, double step) {
        double sum = 0.0;
        double current = start;

        // Anfällig: Kann aufgrund von Gleitkommafehlern nie terminieren
        while (current != end) {  // Exakte Gleichheitsprüfung!
            sum += current;
            current += step;
        }

        return sum;  // Kann ewig laufen wenn current nie exakt end entspricht
    }

    // Anfällig: Schwellenwertvergleich
    public boolean isDiscountEligible(double purchaseAmount) {
        double threshold = 100.0;
        double discount = purchaseAmount * 0.1;  // 10% Rabatt

        // Anfällig: Kann aufgrund von Gleitkommapräzision fehlschlagen
        // 99.9 * 0.1 ist möglicherweise nicht exakt 9.99
        return discount == 9.99;
    }

    // Anfällig: Finanzberechnung
    public void processTransaction(double amount) {
        double tax = amount * 0.07;
        double total = amount + tax;

        // Anfällig: Kann fehlschlagen sogar für korrekte Beträge
        if (total != 107.0 && amount == 100.0) {
            throw new IllegalStateException("Berechnungsfehler");
        }
    }
}
# Anfällig: Gleitkommavergleichsfehler in Python
class VulnerableCalculator:

    def check_result(self, a, b, expected):
        """Anfällig: Direkter Gleichheitsvergleich."""
        result = a + b
        # Dies schlägt fehl: 0.1 + 0.2 == 0.3 gibt False zurück!
        if result == expected:
            return True
        return False

    def iterate_range(self, start, end, step):
        """Anfällig: Gleitkomma-Schleifenabbruch."""
        values = []
        current = start

        # Anfällig: Kann nie terminieren
        while current != end:  # Exakte Gleichheit!
            values.append(current)
            current += step
            # 0.1 + 0.1 + 0.1 ... wird möglicherweise nie exakt 1.0 entsprechen

        return values

    def calculate_discount(self, price, discount_rate):
        """Anfällig: Exakter Vergleich für Rabatte."""
        discount = price * discount_rate

        # Anfällig: 20.0 * 0.15 ist möglicherweise nicht exakt 3.0
        if discount == 3.0:
            return "Standardrabatt"
        elif discount == 5.0:
            return "Premiumrabatt"
        else:
            return "Kein passender Rabatt"

    def verify_split(self, total, parts):
        """Anfällig: Verifizierung gleichmäßiger Aufteilung."""
        share = total / parts

        # Anfällig: Summe der Anteile entspricht möglicherweise nicht total
        reconstructed = share * parts
        if reconstructed == total:
            return True
        return False  # Gibt oft False zurück!


# Demonstration des Problems:
# >>> 0.1 + 0.2
# 0.30000000000000004
# >>> 0.1 + 0.2 == 0.3
# False
// Anfällig: C-Gleitkommavergleich
#include <stdio.h>

// Anfällig: Exakte Gleichheit für Terminierung
int vulnerable_count_steps(float start, float end, float step) {
    int count = 0;
    float current = start;

    // Anfällig: Kann nie terminieren
    while (current != end) {
        count++;
        current += step;

        // Sicherheitsprüfung um Endlosschleife zu verhindern
        if (count > 1000000) {
            printf("Warnung: Iterationslimit überschritten\n");
            break;
        }
    }

    return count;
}

// Anfällig: Zugriffskontrolle basierend auf Gleitkomma
int vulnerable_check_access(float user_level, float required_level) {
    // Anfällig: Exakter Vergleich
    if (user_level == required_level) {
        return 1;  // Zugriff gewähren
    }
    return 0;  // Zugriff verweigern
}

// Anfällig: Finanzberechnung
void vulnerable_calculate_interest(double principal, double rate) {
    double interest = principal * rate;
    double total = principal + interest;

    // Anfällig: Kann aufgrund von Präzision fehlschlagen
    if (principal == 1000.0 && rate == 0.05) {
        if (total != 1050.0) {  // Möglicherweise nicht exakt 1050.0
            printf("Berechnungsfehler!\n");
        }
    }
}
// Anfällig: C#-Gleitkommavergleiche
public class VulnerablePriceCalculator
{
    // Anfällig: Direkte Gleichheit
    public bool AreEqual(double a, double b)
    {
        return a == b;  // Kann für mathematisch gleiche Werte fehlschlagen
    }

    // Anfällig: Schleife mit Gleitkomma-Terminierung
    public List<double> GenerateSequence(double start, double end, double step)
    {
        var sequence = new List<double>();
        double current = start;

        // Anfällig: Kann nicht terminieren
        while (current != end)
        {
            sequence.Add(current);
            current += step;
        }

        return sequence;
    }

    // Anfällig: Preisvergleich
    public bool IsPriceValid(decimal price, decimal expected)
    {
        // Auch decimal kann bei manchen Berechnungen Präzisionsprobleme haben
        return price == expected;
    }

    // Anfällig: Budgetprüfung
    public bool IsWithinBudget(double spending, double budget)
    {
        // Anfällig: Nach vielen Berechnungen könnte spending
        // 99.99999999999999 statt 100.0 sein
        return spending == budget || spending < budget;
    }
}

Korrigierter Code

// Korrigiert: Ordnungsgemäßer Gleitkommavergleich
public class FixedFinancialCalculator {

    private static final double EPSILON = 1e-9;  // Toleranz für Vergleich

    // Korrigiert: Epsilon-basierter Vergleich
    public boolean hasZeroBalance(double balance) {
        return Math.abs(balance) < EPSILON;
    }

    // Korrigiert: Vergleich mit Toleranz
    public static boolean approximatelyEqual(double a, double b, double epsilon) {
        return Math.abs(a - b) < epsilon;
    }

    // Korrigiert: Relatives Epsilon für größere Werte
    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;
    }

    // Korrigiert: Schleife mit ordnungsgemäßer Terminierung
    public double sumRange(double start, double end, double step) {
        double sum = 0.0;
        double current = start;

        // Korrigiert: Kleiner-als-Vergleich mit Epsilon verwenden
        while (current < end - EPSILON) {
            sum += current;
            current += step;
        }

        // Korrigiert: Alternative - Integer-Zählung verwenden
        // int steps = (int) Math.round((end - start) / step);
        // for (int i = 0; i < steps; i++) { ... }

        return sum;
    }

    // Korrigiert: BigDecimal für Finanzberechnungen verwenden
    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;
    }

    // Korrigiert: Finanzberechnungen mit 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("Berechnungsfehler");
        }
    }
}
# Korrigiert: Ordnungsgemäßer Gleitkommavergleich in Python
import math
from decimal import Decimal, ROUND_HALF_UP


class FixedCalculator:

    EPSILON = 1e-9  # Standard-Toleranz

    def approximately_equal(self, a: float, b: float,
                           epsilon: float = EPSILON) -> bool:
        """Vergleicht Floats mit Toleranz."""
        return abs(a - b) < epsilon

    def relatively_equal(self, a: float, b: float,
                        rel_tol: float = 1e-9) -> bool:
        """Vergleicht Floats mit relativer Toleranz."""
        # Pythons eingebaute Funktion dafür verwenden
        return math.isclose(a, b, rel_tol=rel_tol, abs_tol=1e-12)

    def check_result(self, a: float, b: float, expected: float) -> bool:
        """Korrigiert: math.isclose für Vergleich verwenden."""
        result = a + b
        return math.isclose(result, expected, rel_tol=1e-9)

    def iterate_range(self, start: float, end: float, step: float) -> list:
        """Korrigiert: Integer-basierte Iteration."""
        # Anzahl der Schritte berechnen
        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:
        """Korrigiert: Decimal für Finanzberechnungen verwenden."""
        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 "Standardrabatt"
        elif discount == Decimal('5.00'):
            return "Premiumrabatt"
        else:
            return f"Rabatt: {discount}"

    def verify_split(self, total: float, parts: int) -> bool:
        """Korrigiert: Epsilon-Vergleich verwenden."""
        share = total / parts
        reconstructed = share * parts
        return math.isclose(reconstructed, total, rel_tol=1e-9)


# Verwendungsdemonstration:
calc = FixedCalculator()
print(calc.check_result(0.1, 0.2, 0.3))  # True (war vorher False!)
// Korrigiert: C-Gleitkommavergleich mit Epsilon
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#include <float.h>

#define EPSILON 1e-9

// Korrigiert: Epsilon-basierte Gleichheit
bool approximately_equal(double a, double b, double epsilon) {
    return fabs(a - b) < epsilon;
}

// Korrigiert: Relatives Epsilon für variierende Größenordnungen
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;
}

// Korrigiert: Ordnungsgemäße Schleifenterminierung
int fixed_count_steps(float start, float end, float step) {
    // Schritte mit Integer berechnen
    int expected_steps = (int)roundf((end - start) / step);
    return expected_steps;
}

// Alternative: Kleiner-als-Vergleich
double fixed_sum_range(double start, double end, double step) {
    double sum = 0.0;
    double current = start;

    // Korrigiert: Kleiner-als mit Epsilon-Puffer verwenden
    while (current < end - EPSILON) {
        sum += current;
        current += step;
    }

    return sum;
}

// Korrigiert: Zugriffskontrolle mit Toleranz
int fixed_check_access(float user_level, float required_level) {
    // Korrigiert: Epsilon-Vergleich
    if (fabs(user_level - required_level) < FLT_EPSILON) {
        return 1;  // Zugriff gewähren
    }
    return 0;  // Zugriff verweigern
}

// Korrigiert: Integer-Arithmetik für Geld verwenden
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) {
    // Korrigiert: Zinsberechnung in Cents (Integer)
    cents_t interest_cents = (cents_t)round(principal_cents * rate);
    cents_t total_cents = principal_cents + interest_cents;

    // Sicherer Integer-Vergleich
    if (principal_cents == 100000 && total_cents != 105000) {  // 1000.00 und 1050.00 Euro
        printf("Berechnungsfehler!\n");
    }
}
// Korrigiert: C#-Gleitkommavergleiche
public class FixedPriceCalculator
{
    private const double Epsilon = 1e-9;

    // Korrigiert: Epsilon-basierte Gleichheit
    public bool AreEqual(double a, double b, double epsilon = Epsilon)
    {
        return Math.Abs(a - b) < epsilon;
    }

    // Korrigiert: Math.Approximately für Unity verwenden oder eigene Implementierung
    public bool AreApproximatelyEqual(double a, double b, double tolerance)
    {
        return Math.Abs(a - b) <= tolerance * Math.Max(Math.Abs(a), Math.Abs(b));
    }

    // Korrigiert: Integer-basierte Iteration
    public List<double> GenerateSequence(double start, double end, double step)
    {
        var sequence = new List<double>();

        // Anzahl der Schritte berechnen
        int steps = (int)Math.Round((end - start) / step);

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

        return sequence;
    }

    // Korrigiert: Decimal für Geld verwenden
    public bool IsPriceValid(decimal price, decimal expected)
    {
        // Decimal ist exakt für Dezimalbrüche
        return price == expected;
    }

    // Korrigiert: Budgetprüfung mit Toleranz
    public bool IsWithinBudget(double spending, double budget)
    {
        // Korrigiert: Kleiner-oder-gleich mit Epsilon verwenden
        return spending < budget + Epsilon;
    }

    // Korrigiert: Finanzberechnungen mit decimal
    public decimal CalculateTax(decimal amount, decimal rate)
    {
        return Math.Round(amount * rate, 2, MidpointRounding.AwayFromZero);
    }
}

CVE-Beispiele

Gleitkommavergleichsfehler haben zu Schwachstellen in Finanzsystemen, Zugriffskontrolle und schleifenbasierten Berechnungen beigetragen, obwohl spezifische CVEs typischerweise die resultierende Auswirkung beschreiben anstatt diese spezifische Schwäche.


Verwandte CWEs

  • CWE-697: Incorrect Comparison (Eltern)
  • CWE-682: Incorrect Calculation (verwandt)
  • CWE-190: Integer Overflow or Wraparound (verwandt für Überlaufprobleme)

Referenzen

  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.