# Format f32 with correct precision

I'm a bit confused by the way floating point numbers are converted to strings. There is some conversion of data to and from strings involved (using `serde_json`, but I think this is not relevant). I see that some precision is lost and I'm wondering how I can do this correctly.

As an example, suppose I have this program:

``````fn main() {
let f: f32 = 67108872.0;
println!("{:.1}\n{}", f, f.to_string());
}
``````

What it prints is

``````67108872.0
67108870
``````

Note the difference in the last digit. I certainly expect the result of the first line. My problem is that I'm not able to know the precision I'd have to put in the format string (`.1` in this case) beforehand. Other examples that fail to print correctly use more decimal places, this integer is just an example.

What I need is a way to print the `f32` values in a way that does not loose precision.

I've also tried dtoa, but results are the same.

This is not a problem with printing. This is inherent: `f32` has 24 bits of mantissa, which means the only integers it can represent exactly are those less than around 16 million in absolute value. You have 67 million, so the number you have is not exactly representable as an `f32`. So your expectation to print these kinds of numbers exact to 1 decimal digit (i.e., a total of 9 significant digits) is impossible to satisfy.

5 Likes

But why does the print with format `{:.1}` work then? This is what confuses me most. If `67108872.0` would not fit into a `f32`, why is it printed correctly in the first line then?

It doesn't "work", you just happened to be lucky. Or, rather, it's not printed exactly. If you try the same code with the value `67108873.0_f32`, you will also get `67108872` when you ask the system to print it with excessive precision, and `67108870` when printed with automatic precision. This is because all three values round to the same internal representation in `f32`, and when you ask for a too high number of significant digits, then it will try to squeeze out more digits, which, however, aren't meaningful.

5 Likes

You're just lucky in that case, since a lot of numbers round to 67108872.
to_string probably rounds to the least integer number of digits of precision. Even though there is a fractional amount of precision left over, it is not reliable to print another digit.

``````fn main() {
let f0: f32 = 67108872.0;
for i in 0..10 {
let f = f0 + (i as f32);
println!("{} {:.1}  {}", i, f, f.to_string());
}
}

0 67108872.0  67108870
1 67108872.0  67108870
2 67108872.0  67108870
3 67108872.0  67108870
4 67108880.0  67108880
5 67108880.0  67108880
6 67108880.0  67108880
7 67108880.0  67108880
8 67108880.0  67108880
9 67108880.0  67108880
``````

edit: I basically said the same thing as H2CO3.

2 Likes

Try out this site: https://float.exposed/0x4c800001

You can flip the last bit of the representation and see that it changes the value by 8, not by one.

So, for example, `67108869.0` and `67108875.90215694861897` with both give you the same `f32` as the `67108872.0` that you're using.

8 Likes

Ok, thank you all, especially for your thorough explanations. I just learned a lot about floating point numbers 1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.