È una buona idea usare il tipo float come chiave per un contenitore?
sobes.tech AI
Risposta dell'AI
L'uso di float come chiave per contenitori associativi (ad esempio, std::map, std::unordered_map) o per ordinare direttamente non è raccomandato per i seguenti motivi:
-
Problemi di confronto: L'uguaglianza di due numeri di tipo
float(odouble) si verifica raramente a causa di errori nella rappresentazione in virgola mobile. Confrontarea == bpuò dare un risultato falso, anche se matematicamente i numeri sono uguali. Ciò viola gli invarianti dei contenitori che richiedono un ordine debole rigoroso (std::mapestd::set) o una corretta funzione hash e confronto di uguaglianza (std::unordered_mapestd::unordered_set). -
Ordinamento errato: Gli operatori di confronto standard per
floatnon garantiscono sempre un ordine debole rigoroso per tutti i valori possibili (ad esempio, NaN). -
Hash instabile: L'implementazione di funzioni hash per
floatpuò essere instabile a causa degli stessi problemi di rappresentazione, portando a comportamenti imprevedibili o bassa performance nelle tabelle hash.
Approcci raccomandati:
-
Usare rappresentazione intera: Se la precisione non è importante o i numeri hanno un intervallo e una risoluzione limitati, si può scalare e convertire
floatin un intero (ad esempio,intolong long) e usarlo come chiave.float f = 1.23f; int key = static_cast<int>(f * 100); // Esempio di scalatura std::map<int, Value> my_map; my_map[key] = some_value; -
Usare punto fisso: Per i casi in cui è richiesta una rappresentazione esatta di numeri frazionari, si può usare una libreria per lavorare con numeri a punto fisso.
-
Confrontare con tolleranza (epsilon): Sebbene questo non consenta di usare
floatcome chiave direttamente, durante la ricerca si può confrontare usando una piccola tolleranza (epsilon).bool are_equal(float a, float b, float epsilon = 1e-6) { return std::abs(a - b) < epsilon; } // Non adatto per uso diretto come comparatore di chiavi in map -
Usare un comparatore personalizzato (per
std::map/std::set): Si può definire un comparatore personalizzato che tenga conto della tolleranza, ma questo può comunque violare l'invariante di ordine debole rigoroso.struct FloatComparer { bool operator()(float a, float b) const { // Esempio semplice, che può violare l'ordine debole rigoroso return a < b - 1e-6; } }; // Non raccomandato usare direttamente in applicazioni reali // std::map<float, Value, FloatComparer> my_map; -
Usare rappresentazione binaria in intero (per
std::unordered_map/std::unordered_set): Per le tabelle hash, si può convertire la rappresentazione binaria difloatin un intero e usarlo come chiave. Questo garantisce l’unicità della chiave per ogni rappresentazione binaria difloat, ma non risolve il problema del confronto di numeri in virgola mobile che sono matematicamente uguali ma hanno rappresentazioni diverse (ad esempio, 0.0 e -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 { // Confronto delle rappresentazioni binarie 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; // Nota: questo confronta le rappresentazioni binarie, non l’uguaglianza matematica
Nella maggior parte dei casi, se le chiavi sono numeri in virgola mobile, si dovrebbe riconsiderare la struttura dati o usare uno degli approcci con rappresentazione intera o numeri a virgola fissa.