Ответ
Нет, это крайне не рекомендуется для std::map, std::set, std::unordered_map и std::unordered_set.
Причины:
- Проблемы точности: Из-за двоичного представления чисел с плавающей точкой операции могут давать разные результаты на разных архитектурах или с разными уровнями оптимизации. Значение, вычисленное как
0.1 + 0.2, не будет бинарно равно литералу0.3. - Ненадёжное сравнение:
std::mapиспользуетoperator<для упорядочения. Дляfloatэто приводит к недетерминированному поведению из-за погрешностей. - Неподходящее хеширование:
std::unordered_mapтребует строгого равенства ключей. Два математически равныхfloatмогут иметь разные битовые представления и, следовательно, разные хеши.
Альтернативы, которые я использовал:
- Фиксированная точка: Умножить на коэффициент и использовать целочисленный тип.
std::map<int, std::string> priceMap; // Цена в рублях с копейками float price = 123.45f; priceMap[static_cast<int>(price * 100)] = "Товар"; // Ключ: 12345 - Кастомный компаратор с эпсилон-окрестностью (осторожно, нарушает строгое слабое упорядочение):
struct FloatCompare { bool operator()(float a, float b) const { const float eps = 1e-6f; if (std::abs(a - b) < eps) return false; // Считаем равными return a < b; } }; // std::map<float, Value, FloatCompare> map; // Рискованно - Использовать
std::ratioили библиотеки decimal для финансовых расчётов.
В production-коде я всегда избегаю float/double как ключей, предпочитая целочисленные или строковые представления.
Ответ 18+ 🔞
Бля, слушай, тут один чувак на днях спрашивает: "А можно ли использовать float как ключ в map?" Я ему так сразу: "Нет, ёпта, это крайне не рекомендуется!" И не только для std::map, а вообще для всех этих std::set, std::unordered_map и std::unordered_set. Вообще забей.
А почему, спросишь? Да овердохуища причин!
- Проблемы с точностью — пиздец какие. Из-за того, как числа с плавающей точкой внутри устроены, одна и та же операция на разных железяках или с разными флагами компилятора может дать слегка разные результаты. Ну вот представь:
0.1 + 0.2в компьютере бинарно — это не то же самое, что просто0.3. И что ты с этим будешь делать? - Сравнение работает через жопу. Обычный
std::mapдля порядка используетoperator<. А дляfloatэто сравнение из-за этих самых погрешностей становится каким-то нестабильным, поведение недетерминированное получается. Один раз ключ найдётся, другой раз — хуй с горы. - Хеширование вообще в пизду. Для
std::unordered_mapнужно, чтобы ключи либо были равны, либо нет. А два математически одинаковыхfloatа могут в памяти лежать по-разному. Хеш у них будет разный, и твой ключ в контейнере просто потеряется, манда с ушами.
Так что же делать, если очень надо? Я обычно юзаю вот такие костыли, проверенные временем:
- Фиксированная точка — мой любимый способ. Берёшь, умножаешь всё на какой-нибудь коэффициент (например, на 100 для копеек) и используешь уже нормальный целочисленный ключ. Просто и надёжно.
std::map<int, std::string> priceMap; // Цена в рублях с копейками float price = 123.45f; priceMap[static_cast<int>(price * 100)] = "Товар"; // Ключ: 12345 - Кастомный компаратор с эпсилон-окрестностью. Это уже для отчаянных. Делаешь свою функцию сравнения, которая считает числа равными, если они отличаются меньше чем на какую-то мелкую величину (эпсилон). Но тут, чувак, осторожно — можно нарушить строгое слабое упорядочение, и тогда твой
mapпросто сломается в самый неподходящий момент.struct FloatCompare { bool operator()(float a, float b) const { const float eps = 1e-6f; if (std::abs(a - b) < eps) return false; // Считаем равными return a < b; } }; // std::map<float, Value, FloatCompare> map; // Рискованно, как ходить по охуенно тонкому льду - Использовать
std::ratioили специальные библиотеки для decimal-типов. Особенно если дело касается денег — там без вариантов.
Короче, вывод простой: в нормальном, production-коде я всегда обхожу float/double как ключи стороной. Лучше уж преобразовать во что-то целочисленное или строку, чем потом охуевать от необъяснимых багов. Доверия к этому подходу — ноль ебать.