英文:
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论