Sobes.tech
Middle

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 (of double) wordt zelden bereikt vanwege fouten in de floating-point representatie. Het vergelijken van a == b kan een onjuist resultaat geven, zelfs als de getallen mathematisch gelijk zijn. Dit breekt de invarianten van containers die een strikte zwakke orde vereisen (std::map en std::set) of een correcte hash- en gelijkheidsvergelijking (std::unordered_map en std::unordered_set).

  • Onjuiste sortering: De standaardvergelijkingsoperatoren voor float garanderen niet altijd een strikte zwakke orde voor alle mogelijke waarden (bijvoorbeeld NaN).

  • Onstabiele hash: De implementatie van hash-functies voor float kan onstabiel zijn vanwege dezelfde problemen met de representatie, wat kan leiden tot onvoorspelbaar gedrag of lage prestaties van hash-tabellen.

Aanbevolen benaderingen:

  1. Gebruik een gehele representatie: Als precisie niet belangrijk is of de getallen een beperkt bereik en resolutie hebben, kan float worden geschaald en geconverteerd naar een geheel getal (bijvoorbeeld int of long 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;
    
  2. Gebruik vaste kommagetallen: Voor gevallen waarin een exacte representatie van decimale getallen vereist is, kan een bibliotheek voor vaste kommagetallen worden gebruikt.

  3. Vergelijken met tolerantie (epsilon): Hoewel dit niet direct het gebruik van float als 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
    
  4. 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;
    
  5. Gebruik een binaire representatie in een geheel getal (voor std::unordered_map/std::unordered_set): Voor hash-tabellen kan de bitrepresentatie van float worden geconverteerd naar een geheel getal en als sleutel worden gebruikt. Dit garandeert de uniciteit van de sleutel voor elke unieke bitrepresentatie van float, 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.