6

I am trying to get some floats formatted with the same width using fmt.Printf().

For example, given the float values 0.0606060606060606, 0.3333333333333333, 0.05, 0.4 and 0.1818181818181818, I would like to get each value formatted in, say, 10 runes:

0.06060606
0.33333333
      0.05
       0.4
0.18181818

But I can't understand how it's done. Documentation says that

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 it sets the total number of digits. For example, given 123.45 the format %6.2f prints 123.45 while %.4g prints 123.5. The default precision for %e and %f is 6; for %g it is the smallest number of digits necessary to identify the value uniquely.

So, if I use %f a larger number will not fit in 10-character constraint, therefore %g is required. To get a minimum width of 10 is %10g and to get a maximum number of 9 digits (+1 for the dot) it's %.9g, but combining them in %10.9g is not behaving as I expect

0.0606060606
0.333333333
      0.05
       0.4
0.181818182

How come I get strings which are of 10 runes, others that are 11 runes and others that are 12 runes?

In particular, it seems that %.9g does not produce 9 digits in total. See for example: http://play.golang.org/p/ie9k8bYC7r

nevets
  • 4,631
  • 24
  • 40
AkiRoss
  • 11,745
  • 6
  • 59
  • 86
  • 2
    Precision (as in `%.9g`) is the number of digits in the scientific notations (i.e. leading zeros are ignored). This is not very well documented, I suggest to file a bug at github.com/golang/go. Width (as in `%10g`) is the minimum number of runes to output according to the documentation. So the output looks correct. – kostya Apr 07 '16 at 01:12
  • Ah, therefore a `0.000123456789` is still be formatted using 13 digits using `%.9g`! Damn, that's not clear from the docs... "Total number of digits" is kinda misleading! – AkiRoss Apr 07 '16 at 10:56
  • @AkiRoss you are right, Go's official documentation is really confusing on this :( I believe this question will help a lot of people. – nevets Apr 07 '16 at 13:19
  • 1
    I hope it will be useful. In general, I feel like that formatting is a not-so-straightforward topic. Here's the issue report https://github.com/golang/go/issues/15178 – AkiRoss Apr 07 '16 at 15:51
  • nice job @AkiRoss :D – nevets Apr 07 '16 at 16:50
  • 1
    Related / Possible duplicate of [Golang: is there any standard library to convert float64 to string with fix width with maximum number of significant digits?](http://stackoverflow.com/questions/36515818/golang-is-there-any-standard-library-to-convert-float64-to-string-with-fix-widt) – icza Apr 09 '16 at 18:47

2 Answers2

1

Yes, I agree: it gives precedence to the "precision fields" not to "width". So when we need fix columns for printing we need write new formatting func.

  • Simple Answer is "%10.5g" but it will not return maximum number of Significant Digits of float number –  Apr 07 '16 at 05:13
  • 1
    The OP is asking why, not looking for a solution :p – nevets Apr 07 '16 at 06:52
  • Yes, I agree. I think the fmt lib for Printf needs rewriting for providing more significant digits. With float64 which provides 16 digits of significant digits it’s a little odd to loose significant digits for any reason. And give to length filed a little more respect in %L.Dg –  Apr 08 '16 at 17:52
1

Firstly, we need to understand the documentation correctly:

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 it sets the total number of digits.

This line is grammatically correct, but the it in the last part of this sentence is really confusing: it actually refers to the precision, not the width.

Therefore, let's look at some examples:

123.45
12312.2
1.6069
0.6069
0.0006069

and you print it like fmt.Printf("%.4g"), it gives you

123.5
1.231e+04
1.607
0.6069
0.0006069

only 4 digits, excluding all decimal points and exponent. But wait, what happens to the last 2 example? Are you kidding me isn't that more than 5 digits?

This is the confusing part in printing: leading 0s won't be counted as digits, and won't be shrunk when there are less than 4 zeros.

Let's look at 0 behavior using the example below:

package main

import "fmt"

func main() {
    fmt.Printf("%.4g\n", 0.12345)
    fmt.Printf("%.4g\n", 0.012345)
    fmt.Printf("%.4g\n", 0.0012345)
    fmt.Printf("%.4g\n", 0.00012345)
    fmt.Printf("%.4g\n", 0.000012345)
    fmt.Printf("%.4g\n", 0.0000012345)
    fmt.Printf("%.4g\n", 0.00000012345)

    fmt.Printf("%g\n", 0.12345)
    fmt.Printf("%g\n", 0.012345)
    fmt.Printf("%g\n", 0.0012345)
    fmt.Printf("%g\n", 0.00012345)
    fmt.Printf("%g\n", 0.000012345)
    fmt.Printf("%g\n", 0.0000012345)
    fmt.Printf("%g\n", 0.00000012345)
}

and the output:

0.1235
0.01235
0.001234
0.0001234
1.234e-05
1.234e-06
1.235e-07
0.12345
0.012345
0.0012345
0.00012345
1.2345e-05
1.2345e-06
1.2345e-07

So you could see, when there are less than 4 leading 0s, they will be counted, and be shrunk if there are more than that.

Ok, next thing is the width. From the documentation, width only specifies the minimum width, including decimal place and exponent. Which means, if you have more digits than what width specified, it will shoot out of the width.

Remember, width will be taken account as the last step, which means it needs to first satisfy the precision field.

Let's go back to your case. You specified %10.9g, that means you want a total digit of 9, excluding the leading 0, and a min width of 10 including decimal place and exponent, and the precision should take priority.

0.0606060606060606: take 9 digits without leading 0 will give you 0.0606060606, since it's already 12 width, it passes the min width of 10;

0.3333333333333333: take 9 digits without leading 0 will give you 0.333333333, since it's already 11 width, it passes the min width of 10;

0.05: take 9 digits without leading 0 will give you 0.05, since it's less than width 10, it will pad with another 6 width to get width of 10;

0.4: same as above;

0.1818181818181818: take 9 digits without leading 0 will give you 0.181818182 with rounding, since it's already 11 width, it passes the min width of 10.

So this explains why you got the funny printing.

nevets
  • 4,631
  • 24
  • 40