f64 to f32 cast seems to lose much precision


#1

I discovered that when I convert a timestamp stored in a f64 to a f32 a test of mine that depends on timing breaks.

Is this normal?

let val = 1452089033.7674935_f64;
println!("{}", val);
println!("{}", val as f32);

Prints:

1452089033.7674935
1452089100

My rust is:

$ rustc -V
rustc 1.7.0-nightly (8f11a9ef4 2016-01-03)

#2

It’s losing as much precision as it’s supposed to. The simplest way to check this is to just look at the numbers. Run the following (or run it in the playpen):

use std::mem::transmute;

fn main() {
    let v_32 = 1452089033.7674935_f32;
    let v_64 = 1452089033.7674935_f64;

    println!("  v_32: {:?}", v_32);
    println!("  v_64: {:?}", v_64);
    
    unsafe {
        {
            let m = ((transmute::<_, u32>(v_32) & 0x7fffff) as u64) << (52-23);
            println!("v_32.m: 0b{:052b}", m);
            println!("  used: 0b{:052b}", 0x7fffffu64 << (52-23));
        }
        {
            let m = (transmute::<_, u64>(v_64) & 0xfffff_ffffffff) as u64;
            println!("v_64.m: 0b{:052b}", m);
            println!("  used: 0b{:052b}", 0xfffff_ffffffffu64);
        }
    }
}

The output is:

  v_32: 1452089100
  v_64: 1452089033.7674935
v_32.m: 0b0101101000110100011111000000000000000000000000000000
  used: 0b1111111111111111111111100000000000000000000000000000
v_64.m: 0b0101101000110100011110110010011100010001111010011101
  used: 0b1111111111111111111111111111111111111111111111111111

This extracts and shows the mantissa (i.e. fractional) part of both numbers, shifted so that they visually line up. The mantissa for v_32 is as close to v_64 as it can get given the allowable space.


#3

As a heuristic, an f32 has approximately 7 decimal digits of precision, and an f64 has about 16, so the 8 leading non-zero digits in 1452089100 are about what one might expect (using the e formatter—"{:e}"—for each makes this relationship clearer: it prints 1.4520890337674935e9 and 1.4520891e9.).