fmt.Printf在使用%g格式时,带有宽度和精度字段的行为出现了意外情况。

huangapple go评论86阅读模式
英文:

fmt.Printf with width and precision fields in %g behaves unexpectedly

问题

我正在尝试使用fmt.Printf()函数以相同的宽度格式化一些浮点数。

例如,给定浮点数值0.0606060606060606、0.3333333333333333、0.05、0.4和0.1818181818181818,我希望将每个值格式化为10个符文:

0.06060606
0.33333333
      0.05
       0.4
0.18181818

但我无法理解如何实现。文档中说:

> 对于浮点数值,width设置字段的最小宽度,precision设置小数点后的位数(如果适用),但对于%g/%G,它设置总位数。例如,给定123.45,格式%6.2f打印123.45,而%.4g打印123.5。%e和%f的默认精度为6;%g的默认精度是最小的位数,足以唯一标识该值。

因此,如果我使用%f,一个较大的数字将无法适应10个字符的限制,因此需要使用%g。要获得最小宽度为10,可以使用%10g,要获得最大9位数字(加1个小数点),可以使用%.9g,但将它们组合在%10.9g中并不像我期望的那样工作:

0.0606060606
0.333333333
      0.05
       0.4
0.181818182

为什么我得到的字符串有10个符文,其他的有11个符文,还有一些有12个符文?

特别是,似乎%.9g并没有产生总共9位数字。例如,请参见:http://play.golang.org/p/ie9k8bYC7r

英文:

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

答案1

得分: 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.

答案2

得分: 1

首先,我们需要正确理解文档:

> width 设置字段的最小宽度,precision 设置小数点后的位数(如果适用),但对于 %g/%G,它设置总位数。

这句话在语法上是正确的,但是最后一部分的 it 真的令人困惑:实际上它指的是 precision,而不是 width

因此,让我们看一些例子:

123.45
12312.2
1.6069
0.6069
0.0006069

如果你使用 fmt.Printf("%.4g") 来打印它,结果是:

123.5
1.231e+04
1.607
0.6069
0.0006069

只有 4 个 数字,不包括所有的小数点和指数。但是等等,最后两个例子发生了什么?难道不是超过了 5 个数字吗?

这是打印中令人困惑的部分:前导的 0 不会被计算为数字,并且当少于 4 个零时不会被缩小

让我们通过下面的例子来看待 0 的行为:

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)
}

输出结果为:

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

因此,你可以看到,当前导的 0 少于 4 个时,它们将被计算,并且如果超过了 4 个,则会被缩小。

好的,下一件事是 width。根据文档,width 只指定最小宽度,包括小数点和指数。这意味着,如果你的数字比 width 指定的更长,它将超出宽度。

记住,宽度是在最后一步考虑的,这意味着它需要首先满足精度字段

让我们回到你的情况。你指定了 %10.9g,这意味着你想要总共 9 个数字,不包括前导的 0,并且最小宽度为 10,包括小数点和指数,而精度应该优先考虑。

0.0606060606060606:取 9 个数字,不包括前导的 0,将得到 0.0606060606,因为它已经是 12 宽度,它通过了最小宽度 10;

0.3333333333333333:取 9 个数字,不包括前导的 0,将得到 0.333333333,因为它已经是 11 宽度,它通过了最小宽度 10;

0.05:取 9 个数字,不包括前导的 0,将得到 0.05,因为它小于宽度 10,它将填充另外 6 个宽度以达到宽度 10;

0.4:与上面相同;

0.1818181818181818:取 9 个数字,不包括前导的 0,将得到 0.181818182,带有四舍五入,因为它已经是 11 宽度,它通过了最小宽度 10。

这就解释了为什么你得到了奇怪的打印结果。

英文:

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.

huangapple
  • 本文由 发表于 2016年4月7日 07:25:22
  • 转载请务必保留本文链接:https://go.coder-hub.com/36464068.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定