behavior of %f

117 views
Skip to first unread message

Dante Castagnoli

unread,
Oct 10, 2022, 12:48:56 PM10/10/22
to golang-nuts
Hi,

I'm wondering what folks think about the behavior of %f and precision.

The go fmt documentation states:
> For floating-point values, width sets the minimum width of the field and precision sets the number of places after the decimal, 

What I discovered is there is rounding going on in %f as shown below.


I found this behavior as I'm converting floats to integers, and was testing fine regions between digits, and experienced the behavior.   I didn't expect it.

I expected the behavior of printing "n" characters of a string.  However, I note that "c" behaves the same way:

$ cat math.c
#include<stdio.h>
int main(int argc, char *argv[]) {
        printf("%0.3f\n", 0.9999999999);
}
$ ./math
1.000

Brian Candler

unread,
Oct 10, 2022, 1:27:00 PM10/10/22
to golang-nuts
Whilst Go handles constants with arbitrary precision AFAIK, precision is lost once values are assigned to a float64 variable (or passed as an argument to a function).  There are only 53 bits of precision in the "mantissa".

For more info, you might find this helpful:

Dante Castagnoli

unread,
Oct 10, 2022, 6:01:31 PM10/10/22
to golang-nuts
Thanks!

It's not lost, though.  It shows up with the %g form.

Knowing about %g, my issue is not extant, but others might hit it.  I'm relatively new to floating point issues, and I saw numbers showing up as integers, and it took a while to sort out what was going on, is all.

As the behavior matches "c", I would suggest at minimum making the documentation clearer.

Rob Pike

unread,
Oct 10, 2022, 7:23:57 PM10/10/22
to Dante Castagnoli, golang-nuts
This behavior is how floating-point math works, which is not best addressed in the documentation for the formatted I/O library. We see may comments from people new to floating point who argue that there are bugs in Go's arithmetic, but it's invariably a lack of familiarity with the—admittedly rather tricky—properties of imprecise arithmetic.

-rob


--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/4a15ac2c-e30d-4d6e-be8f-251996fb01cen%40googlegroups.com.

Dante Castagnoli

unread,
Oct 10, 2022, 7:35:59 PM10/10/22
to Rob Pike, golang-nuts
Thanks, Rob,

As the behavior is as in "C", the behavior is as it should be.  I also understand that "1" is a closer representation than 0.9999 should the number be 0.99999, though it's a choice as it diverges from string-type formats.  Regardless of whether the choice was right or wrong, it is immaterial in my view, as any change would confuse those familiar with the current behavior.

I do think the documentation I cited above could be clearer, and without difficulty.  The documentation of "precision" leads one to believe that additional digits will be printed, leaving one with the impression those digits will be as they are in the float, and not rounded.

Thanks for the response.  My view of the issue hasn't really changed given the responses.  I'm simply considering how this might be easier for others, as I now understand the behavior.  That's all.

D.


robert engels

unread,
Oct 10, 2022, 8:17:08 PM10/10/22
to Dante Castagnoli, Rob Pike, golang-nuts
I will add that this is why most financial applications using “fixed place” or “integer math” libraries - to have control over these sorts of issues.

An example is robaho/fixed

Sean Liao

unread,
Oct 10, 2022, 8:34:33 PM10/10/22
to Dante Castagnoli, Rob Pike, golang-nuts
> leaving one with the impression those digits will be as they are in the float, and not rounded.

This is a fundamental misunderstanding of what a float is. The rounding happens the moment a value is stored in a float. The printed value is exactly what is stored.

- sean

Dante Castagnoli

unread,
Oct 10, 2022, 8:40:55 PM10/10/22
to Sean Liao, Rob Pike, golang-nuts
Sean,

Your comment is not correct.  You can take the original program I wrote and increase the digits and will notice they are printed as they are meant to be.

Also, if you were to take an int(f), you will note that it returns "0", and not "1".

Sorry!  Incorrect information ought to be corrected.  Run some tests to convince yourself.

D

Dante Castagnoli

unread,
Oct 10, 2022, 8:47:16 PM10/10/22
to Sean Liao, Rob Pike, golang-nuts
Sean, Rob,

You can see what i mean here.

Note, even stranger is that %0.3g seems to have identical behavior to %0.3f, but %g and %f differ.

What is the purpose of %0.ng if it mirrors %0.nf?

D

Bakul Shah

unread,
Oct 10, 2022, 9:08:08 PM10/10/22
to Dante Castagnoli, golang-nuts
On Oct 10, 2022, at 5:40 PM, Dante Castagnoli <dante.ca...@gmail.com> wrote:
>
> Also, if you were to take an int(f), you will note that it returns "0", and not "1".

%0.3f does *rounding*, while int(f) does *truncation*.
1.0 is closer to 0.9999... than 0.999 is to 0.9999...

robert engels

unread,
Oct 10, 2022, 9:09:39 PM10/10/22
to Bakul Shah, Dante Castagnoli, golang-nuts
He was using that as an example to show that the value contained in the float is not 1, but less than 1, even though the print shows it as 1.
> --
> You received this message because you are subscribed to the Google Groups "golang-nuts" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/B51E4191-D268-4F17-BD50-1FEB59B94379%40bitblocks.com.

Patrick Smith

unread,
Oct 10, 2022, 9:11:01 PM10/10/22
to Dante Castagnoli, Sean Liao, Rob Pike, golang-nuts
On Mon, Oct 10, 2022 at 5:47 PM Dante Castagnoli <dante.ca...@gmail.com> wrote:
What is the purpose of %0.ng if it mirrors %0.nf?

 %g differs from %f when printing large or small values. https://go.dev/play/p/lxj9fn19kOO

Patrick Smith

unread,
Oct 10, 2022, 9:26:12 PM10/10/22
to Dante Castagnoli, Sean Liao, Rob Pike, golang-nuts
This program (https://go.dev/play/p/w-QE790dGcs)

func main() {
    f := 0.999
    fmt.Printf("%0.2f %0.3f %0.4f\n", f, f, f)
    fmt.Println(f)
    fmt.Printf("%0.64f\n", f)
}

prints

1.00 0.999 0.9990
0.999
0.9989999999999999991118215802998747676610946655273437500000000000

The last line prints enough places so that it can show the actual value stored in f. Note that this is _not_ 0.999; it is the closest approximation that can be stored in a float64.

The first line prints the best possible approximation to this value (not to 0.999) that can be done with 2, 3, or 4 decimal places.

The middle line prints a value that, when converted to float64, will yield the actual value stored in f. Of course, by construction of this program, 0.999 will do and is shorter than printing the actual value.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Dante Castagnoli

unread,
Oct 10, 2022, 9:50:28 PM10/10/22
to Patrick Smith, Sean Liao, Rob Pike, golang-nuts

For posterity, here is the behavior of various formatting %f and %g options.


It appears to match "C", and so my comment remains as I stated, which is that the behavior should not change, but the documentation could be better.

D

Patrick Smith

unread,
Oct 10, 2022, 11:17:46 PM10/10/22
to Dante Castagnoli, Sean Liao, Rob Pike, golang-nuts
On Mon, Oct 10, 2022 at 6:10 PM Patrick Smith <pat42...@gmail.com> wrote:
 %g differs from %f when printing large or small values. https://go.dev/play/p/lxj9fn19kOO

For the sake of completeness, I should add that they can also differ for "normal" sized values: https://go.dev/play/p/xiFwOrK8Tv6
With %f, one specifies the number of fractional decimal places to appear; with %g, the number of significant figures. 

Dante Castagnoli

unread,
Oct 10, 2022, 11:39:31 PM10/10/22
to Patrick Smith, Sean Liao, Rob Pike, golang-nuts
Yah.  This appears to be ancient behavior, at a greenfield time.  If I wasn't clear, I think that's the correct choice for golang, to be compatible.

Golang Doc states:

For floating-point values, width sets the minimum width of the field and precision sets the number of places after the decimal, if appropriate, except that for %g/%G precision sets the maximum number of significant digits (trailing zeros are removed). For example, given 12.345 the format %6.3f prints 12.345 while %.3g prints 12.3. The default precision for %e, %f and %#g is 6; for %g it is the smallest number of digits necessary to identify the value uniquely.


Here is the %0.3g output.  I don't see how it's possible to divine that from the description, for instance:

fmt.Printf("%0.3g\n", 0.0000001)
printf("%0.3g\n", 0.00000001)
1e-07

In any event, I've said my peace.  I think the description could be tightened up, but the behavior is historical and ought not be changed.

-D



Marvin Renich

unread,
Oct 11, 2022, 7:48:41 AM10/11/22
to golan...@googlegroups.com
* Dante Castagnoli <dante.ca...@gmail.com> [221010 18:01]:
> Thanks!
>
> It's not lost, though. It shows up with the %g form.
>
> Knowing about %g, my issue is not extant, but others might hit it. I'm
> relatively new to floating point issues, and I saw numbers showing up as
> integers, and it took a while to sort out what was going on, is all.
>
> As the behavior matches "c", I would suggest at minimum making the
> documentation clearer.
>
> https://go.dev/play/p/WBlf3jltimr

Your comparison is flawed. Take your original playground code (the one
with just %0.3f) and change the "f" to "g" without removing the "0.3"
and run it again. You will see the same rounding behavior as with "f".

The documentation says:

The default precision ... for %g ... is the smallest number of digits
necessary to identify the value uniquely.

So without explicitly specifying the precision, %g will print as many
digits as necessary (i.e. enough precision is used to prevent rounding).

What representation, using only the precision specified, most closely
represents the value being formatted? That is how you should think of
the floating point formatting behavior, rather than thinking of it as
rounding.

...Marvin

Dante Castagnoli

unread,
Oct 11, 2022, 9:04:53 AM10/11/22
to golan...@googlegroups.com
Marvin,

Yours is a better way of describing the behavior.

Now, how does one get that precise definition in the documentation?






--
You received this message because you are subscribed to a topic in the Google Groups "golang-nuts" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/golang-nuts/1nSnnJEj1Jc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to golang-nuts...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/Y0VX94m6RoYs5X8i%40basil.wdw.
Reply all
Reply to author
Forward
0 new messages