Rust如何解决浮点数类型无法插入HashSet和HashMap问题

我发现了个问题,在 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
喜欢就支持一下吧
点赞11 分享
Sirius的头像-[猫狗小铺] - 您的优质资源小铺黄金会员
评论 共1条
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片