Sobes.tech
Middle

Je dobrý nápad použiť typ float ako kľúč pre kontajner?

sobes.tech AI

Odpoveď od AI

Použitie float ako kľúča pre asociatívne kontajnery (napríklad std::map, std::unordered_map) alebo pre priame zoradenie sa neodporúča z nasledujúcich dôvodov:

  • Problémy s porovnávaním: Rovnosť dvoch čísel typu float (alebo double) sa zriedka dosahuje kvôli chybám v reprezentácii s plávajúcou desatinnou čiarkou. Porovnanie a == b môže vrátiť nepravdivý výsledok, aj keď matematicky sú čísla rovnaké. To narúša invarianty kontajnerov, ktoré vyžadujú prísny slabý poriadok (std::map a std::set) alebo správny výpočet hash a porovnanie ekvivalencie (std::unordered_map a std::unordered_set).

  • Nesprávne zoradenie: Štandardné operátory porovnávania pre float nezaručujú vždy prísny slabý poriadok pre všetky možné hodnoty (napríklad NaN).

  • Nestabilný hash: Implementácia hash funkcií pre float môže byť nestabilná kvôli rovnakým problémom s reprezentáciou, čo môže viesť k nepredictívnemu správaniu alebo nízkej výkonnosti hash tabuliek.

Odporúčané prístupy:

  1. Použitie celočíselného reprezentovania: Ak presnosť nie je kritická alebo čísla majú obmedzený rozsah a rozlíšenie, môžete škálovať a konvertovať float na celé číslo (napríklad, int alebo long long) a použiť ho ako kľúč.

    float f = 1.23f;
    int key = static_cast<int>(f * 100); // Príklad škálovania
    std::map<int, Value> my_map;
    my_map[key] = niektore_hodnota;
    
  2. Použitie pevného bodu: Pre prípady, keď je požadované presné zobrazenie desatinných čísel, môžete použiť knižnicu na prácu s číslami s pevným bodom.

  3. Porovnávanie s toleranciou (epsilon): Hoci to neumožňuje priamo použiť float ako kľúč, pri vyhľadávaní môžete porovnávať hodnoty s použitím malého epsilon.

    bool are_equal(float a, float b, float epsilon = 1e-6) {
        return std::abs(a - b) < epsilon;
    }
    // Nie je vhodné na priame použitie ako kľúč v map
    
  4. Použitie vlastného porovnávacieho (pre std::map/std::set): Môžete definovať vlastného porovnávacieho, ktorý zohľadňuje toleranciu, ale to je spojené s rizikami narušenia invariancií prísneho slabého poriadku.

    struct FloatComparer {
        bool operator()(float a, float b) const {
            // Jednoduchý príklad, ktorý môže narušiť prísny slabý poriadok
            return a < b - 1e-6;
        }
    };
    // Neodporúča sa na použitie v reálnych aplikáciách
    // std::map<float, Value, FloatComparer> my_map;
    
  5. Použitie bitovej reprezentácie ako celého čísla (pre std::unordered_map/std::unordered_set): Pre hash tabuľky môžete previesť bitové zobrazenie float na celé číslo a použiť ho ako kľúč. To zabezpečuje jedinečnosť kľúča pre každé unikátne bitové zobrazenie float, ale nerieši problém porovnávania plávajúcich čísel, ktoré sú matematicky rovnaké, ale majú odlišné bitové zobrazenie (napríklad 0.0 a -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 {
            // Porovnanie bitových zobrazení
            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;
    // Poznámka: toto porovnáva bitové zobrazenia, nie matematickú rovnosť
    

Vo väčšine prípadov, ak sú kľúče čísla s plávajúcou desatinnou čiarkou, je vhodné prehodnotiť štruktúru dát alebo použiť jeden z prístupov s celočíselným alebo fixným bodovým zobrazením.