Search code examples
hashrustkeyunsafe

Why does transmuting a f64 into u64 and then back into f64 result in a different value?


I have a unique scenario where I want to use f64 as a key in a HashMap. In particular I know the f64 will never be NaN and I can tolerate f64s that should be equal, but are not. So I transmute() the f64 to u64. However, when I pull the u64 out the HashMap and transmute() it back to a f64 it is a different value. Code below and on playground.

use std::collections::HashMap;

fn main() {
    let x = 5.0;
    let y: u64 = unsafe { std::mem::transmute(x) };
    let x: f64 = unsafe { std::mem::transmute(y) };
    println!("y: {}, x: {}", y, x);
    let mut hash = HashMap::new();
    hash.insert(y, 8);
    for (y, _) in &hash {
        let x: f64 = unsafe { std::mem::transmute(y) };
        println!("y: {}, x: {}", y, x);
    }
}

What am I missing?


Solution

  • When you write for (y, _) in &hash, y will be a reference to the key, which you will then transmute to a meaningless float.

    Should you write for (&y, _) in &hash or use *y, you'd get the expected value.

    Transmuting the wrong thing is why one should never infer the types when using transmute, and should always avoid transmute. In particular, for this specific transformation, there are the safe methods f64::to_bits and f64::from_bits. An even more idiomatic way would be to use a HashMap<FloatWrapper, Value> where FloatWrapper implements Ord.