Floating point 如何使用f64作为Rust中的键的HashMap?
我想使用一个Floating point 如何使用f64作为Rust中的键的HashMap?,floating-point,hashmap,rust,Floating Point,Hashmap,Rust,我想使用一个HashMap,保存一个点到另一个点的距离,该点具有已知的x和y键f64在这里,值不重要,重点应该放在键上 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(); 这似乎非常糟糕,两种解决方案,我自己的结构或浮
HashMap
,保存一个点到另一个点的距离,该点具有已知的x和y键f64
在这里,值不重要,重点应该放在键上
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();
这似乎非常糟糕,两种解决方案,我自己的结构或浮点作为带基数和指数的整数,对于一个键来说似乎都非常复杂
更新:
我可以保证我的密钥永远不会是NaN
,也不会是无限值。此外,我不会计算我的密钥,只会迭代并使用它们。因此,0.1+0.2的已知错误应该没有错误≠ 0.3
。
这个问题的共同点是实现浮点数的全序和相等,区别仅在于散列或迭代。您可以将
f64
拆分为整数部分和小数部分,并以以下方式存储在结构中:
#[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)));
}
距离的定义可以是:
#[derive(Hash, Eq, PartialEq)]
struct Distance((u64, i16, i8));
impl Distance {
fn new(val: f64) -> Distance {
Distance(integer_decode(val))
}
}
此变体也更易于使用:
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)));
}
fn main(){
让mut映射:HashMap=HashMap::new();
地图插入(距离:新的(0.4),f64:hypot(4.2,50.0));
地图插入(距离:新的(1.8),f64:hypot(2.6,50.0));
assert_eq!(map.get(&Distance::new(0.4)),Some(&f64::hypot(4.2,50.0));
}
不幸的是,浮点类型相等是:
因此散列也很难,因为相等值的散列应该相等
如果在您的情况下,您有足够小的范围将您的数字放入
i64
中,并且您可以接受精度损失,那么一个简单的解决方案是首先规范化,然后根据规范值定义equal/hash:
use std::cmp::Eq;
#[derive(Debug)]
struct Distance(f64);
impl Distance {
fn canonicalize(&self) -> i64 {
(self.0 * 1024.0 * 1024.0).round() as i64
}
}
impl PartialEq for Distance {
fn eq(&self, other: &Distance) -> bool {
self.canonicalize() == other.canonicalize()
}
}
impl Eq for Distance {}
fn main() {
let d = Distance(0.1 + 0.2);
let e = Distance(0.3);
println!("{:?} {:?} {:?}", d, e, d == e);
}
// Prints: Distance(0.30000000000000004) Distance(0.3) true
Hash
如下所示,从那时起,您可以使用Distance
作为Hash映射中的键:
impl Hash for Distance {
fn hash<H>(&self, state: &mut H) where H: Hasher {
self.canonicalize().hash(state);
}
}
fn main() {
let d = Distance(0.1 + 0.2);
let e = Distance(0.3);
let mut m = HashMap::new();
m.insert(d, "Hello");
println!("{:?}", m.get(&e));
}
// Prints: Some("Hello")
impl距离哈希{
fn散列(&self,state:&mut H),其中H:Hasher{
self.canonicalize().hash(状态);
}
}
fn main(){
设d=距离(0.1+0.2);
设e=距离(0.3);
让mut m=HashMap::new();
m、 插入(d,“你好”);
println!(“{:?}”,m.get(&e));
}
//印刷品:一些(“你好”)
警告:重申一下,只有(a)值的动态范围足够小,可以在
i64
(19位)中捕获,并且(b)动态范围是已知的静态因素,此策略才有效。幸运的是,这适用于许多常见问题,但需要记录和测试…除了阅读所有其他评论和答案之外,没有任何评论。阅读所有其他评论和答案,了解您可能不想这样做的原因。:
使用std:{collections::HashMap,hash};
#[派生(调试、复制、克隆)]
除非您了解范围(f64),否则结构不满足此条件;
除非你了解危险,否则不要这样做{
fn键(&self)->u64{
self.0.to_位()
}
}
impl hash::对dontuseThat进行哈希,除非您了解范围{
fn散列(&self,state:&mut H)
哪里
H:hash::Hasher,
{
self.key().hash(状态)
}
}
除非您了解危险,否则请为Dontus执行PartialQ{
fn eq(&自我,其他:&不了解这些变化)->bool{
self.key()==其他.key()
}
}
除非您了解范围{},否则请为Dontus执行Eq
fn main(){
设a=dont,除非您了解范围(0.1);
设b=Dont,除非您了解范围(0.2);
设c=dont,除非您了解范围(0.3);
让mut map=HashMap::new();
地图.插入(a,1);
地图.插入(b,2);
println!(“{:?}”,map.get(&a));
println!(“{:?}”,map.get(&b));
println!(“{:?}”,map.get(&c));
}
基本上,如果您想将一个f64
视为一组没有意义的位,那么我们可以将它们视为一个大小相等的位包,知道如何进行散列和按位比较
当其中一个。您真的需要按精确的距离获取对象时,请不要感到惊讶?使用浮点数作为键与测试两个是否相等(舍入错误确实会发生)一样是一个坏主意。@Shepmaster的重复:这里可能存在
f64
未实现Eq
的问题,但我认为问题更深=>即使排除NaN
,比较两个浮点数是否相等只是自找麻烦。您希望您的键有重复值吗?是否有必要通过哈希映射消除重复数据?最好将规范化为f32
,而不是乘以常数并转换为整数,因为1e-12
和2e-15
在当前方案中都映射为0
,但f32
中的值不同。它也解决了精度问题,因为它只是在比较过程中进行的。@John:也许,也许不是。这完全取决于你想考虑什么。对于以米为单位的距离测量,1e-12
是1皮米:如果1皮米的差异与任何类型的地理跟踪相关(例如),我会非常惊讶。这确实是一个领域建模决策。如果您希望保持更高的精度,那么哈希映射查找是有缺陷的,您将需要诸如边界卷、KD树等的内容……我不喜欢这种解决方案;它增加了不必要的不精确性,似乎无法很好地映射到任何领域。如果像这样的四舍五入就足够了,那么一开始就不应该使用浮点数。我不认为如果f32
加法不能产生准确的预期结果,那么哈希将受到任何影响。毕竟,没有“定律”,它要求x1+x2==x3
->x1.hash()+x2.hash()==x3.hash()
。如果舍入错误是应用程序的一个问题,则不要使用浮点,但不要声明由于舍入而无法对浮点值进行散列。使用大块头或任何生锈的东西
fn main() {
println!("{} {} {}", 0.1 + 0.2, 0.3, 0.1 + 0.2 == 0.3);
}
// Prints: 0.30000000000000004 0.3 false
use std::cmp::Eq;
#[derive(Debug)]
struct Distance(f64);
impl Distance {
fn canonicalize(&self) -> i64 {
(self.0 * 1024.0 * 1024.0).round() as i64
}
}
impl PartialEq for Distance {
fn eq(&self, other: &Distance) -> bool {
self.canonicalize() == other.canonicalize()
}
}
impl Eq for Distance {}
fn main() {
let d = Distance(0.1 + 0.2);
let e = Distance(0.3);
println!("{:?} {:?} {:?}", d, e, d == e);
}
// Prints: Distance(0.30000000000000004) Distance(0.3) true
impl Hash for Distance {
fn hash<H>(&self, state: &mut H) where H: Hasher {
self.canonicalize().hash(state);
}
}
fn main() {
let d = Distance(0.1 + 0.2);
let e = Distance(0.3);
let mut m = HashMap::new();
m.insert(d, "Hello");
println!("{:?}", m.get(&e));
}
// Prints: Some("Hello")