Существует причина, по которой типы с плавающей запятой не поддерживают Ord
.Внутренние элементы BTreeMap
делают предположения о разработчиках Ord
, которые позволяют ему быть более эффективным, но если эти предположения оказываются неверными, то это может привести к неопределенному поведению, поскольку эти предположения основаны на unsafe
кодПлавающие точки не могут удовлетворить эти предположения из-за существования NaN
, значений, которые представляют бесконечность, и природы арифметики с плавающей запятой, которая означает, что «одно и то же» число может иметь различные двоичные представления.
Вероятно, ваш код C ++страдать от тех же проблем, но иногда кажется, что код с неопределенным поведением просто работает.До тех пор, пока однажды это не произойдет - это просто природа неопределенного поведения!
Плавающие точки хороши для представления мер или в тех случаях, когда значения могут иметь сильно различающиеся порядки величин.Если вы сделаете расчет расстояний между городами, а цифры будут отклоняться на несколько нанометров, вам все равно.Вам никогда не придется искать другой город, который будет точно на том же расстоянии, что и Лондон от Нью-Йорка.Скорее всего, вы будете рады найти город, который находится на таком же расстоянии до ближайшего 1 км - число, которое вы можете сравнить как целое число.
Что подводит меня к вопросу , почему вы используете числа с плавающей запятой в качестве ключей?Что это значит для вас и что вы пытаетесь там хранить?Является ли f64::NAN
значением, которое вы хотите использовать?Является ли 45.00000000001
"таким же", как 45.00000000001001
?Вы с такой же вероятностью будете хранить очень большие числа, как очень маленькие?Точное равенство имеет смысл для этих чисел?Являются ли они результатом вычислений, в которых могут быть ошибки округления?
Невозможно сказать вам, что здесь делать, но я могу предложить вам взглянуть на свою реальную проблему и смоделировать ее так, чтобы она отражалаваши реальные ограничения.Если вы заботитесь только о конкретной точности, то округлите числа до этой точности и сохраните их в типе с фиксированной точкой , или целом числе, или, возможно, рациональном , все из которыхреализовать Ord
.
Исходя из вашего кода C ++, похоже, что вы довольны точностью 0.001
.Таким образом, вы можете хранить свои ключи как целые числа - вам просто нужно помнить, чтобы выполнить преобразование и умножить / разделить на 1000 в зависимости от ситуации.Вам придется иметь дело с возможностью NaN
и бесконечностями, но вы будете делать это в безопасном коде, поэтому вам не придется беспокоиться о UB.
Вот как вы можете использоватьnum-rational
Ящик, чтобы получить что-то похожее на ваш код C ++:
extern crate num_rational; // 0.2.1
use num_rational::Rational64;
use std::collections::BTreeMap;
fn in_thousandths(n: f64) -> Rational64 {
// you may want to include further validation here to deal with `NaN` or infinities
let denom = 1000;
Rational64::new_raw((n * denom as f64) as i64, denom)
}
fn main() {
let mut map = BTreeMap::new();
map.insert(in_thousandths(1.0), 1);
map.insert(in_thousandths(2.0), 2);
map.insert(in_thousandths(3.0), 3);
let value = map.get(&1.into());
assert_eq!(Some(&1), value);
}