golang.org/x/text/message
可以针对 Unicode CLDR 中的任何语言使用本地化格式进行打印:package main
import (
"golang.org/x/text/language"
"golang.org/x/text/message"
)
func main() {
p := message.NewPrinter(language.English)
p.Printf("%d\n", 1000)
// Output:
// 1,000
}
Sprintf
而不是 Printf
。 - Eagle我也写了一份库,用于处理人类可读性等问题。
以下是一些示例结果:
0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
使用示例:
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
当前的fmt印刷动词都不支持千位分隔符。
golang.org/x/text/message
代替。请参见我的答案。 - dolmen前言:我在 github.com/icza/gox
上发布了一个带有更多自定义功能的实用程序,查看 fmtx.FormatInt()
。
fmt
包不支持分组小数。
我们必须自己实现(或使用现有的实现)。
以下是一个紧凑而高效的解决方案(请参见解释):
在 Go Playground 上尝试。
func Format(n int64) string {
in := strconv.FormatInt(n, 10)
numOfDigits := len(in)
if n < 0 {
numOfDigits-- // First character is the - sign (not a digit)
}
numOfCommas := (numOfDigits - 1) / 3
out := make([]byte, len(in)+numOfCommas)
if n < 0 {
in, out[0] = in[1:], '-'
}
for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
out[j] = in[i]
if i == 0 {
return string(out)
}
if k++; k == 3 {
j, k = j-1, 0
out[j] = ','
}
}
}
测试一下:
for _, v := range []int64{0, 1, 12, 123, 1234, 123456789} {
fmt.Printf("%10d = %12s\n", v, Format(v))
fmt.Printf("%10d = %12s\n", -v, Format(-v))
}
输出:
0 = 0
0 = 0
1 = 1
-1 = -1
12 = 12
-12 = -12
123 = 123
-123 = -123
1234 = 1,234
-1234 = -1,234
123456789 = 123,456,789
-123456789 = -123,456,789
基本上,Format()
函数的作用是对数字进行格式化而不使用分组,然后创建足够大的另一个片段,并在必要时插入逗号 (','
) 分组符号来复制数字的位数(如果有更多的数字,则在每 3 个数字之后)。同时注意保留负号。
输出长度:
基本上,输出长度等于输入长度加上要插入的分组符号的数量。插入分组符号的数量为:
numOfCommas = (numOfDigits - 1) / 3
由于输入字符串是一个只包含数字('0..9'
)和可选负号('-'
)的数字,因此字符以 1 对 1 的方式映射到 UTF-8 编码中的字节中(这就是 Go 在内存中存储字符串的方式)。 因此,我们可以直接使用字节而不是符文。 因此,数字的位数是输入字符串的长度,如果数字为负,则可选地减去 1:
numOfDigits := len(in)
if n < 0 {
numOfDigits-- // First character is the - sign (not a digit)
}
因此,分组符号的数量:
numOfCommas := (numOfDigits - 1) / 3
因此,输出的切片将是:out := make([]byte, len(in)+numOfCommas)
处理负号字符:
如果数字是负数,我们只需从输入字符串中去掉负号并手动将符号位复制到输出中:
if n < 0 {
in, out[0] = in[1:], '-'
}
因此,该函数的其余部分不需要知道/关心可选的负号字符。func Format2(n int64) string {
if n < 0 {
return "-" + Format2(-n)
}
in := strconv.FormatInt(n, 10)
numOfCommas := (len(in) - 1) / 3
out := make([]byte, len(in)+numOfCommas)
for i, j, k := len(in)-1, len(out)-1, 0; ; i, j = i-1, j-1 {
out[j] = in[i]
if i == 0 {
return string(out)
}
if k++; k == 3 {
j, k = j-1, 0
out[j] = ','
}
}
}
基本上这个函数通过递归调用处理负数:如果这个数字是负数,它会使用绝对值递归调用自己并在结果前加上一个"-"
字符串。
append()
和切片以下是另一个版本,使用内置的append()
函数和切片操作。相对容易理解但性能不如上一个版本:
func Format3(n int64) string {
if n < 0 {
return "-" + Format3(-n)
}
in := []byte(strconv.FormatInt(n, 10))
var out []byte
if i := len(in) % 3; i != 0 {
if out, in = append(out, in[:i]...), in[i:]; len(in) > 0 {
out = append(out, ',')
}
}
for len(in) > 0 {
if out, in = append(out, in[:3]...), in[3:]; len(in) > 0 {
out = append(out, ',')
}
}
return string(out)
}
第一个if
语句处理第一个可选的、不完整的组,如果存在并且少于3个数字,则后续的for
循环将处理其余部分,每次迭代复制3个数字并添加逗号(','
)分组符号。
0
来检测前导连字符是巧妙的,但也许过于聪明了一半。我更喜欢在辅助函数中引入一个显式分支,就像 commaCount
函数所示。我还提供了 altCommaCount
来计算不使用字符串转换的计数,但在您的情况下,您无论如何都要创建字符串,所以这并不值得。https://play.golang.org/p/NO5bAHs1lo - sehif
,这里的目的是让代码变得简短、紧凑和高效。 - icza我在Github上发布了一个Go代码片段,用于根据用户指定的千位分隔符、小数点分隔符和小数精度呈现数字(float64或int)。
https://gist.github.com/gorhill/5285193
用法: s := RenderFloat(format, n)
format参数告诉如何呈现数字n。
格式字符串示例,给定n = 12345.6789:
"#,###.##" => "12,345.67" "#,###." => "12,345" "#,###" => "12345,678" "#\u202F###,##" => "12 345,67" "#.###,###### => 12.345,678900 "" (默认格式) => 12,345.67
import (
"regexp"
)
func formatCommas(num int) string {
str := fmt.Sprintf("%d", num)
re := regexp.MustCompile("(\\d+)(\\d{3})")
for n := ""; n != str; {
n = str
str = re.ReplaceAllString(str, "$1,$2")
}
return str
}
例子:
fmt.Println(formatCommas(1000))
fmt.Println(formatCommas(-1000000000))
输出:
1,000
-1,000,000,000
func NumberToString(n int, sep rune) string {
s := strconv.Itoa(n)
startOffset := 0
var buff bytes.Buffer
if n < 0 {
startOffset = 1
buff.WriteByte('-')
}
l := len(s)
commaIndex := 3 - ((l - startOffset) % 3)
if (commaIndex == 3) {
commaIndex = 0
}
for i := startOffset; i < l; i++ {
if (commaIndex == 3) {
buff.WriteRune(sep)
commaIndex = 0
}
commaIndex++
buff.WriteByte(s[i])
}
return buff.String()
}
[]byte
而不是bytes.Buffer
:https://play.golang.org/p/fkg7FsquII - dolmen我对早期答案提供的解决方案的性能感到兴趣,并编写了带有基准测试的测试,其中包括我的两个代码片段。以下结果是在 MacBook 2018 上测量的,i7 2.6GHz:
+---------------------+-------------------------------------------+--------------+
| Author | Description | Result |
|---------------------|-------------------------------------------|--------------|
| myself | dividing by 1,000 and appending groups | 3,472 ns/op |
| myself | inserting commas to digit groups | 2,662 ns/op |
| @icza | collecting digit by digit to output array | 1,695 ns/op |
| @dolmen | copying digit groups to output array | 1,797 ns/op |
| @Ivan Tung | writing digit by digit to buffer | 2,753 ns/op |
| @jchavannes | inserting commas using a regexp | 63,995 ns/op |
| @Steffi Keran Rani, | using github.com/dustin/go-humanize | 3,525 ns/op |
| @abourget, @Dustin | | |
| @dolmen | using golang.org/x/text/message | 12,511 ns/op |
+---------------------+-------------------------------------------+--------------+
其他手写的解决方案也很快,您不会后悔选择任何一个,除了使用regexp。 使用regexp需要长度最短的代码片段,但性能非常差,不值得。
我对这个话题的贡献,您可以在playground中尝试运行:
func formatInt(number int) string {
output := strconv.Itoa(number)
startOffset := 3
if number < 0 {
startOffset++
}
for outputIndex := len(output); outputIndex > startOffset; {
outputIndex -= 3
output = output[:outputIndex] + "," + output[outputIndex:]
}
return output
}
package main
import (
"fmt"
)
func IntComma(i int) string {
if (i < 0) {
return "-" + IntComma(-i)
}
if (i < 1000) {
return fmt.Sprintf("%d",i)
}
return IntComma(i / 1000) + "," + fmt.Sprintf("%03d",i % 1000)
}
func main() {
fmt.Println(IntComma(1234567891234567))
}
这是关于基准测试的内容:实现与icza非常相似。
func IntCommaB(num int) string {
str := strconv.Itoa(num)
l_str := len(str)
digits := l_str
if num < 0 {
digits--
}
commas := (digits + 2) / 3 - 1
l_buf := l_str + commas
var sbuf [32]byte // pre allocate buffer at stack rather than make([]byte,n)
buf := sbuf[0:l_buf]
// copy str from the end
for s_i, b_i, c3 := l_str-1, l_buf-1, 0; ; {
buf[b_i] = str[s_i]
if s_i == 0 {
return string(buf)
}
s_i--
b_i--
// insert comma every 3 chars
c3++
if c3 == 3 && (s_i > 0 || num>0) {
buf[b_i] = ','
b_i--
c3 = 0
}
}
}
使用输入数字-1234567890123456789时,它比icza的方案约快15%。