Sobes.tech
Middle

Jó ötlet a float típus használata kulcsként egy tárolóhoz?

sobes.tech MI

Válasz az MI-től

Az float használata kulcsként asszociatív tárolókban (például std::map, std::unordered_map) vagy közvetlen rendezéshez nem ajánlott a következő okok miatt:

  • Összehasonlítási problémák: Két float (vagy double) típusú szám egyenlősége ritkán érhető el a lebegőpontos ábrázolási hibák miatt. Az a == b összehasonlítás hamis eredményt adhat, még akkor is, ha matematikailag a számok egyenlőek. Ez sérti a konténerek szigorú gyenge sorrend invariánsait (std::map és std::set) vagy a hash és az egyenlőség összehasonlás helyességét (std::unordered_map és std::unordered_set).

  • Hibás rendezés: A float alapértelmezett összehasonlító operátorai nem biztosítanak minden lehetséges értékre szigorú gyenge sorrendet (például NaN).

  • Instabil hash: A float-hoz készült hash függvények implementációja instabil lehet ugyanazon problémák miatt, ami kiszámíthatatlan viselkedést vagy alacsony hash-táblázati teljesítményt eredményezhet.

Ajánlott megközelítések:

  1. Egész szám reprezentáció használata: Ha a pontosság nem kritikus, vagy a számok korlátozott tartományban és felbontással rendelkeznek, akkor a float-ot skálázással és átalakítással egész számmá (például int vagy long long) lehet alakítani, és kulcsként használni.

    float f = 1.23f;
    int key = static_cast<int>(f * 100); // Skálázási példa
    std::map<int, Value> my_map;
    my_map[key] = some_value;
    
  2. Fix pont használata: Olyan esetekben, amikor pontos tizedes szám ábrázolás szükséges, használhatunk fix pont számokat kezelő könyvtárakat.

  3. Epsilon-alapú összehasonlítás: Bár ez nem teszi lehetővé a float közvetlen kulcsként való használatát, kereséskor összehasonlíthatjuk az értékeket egy kis epsilon értékkel.

    bool are_equal(float a, float b, float epsilon = 1e-6) {
        return std::abs(a - b) < epsilon;
    }
    // Nem alkalmas közvetlenül kulcsként való használatra map-ben
    
  4. Felhasználó által definiált összehasonlító (pl. std::map/std::set esetén): Létrehozhatunk olyan összehasonlítót, amely figyelembe veszi az epsilon-t, de ez kockázatokat rejt magában a szigorú gyenge sorrend invariánsainak megsértése miatt.

    struct FloatComparer {
        bool operator()(float a, float b) const {
            // Egyszerű példa, ami megsértheti a szigorú gyenge sorrendet
            return a < b - 1e-6;
        }
    };
    // Nem ajánlott közvetlenül használni valós alkalmazásokban
    // std::map<float, Value, FloatComparer> my_map;
    
  5. Bit reprezentáció egész számként való használata (pl. std::unordered_map/std::unordered_set): Hash táblák esetén a float bit reprezentációját egész számmá lehet alakítani, és kulcsként használni. Ez biztosítja, hogy minden egyedi float bit reprezentációhoz egyedi kulcs tartozik, de nem oldja meg a lebegőpontos számok összehasonlításának problémáját, amelyek matematikailag egyenlők lehetnek, de különböző bit reprezentációval rendelkeznek (pl. 0.0 és -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 {
            // Bit reprezentációk összehasonlítása
            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;
    // Figyelem: ez a bit reprezentációkat hasonlítja össze, nem a matematikai egyenlőséget
    

Általánosságban, ha lebegőpontos számokat használnak kulcsként, érdemes átgondolni az adatszerkezetet, vagy az egyik fent említett megközelítést alkalmazni egész szám vagy fix pont reprezentációval.