我发现了个问题,在 Rust 里面浮点数 f32、f64 是无法作为元素插入 HashSet<T> 和 HashMap<K, V> 里面的:
#![allow(unused)]
use std::collections::HashSet;
fn main() {
let mut floats = HashSet::new();
floats.insert(0.0); // 这一段必报错
}
原因是 HashSet<T> 和 HashMap<K, V>,T、K、V 必须得实现 Eq + Hash trait,然而 f32、f64 仅实现了 PartialEq,因为 NaN 和 Infinite 无法与任何正常浮点数比较。
然而又不能为 f32、f64 手动实现 Eq:
impl std::ops::Eq for i32 {} // 必报错
不过我们可以绕开这一机制,用 struct 的 New Type 模式封装 f32:
#[derive(Debug)
pub struct F32(f32);
然后手动为 F32 实现 PartialEq + Eq + Hash:
use std::cmp::{Eq, PartialEq};
use std::hash::{Hash, Hasher};
impl PartialEq for F32 {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.0 == 0.0 {
other.0 == 0.0
} else {
(self.0.is_normal() && other.0.is_normal()) || self.0 == other.0
}
}
}
impl Eq for F32 {}
impl Hash for F32 {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
这下就可以了:
let floats = HashSet::new();
floats.insert(F32(0.0));
我们可以为 F32 实现 AsRef 和 Deref,你懂的,然后再为 f64 实现同样的内容:
#![allow(unused)]
use std::collections::HashSet;
use std::cmp::{PartialEq, Eq};
use std::hash::{Hash, Hasher};
use std::ops::Deref;
use std::convert::AsRef;
macro_rules! float_impl {
(
$(
$outer:ident($inner:ty),
)*
) => {
$(
#[derive(Debug)]
pub struct $outer($inner);
impl PartialEq for $outer {
#[inline]
fn eq(&self, other: &Self) -> bool {
if self.0 == 0.0 {
other.0 == 0.0
} else {
(self.0.is_normal() && other.0.is_normal()) || self.0 == other.0
}
}
}
impl Deref for $outer {
type Target = $inner;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<$inner> for $outer {
#[inline]
fn as_ref(&self) -> &$inner {
&self.0
}
}
impl Eq for $outer {}
impl Hash for $outer {
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.to_bits().hash(state);
}
}
)+
};
}
float_impl! {
F32(f32),
F64(f64),
}
fn main() {
let mut floats = HashSet::new();
floats.insert(F32(0.0));
println!("{floats:?}");
}
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END
- 最新
- 最热
只看作者