如何在 Rust 中使用 f64 作为键的 HashMap? [英] How can I use a HashMap with f64 as key in Rust?

查看:57
本文介绍了如何在 Rust 中使用 f64 作为键的 HashMap?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想使用 HashMap<f64, f64> 来保存具有已知 x 和键 y 的点到另一个点的距离.f64 作为值在这里应该无关紧要,重点应该放在键上.

I want to use a HashMap<f64, f64>, for saving the distances of a point with known x and key y to another point. f64 as value shouldn't matter here, the focus should be on key.

let mut map = HashMap<f64, f64>::new();
map.insert(0.4, f64::hypot(4.2, 50.0));
map.insert(1.8, f64::hypot(2.6, 50.0));
...
let a = map.get(&0.4).unwrap();

由于f64既不是Eq也不是Hash,而只是PartialEqf64 作为键是不够的.我需要先保存距离,但也需要稍后通过 y 访问距离.y 的类型需要是浮点精度,但如果不适用于 f64,我将使用具有已知指数的 i64.

As f64 is neither Eq nor Hash, but only PartialEq, f64 is not sufficient as a key. I need to save the distances first, but also access the distances later by y. The type of y needs to be floating point precision, but if doesn't work with f64, I'll use an i64 with an known exponent.

我通过使用自己的 struct Dimension(f64) 尝试了一些技巧,然后通过将浮点数转换为 String 来实现 Hash,然后散列它.

I tried some hacks by using my own struct Dimension(f64) and then implementing Hash by converting the float into a String and then hashing it.

#[derive(PartialEq, Eq)]
struct DimensionKey(f64);

impl Hash for DimensionKey {
    fn hash<H: Hasher>(&self, state: &mut H) {
        format!("{}", self.0).hash(state);
    }
}

这似乎很糟糕,两种解决方案,我自己的结构或浮点数作为具有基数和指数的整数似乎都非常复杂,只是一个键.

It seems very bad and both solutions, my own struct or float as integers with base and exponent seem to be pretty complicated for just a key.

更新:我可以保证我的密钥永远不会是 NaN 或无限值.另外,我不会计算我的密钥,只会遍历它们并使用它们.因此 0.1 + 0.2 ≠ 0.3 的已知错误应该没有错误.如何对 Vec 进行二分搜索浮点数?和这个问题的共同点是实现浮点数的全序和相等,区别仅在于散列或迭代.

Update: I can guarantee that my key never will be NaN, or an infinite value. Also, I won't calculate my keys, only iterating over them and using them. So there should no error with the known error with 0.1 + 0.2 ≠ 0.3. How to do a binary search on a Vec of floats? and this question have in common to implement total ordering and equality for a floating number, the difference lies only in the hashing or iterating.

推荐答案

您可以将 f64 拆分为整数部分和小数部分,并按以下方式将它们存储在结构中:

You could split the f64 into the integral and fractional part and store them in a struct in the following manner:

#[derive(Hash, Eq, PartialEq)]
struct Distance {
    integral: u64,
    fractional: u64
}

剩下的很简单:

use std::collections::HashMap;

#[derive(Hash, Eq, PartialEq)]
struct Distance {
    integral: u64,
    fractional: u64
}

impl Distance {
    fn new(i: u64, f: u64) -> Distance {
        Distance {
            integral: i,
            fractional: f
        }
    }
}

fn main() {
    let mut map: HashMap<Distance, f64> = HashMap::new();

    map.insert(Distance::new(0, 4), f64::hypot(4.2, 50.0));
    map.insert(Distance::new(1, 8), f64::hypot(2.6, 50.0));

    assert_eq!(map.get(&Distance::new(0, 4)), Some(&f64::hypot(4.2, 50.0)));
}

编辑:正如 Veedrac 所说,更通用和更有效的选择是将 f64 解构为尾数指数符号三元组.可以做到这一点的函数,integer_decode(),在 std 中已弃用,但可以在 Rust GitHub.

Edit: As Veedrac said, a more general and efficient option would be to deconstruct the f64 into a mantissa-exponent-sign triplet. The function that can do this, integer_decode(), is deprecated in std, but it can be easily found in Rust GitHub.

integer_decode()函数可以定义如下:

use std::mem;

fn integer_decode(val: f64) -> (u64, i16, i8) {
    let bits: u64 = unsafe { mem::transmute(val) };
    let sign: i8 = if bits >> 63 == 0 { 1 } else { -1 };
    let mut exponent: i16 = ((bits >> 52) & 0x7ff) as i16;
    let mantissa = if exponent == 0 {
        (bits & 0xfffffffffffff) << 1
    } else {
        (bits & 0xfffffffffffff) | 0x10000000000000
    };

    exponent -= 1023 + 52;
    (mantissa, exponent, sign)
}

距离的定义可以是:

#[derive(Hash, Eq, PartialEq)]
struct Distance((u64, i16, i8));

impl Distance {
    fn new(val: f64) -> Distance {
        Distance(integer_decode(val))
    }
}

这个变种也更容易使用:

This variant is also easier to use:

fn main() {
    let mut map: HashMap<Distance, f64> = HashMap::new();

    map.insert(Distance::new(0.4), f64::hypot(4.2, 50.0));
    map.insert(Distance::new(1.8), f64::hypot(2.6, 50.0));

    assert_eq!(map.get(&Distance::new(0.4)), Some(&f64::hypot(4.2, 50.0)));
}

这篇关于如何在 Rust 中使用 f64 作为键的 HashMap?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆