Sobes.tech
Middle

Da li je dobra ideja koristiti tip float kao ključ za kontejner?

sobes.tech АИ

Одговор од АИ

Користење float као кључ за асоцијативне контејнере (на пример, std::map, std::unordered_map) или за директно сортирање се не препоручује из следећих разлога:

  • Проблеми са поређењем: Једнакост два броја типа float (или double) се ретко постиже због грешака у представљању са плавајућом тачком. Поређење a == b може дати лажан резултат, чак и ако су бројеви математички једнаки. Ово нарушава инваријанте контејнера који захтевају строгу слабу поредак (std::map и std::set) или исправан израчун хеша и поређење еквиваленције (std::unordered_map и std::unordered_set).

  • Нетачно сортирање: Стандардни оператори поређења за float не гарантују увек строги слабак поредак за све могуће вредности (на пример, NaN).

  • Нестабилан хеш: Имплементација функција хеша за float може бити нестабилна због истих проблема са представљањем, што може довести до непредвидивог понашања или ниске перформансе хеш табела.

Препоручени приступи:

  1. Користите целобројну репрезентацију: Ако прецизност није критична или бројеви имају ограничени распон и резолуцију, можете скалирати и претворити float у целу број (на пример, int или long long) и користити га као кључ.

    float f = 1.23f;
    int key = static_cast<int>(f * 100); // Пример скалирања
    std::map<int, Value> my_map;
    my_map[key] = нека вредност;
    
  2. Користите фиксну тачку: За случајеве када је потребно тачно представљање децималних бројева, можете користити библиотеку за рад са бројевима са фиксном тачком.

  3. Поређење са толеранцијом (epsilon): Иако то не дозвољава директно коришћење float као кључ, при претрази можете поређивати вредности користећи мали допуштени праг (epsilon).

    bool are_equal(float a, float b, float epsilon = 1e-6) {
        return std::abs(a - b) < epsilon;
    }
    // Не одговара за директно коришћење као кључ у мапи
    
  4. Користите кориснички дефинисан компаратор (за std::map/std::set): Можете дефинисати кориснички компаратор који узима у обзир допуштени праг, али то носи ризике од нарушавања инваријаната строге слабе поредак.

    struct FloatComparer {
        bool operator()(float a, float b) const {
            // Једноставан пример, који може нарушити строги слабак поредак
            return a < b - 1e-6;
        }
    };
    // Не препоручује се за директну употребу у стварним апликацијама
    // std::map<float, Value, FloatComparer> my_map;
    
  5. Користите битну репрезентацију као целу број: За хеш табеле можете претворити битну репрезентацију float у целу број и користити је као кључ. Ово обезбеђује јединственост кључа за сваки јединствени битни приказ float, али не решава проблем поређења бројева са плавајућом тачком који су математички једнаки, али имају различите битне приказе (на пример, 0.0 и -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 {
            // Поређење битних приказа
            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;
    // Обратите пажњу: ово пореди битне приказе, не математичку равност
    

У већини случајева, ако су кључеви бројеви са плавајућом тачком, треба преиспитати структуру података или користити један од приступа са целобројном или фиксном тачком репрезентацијом.