Sobes.tech
Middle

Je dobrý nápad použít typ float jako klíč pro kontejner?

sobes.tech AI

Odpověď od AI

Použití float jako klíče pro asociativní kontejnery (například std::map, std::unordered_map) nebo pro přímé třídění se nedoporučuje z následujících důvodů:

  • Problémy s porovnáním: Rovnost dvou čísel typu float (nebo double) je vzácná kvůli chybám v reprezentaci s plovoucí desetinnou čárkou. Porovnání a == b může vrátit nepravdivý výsledek, i když jsou čísla matematicky rovna. To narušuje invariance kontejnerů, které vyžadují přísný slabý řád (std::map a std::set) nebo správný výpočet hashe a porovnání ekvivalence (std::unordered_map a std::unordered_set).

  • Nesprávné řazení: Standardní operátory porovnání pro float nezaručují vždy přísný slabý řád pro všechny možné hodnoty (například NaN).

  • Nestabilní hash: Implementace hash funkcí pro float může být nestabilní kvůli stejným problémům s reprezentací, což povede k nepředvídatelnému chování nebo nízké výkonnosti hash tabulek.

Doporučené přístupy:

  1. Použití celočíselné reprezentace: Pokud přesnost není kritická nebo čísla mají omezený rozsah a rozlišení, lze škálovat a převést float na celé číslo (například int nebo long long) a použít ho jako klíč.

    float f = 1.23f;
    int key = static_cast<int>(f * 100); // Příklad škálování
    std::map<int, Value> my_map;
    my_map[key] = some_value;
    
  2. Použití pevného bodu: Pro případy, kdy je požadováno přesné zobrazení desetinných čísel, lze použít knihovnu pro práci s čísly s pevným bodem.

  3. Porovnávání s tolerancí (epsilon): Ačkoliv to přímo nepoužívá float jako klíč, při hledání lze porovnávat hodnoty s malou tolerancí (epsilon).

    bool are_equal(float a, float b, float epsilon = 1e-6) {
        return std::abs(a - b) < epsilon;
    }
    // Není vhodné pro přímé použití jako klíč v mapě
    
  4. Použití vlastního porovnavače (pro std::map/std::set): Lze definovat vlastního porovnavače, který bere v úvahu toleranci, ale to je spojeno s riziky narušení invariance přísného slabého řádu.

    struct FloatComparer {
        bool operator()(float a, float b) const {
            // Jednoduchý příklad, který může narušit přísný slabý řád
            return a < b - 1e-6;
        }
    };
    // Nedoporučuje se používat přímo v reálných aplikacích
    // std::map<float, Value, FloatComparer> my_map;
    
  5. Použití bitové reprezentace jako celého čísla (pro std::unordered_map/std::unordered_set): Pro hash tabulky lze převést bitovou reprezentaci float na celé číslo a použít ji jako klíč. To zajišťuje jedinečnost klíče pro každou unikátní bitovou reprezentaci float, ale neřeší problém porovnání plovoucích čísel, která jsou matematicky rovna, ale mají odlišnou bitovou reprezentaci (napří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 {
            // Porovnání bitových reprezentací
            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ává bitové reprezentace, nikoliv matematickou rovnost
    

Ve většině případů, pokud jsou klíče čísla s plovoucí desetinnou čárkou, je vhodné přehodnotit strukturu dat nebo použít některý z přístupů s celočíselnou nebo fixní bodovou reprezentací.