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
| Auswirkung | Details |
|---|---|
| Andere | Bereich: Ändere Reduzierte Zuverlässigkeit - Gleitkommavergleichsfehler verursachen falsches Programmverhalten. |
| Verfügbarkeit | Bereich: Verfügbarkeit DoS: Endlosschleife - Schleifenabbruch basierend auf Gleichheit kann aufgrund von Präzisionsfehlern nie eintreten. |
| Integrität | Bereich: 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
-
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.