Sobes.tech
Middle

Ar gerai naudoti float tipą kaip raktą konteineriui?

sobes.tech AI

Atsakymas iš AI

Naudoti float kaip raktą asociatyviuose konteineriuose (pvz., std::map, std::unordered_map) arba tiesiogiai rūšiavimui nerekomenduojama dėl šių priežasčių:

  • Lyginimo problemos: Du float skaičiai (arba double) retai būna lygūs dėl plaukiojančio taško atstojimo klaidų. a == b palyginimas gali duoti klaidingą rezultatą, net jei matematiškai jie yra lygūs. Tai pažeidžia konteinerių invarianus, kurie reikalauja griežto silpnojo tvarkos (std::map ir std::set) arba teisingo maišos ir lyginimo (std::unordered_map ir std::unordered_set) skaičiavimo.

  • Netinkamas rūšiavimas: float palyginimo operatoriai ne visada užtikrina griežtą silpnąją tvarką visoms galimoms reikšmėms (pvz., NaN).

  • Nestabilus maišas: float maišos funkcijų įgyvendinimas gali būti nestabilus dėl tų pačių atstojimo klaidų, kas gali sukelti nenuspėjamą elgesį arba žemą našumą maišos lentelėse:

Rekomenduojami būdai:

  1. Naudoti sveikųjų skaičių atvaizdavimą: Jei tikslumas nėra svarbus arba skaičiai turi ribotą diapazoną ir rezoliuciją, galima mastelio keisti ir konvertuoti float į sveikąjį skaičių (pvz., int arba long long) ir naudoti jį kaip raktą:

    float f = 1.23f;
    int raktas = static_cast<int>(f * 100); // Mastelio keitimo pavyzdys
    std::map<int, Value> mano_map;
    mano_map[raktas] = kažkoks_reikšmė;
    
  2. Naudoti fiksuotą tašką: Kai reikalingas tikslius trupmeninius skaičius, galima naudoti fiksuoto taško skaičių biblioteką.

  3. Lyginti su leistinu nuokrypiu (epsilon): Nors tai tiesiogiai neleidžia naudoti float kaip rakto, paieškos metu galima lyginti reikšmes naudojant mažą leistiną nuokrypį (epsilon):

    bool yraLygu(float a, float b, float epsilon = 1e-6) {
        return std::abs(a - b) < epsilon;
    }
    // Netinka tiesioginiam naudojimui kaip rakto map'e
    
  4. Naudoti naudotojo apibrėžtą palyginimo funkciją (std::map/std::set): Galima apibrėžti naudotojo palyginimo funkciją, kuri atsižvelgia į leistiną nuokrypį, tačiau tai vis tiek gali pažeisti griežto silpnojo tvarkos invarianus:

    struct FloatComparer {
        bool operator()(float a, float b) const {
            // Paprastas pavyzdys, kuris gali pažeisti griežtą silpnąją tvarką
            return a < b - 1e-6;
        }
    };
    // Nerekomenduojama naudoti realiuose projektuose
    // std::map<float, Value, FloatComparer> mano_map;
    
  5. Bitų atvaizdžio konvertavimas į sveikąjį skaičių: std::unordered_map/std::unordered_set gali naudoti float bitų atvaizdį kaip raktą, konvertuojant jį į sveikąjį skaičių, užtikrinant unikalumą, bet nepašalina problemų su float lyginimu, kai matematiškai lygūs skaičiai turi skirtingą bitų atvaizdą (pvz., 0.0 ir -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ų atvaizdžių palyginimas
            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> mano_map;
    // Pastaba: tai lygina bitų atvaizdus, ne matematinį lygumą
    

Apibendrinant, jei raktai yra plaukiojantys skaičiai ir jų atvaizdavimas ar tikslumas kelia problemų, rekomenduojama peržiūrėti duomenų struktūrą arba naudoti sveikųjų skaičių arba fiksuoto taško metodus.