Is het een goed idee om het float-type te gebruiken als sleutel voor een container?
sobes.tech AI
Antwoord van AI
Het gebruik van float als sleutel voor associatieve containers (bijvoorbeeld std::map, std::unordered_map) of voor directe sortering wordt om de volgende redenen niet aanbevolen:
-
Vergelijkingsproblemen: De gelijkheid van twee
float-getallen (ofdouble) wordt zelden bereikt vanwege fouten in de floating-point representatie. Het vergelijken vana == bkan een onjuist resultaat geven, zelfs als de getallen mathematisch gelijk zijn. Dit breekt de invarianten van containers die een strikte zwakke orde vereisen (std::mapenstd::set) of een correcte hash- en gelijkheidsvergelijking (std::unordered_mapenstd::unordered_set). -
Onjuiste sortering: De standaardvergelijkingsoperatoren voor
floatgaranderen niet altijd een strikte zwakke orde voor alle mogelijke waarden (bijvoorbeeld NaN). -
Onstabiele hash: De implementatie van hash-functies voor
floatkan onstabiel zijn vanwege dezelfde problemen met de representatie, wat kan leiden tot onvoorspelbaar gedrag of lage prestaties van hash-tabellen.
Aanbevolen benaderingen:
-
Gebruik een gehele representatie: Als precisie niet belangrijk is of de getallen een beperkt bereik en resolutie hebben, kan
floatworden geschaald en geconverteerd naar een geheel getal (bijvoorbeeldintoflong long) en als sleutel worden gebruikt.float f = 1.23f; int key = static_cast<int>(f * 100); // Voorbeeld van schaling std::map<int, Value> my_map; my_map[key] = some_value; -
Gebruik vaste kommagetallen: Voor gevallen waarin een exacte representatie van decimale getallen vereist is, kan een bibliotheek voor vaste kommagetallen worden gebruikt.
-
Vergelijken met tolerantie (epsilon): Hoewel dit niet direct het gebruik van
floatals sleutel mogelijk maakt, kan bij het zoeken worden vergeleken met behulp van een kleine tolerantie (epsilon).bool are_equal(float a, float b, float epsilon = 1e-6) { return std::abs(a - b) < epsilon; } // Niet geschikt voor direct gebruik als sleutel in een map -
Gebruik een aangepaste comparator (voor
std::map/std::set): Een aangepaste comparator die rekening houdt met de tolerantie kan worden gedefinieerd, maar dit kan nog steeds de invarianten van de strikte zwakke orde schenden.struct FloatComparer { bool operator()(float a, float b) const { // Eenvoudig voorbeeld, dat de strikte zwakke orde kan schenden return a < b - 1e-6; } }; // Niet aanbevolen voor gebruik in echte toepassingen // std::map<float, Value, FloatComparer> my_map; -
Gebruik een binaire representatie in een geheel getal (voor
std::unordered_map/std::unordered_set): Voor hash-tabellen kan de bitrepresentatie vanfloatworden geconverteerd naar een geheel getal en als sleutel worden gebruikt. Dit garandeert de uniciteit van de sleutel voor elke unieke bitrepresentatie vanfloat, maar lost het probleem niet op dat numeriek gelijkwaardige getallen verschillende bitrepresentaties kunnen hebben (bijvoorbeeld 0.0 en -0.0).union FloatIntConverter { float f; int i; }; struct FloatHash { size_t operator()(float f) const { FloatIntConverter converter; converter.f = f; return std::hash<int>()(converter.i); } }; struct FloatEqual { bool operator()(float a, float b) const { // Vergelijking van bitrepresentaties FloatIntConverter converter_a, converter_b; converter_a.f = a; converter_b.f = b; return converter_a.i == converter_b.i; } }; // std::unordered_map<float, Value, FloatHash, FloatEqual> my_map; // Opmerking: dit vergelijkt bitrepresentaties, niet de wiskundige gelijkheid
In de meeste gevallen, als de sleutels floating-point getallen zijn, moet de datastructuur worden heroverwogen of een van de benaderingen met gehele representatie of vaste puntgetallen worden gebruikt.