Precision lose on printing

Hi

Why when I do that

pub fn main() {
    let a: f32 = 123456100_f32;
    println!("{} // a", a);
    println!("{:.0} // a precision", a);
    println!("{} // a as u64", a as u64);
    println!("{} // f64::from", f64::from(a));

    println!("direct u64");
    let a: f64 = 123456100_f64;
    println!("{}", a);
    println!("{}", a as u64);
}

I see

123456100 // a
123456096 // a precision
123456096 // a as u64
123456096 // f64::from
direct u64
123456100
123456100

For me it should be

123456096 // a                                     here
123456096 // a precision
123456096 // a as u64
123456096 // f64::from
direct u64
123456100
123456100

Thanks

There have been many arguments about whether this behavior is appropriate, but what {} formatting is doing is not actually losing any precision. It is printing the decimal number with the fewest contiguous nonzero places that, if parsed as a f32, produces the same f32. This makes sense when the number has a fractional part, but it is dubious when the zeroes are on the left of the decimal point. I personally think this is misleading and ought to have been different, but it’s not really incorrect. (The people who prefer this behavior argue that it more accurately depicts the floating-point number’s limited precision by depicting it as a rounder decimal number.)

123456100 is chosen because its nonzero part 1234561 has 7 digits, whereas 123456096 has 9. But they both are closest to the same f32 value.

fn parse_f32_integer(input: &str) -> u64 {
    input.parse::<f32>().unwrap() as u64
}

fn main() {
    dbg!(parse_f32_integer("123456100"));
    dbg!(parse_f32_integer("123456096"));
    dbg!(parse_f32_integer("123456092"));
}
[src/main.rs:6:5] parse_f32_integer("123456100") = 123456096
[src/main.rs:7:5] parse_f32_integer("123456096") = 123456096
[src/main.rs:8:5] parse_f32_integer("123456092") = 123456096
5 Likes

if you toggle the rightmost bit, you can see that in this range, the smallest increment is 8.

Yes I agree that it is misleading !

Thanks for the answer

Yes there is the next_up() fn for that

I think it's just a couple of magnitudes that make this seem odd, but just switch to larger or smaller exponents and I think it's the obvious one.

For example, if you keep the mantissa but drop the exponent by 100, you get 9.738969e-23 where printing it as 9.73896876e-23 I think we could agree is worse.

So to me, keeping the consistent "no unnecessary sigfigs" rule is good, scale-invariant. After all, the reason to use floating-point is to be scale-invariant. (If one wants other things then other types are better -- like how if you care about translation-invariant but not scale-invariant then fixed point is better than floating-point.)

4 Likes