Este o idee bună să folosiți tipul float ca cheie pentru un container?
sobes.tech AI
Răspuns de la AI
Utilizarea float ca cheie pentru containerele asociative (de exemplu, std::map, std::unordered_map) sau pentru sortare directă nu este recomandată din următoarele motive:
-
Probleme de comparație: Egalitatea a două numere de tip
float(saudouble) este rar atinsă din cauza erorilor de reprezentare în virgulă mobilă. Comparareaa == bpoate da un rezultat fals, chiar dacă matematic numerele sunt egale. Acest lucru încalcă invariabilele containerelor care necesită o ordine slabă strictă (std::mapșistd::set) sau o calculare corectă a hash-ului și compararea echivalenței (std::unordered_mapșistd::unordered_set). -
Ordinare incorect: Operatorii standard de comparație pentru
floatnu asigură întotdeauna o ordine slabă strictă pentru toate valorile posibile (de exemplu, NaN). -
Hash nesigur: Implementarea funcțiilor hash pentru
floatpoate fi nesigură din cauza aceluiași probleme de reprezentare, ceea ce poate duce la comportament imprevizibil sau performanță scăzută a tabelelor hash.
Abordări recomandate:
-
Utilizarea reprezentării întregi: Dacă precizia nu este critică sau numerele au un interval și o rezoluție limitată, se poate scala și converti
floatîn numere întregi (de exemplu,intsaulong long) și să le folosiți ca cheie.float f = 1.23f; int key = static_cast<int>(f * 100); // Exemplu de scalare std::map<int, Value> my_map; my_map[key] = some_value; -
Utilizarea punctului fix: Pentru cazurile în care este necesară o reprezentare exactă a numerelor fracționare, se poate folosi o bibliotecă pentru lucrul cu numere în punct fix.
-
Compararea cu toleranță (epsilon): Deși nu permite utilizarea directă a
floatca și cheie, la căutare se pot compara valorile folosind o toleranță mică (epsilon).bool are_equal(float a, float b, float epsilon = 1e-6) { return std::abs(a - b) < epsilon; } // Nu este potrivit pentru utilizarea directă ca și comparator în map -
Utilizarea unui comparator personalizat (pentru
std::map/std::set): Se poate defini un comparator personalizat care ia în considerare toleranța, dar acest lucru implică riscuri de încălcare a invariabilor de ordine slabă strictă.struct FloatComparer { bool operator()(float a, float b) const { // Exemplu simplu, care poate încălca ordinea slabă strictă return a < b - 1e-6; } }; // Nu se recomandă utilizarea directă în aplicații reale // std::map<float, Value, FloatComparer> my_map; -
Utilizarea reprezentării bitilor ca întreg (pentru
std::unordered_map/std::unordered_set): Pentru tabelele hash, se poate converti reprezentarea bitilor afloatîntr-un număr întreg și să o folosiți ca și cheie. Aceasta asigură unicitatea cheii pentru fiecare reprezentare bit afloat, dar nu rezolvă problema comparării numerelor în virgulă mobilă care sunt matematic egale, dar au reprezentări bit diferite (de exemplu, 0.0 și -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 { // Compararea reprezentărilor bitilor 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; // Atenție: compară reprezentările bitilor, nu egalitatea matematică
În majoritatea cazurilor, dacă cheile sunt numere în virgulă mobilă, ar trebui să reconsiderați structura de date sau să folosiți una dintre abordările cu reprezentare întreagă sau numere în punct fix.