如何fmt.Printf一个带有数千个逗号的整数

Bra*_*AGr 55 string formatting printf go

Go fmt.Printf支持输出带有数千个逗号的数字吗?

fmt.Printf("%d", 1000)输出1000,我可以指定哪种格式输出1,000

文档似乎不提逗号,我不能马上看到任何来源.

dol*_*men 53

用于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
}
Run Code Online (Sandbox Code Playgroud)


Dus*_*tin 46

我为此写了一个图书馆以及其他一些人为代表的问题.

示例结果:

0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000
Run Code Online (Sandbox Code Playgroud)

用法示例:

fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
Run Code Online (Sandbox Code Playgroud)

  • 那么,你对"人"的定义似乎仅限于习惯于用英语阅读数字的人.有关更广泛的定义,请参阅我自己使用`golang.org/x/text/message`的答案. (6认同)

zzz*_*zzz 24

没有fmt打印动词支持数千个分隔符.

  • 相反,使用`golang.org/x/text/message`.看看我自己的答案. (6认同)
  • 它确实回答了这个问题,但根本没有帮助.-1 (6认同)

R. *_*ill 12

我在Github上发布了一个Go片段,用于根据用户指定的千位分隔符,小数分隔符和小数精度呈现数字(float64或int).

https://gist.github.com/gorhill/5285193

Usage: s := RenderFloat(format, n)

The format parameter tells how to render the number n.

Examples of format strings, given n = 12345.6789:

"#,###.##" => "12,345.67"
"#,###." => "12,345"
"#,###" => "12345,678"
"#\u202F###,##" => "12?345,67"
"#.###,###### => 12.345,678900
"" (aka default format) => 12,345.67

  • 喜欢这个执照! (4认同)

icz*_*cza 11

fmt程序包不支持分组小数.

我们必须自己实施(或使用现有的).

代码

这是一个紧凑而真正有效的解决方案(见后面的解释):

Go Playground尝试一下.

func Format(n int64) string {
    in := strconv.FormatInt(n, 10)
    out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)
    if in[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] = ','
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

测试它:

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))
}
Run Code Online (Sandbox Code Playgroud)

输出:

         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
Run Code Online (Sandbox Code Playgroud)

说明:

基本上该Format()函数的作用是在没有分组的情况下格式化数字,然后创建足够大的其他切片并','在必要时复制插入逗号()分组符号的数字的数字(如果有更多数字,则在数字组3之后)同时取关心要保留的负号.

输出的长度:

它基本上是输入的长度加上要插入的分组符号的数量.分组标志的数量是:

numOfCommas = (numOfDigits - 1) / 3
Run Code Online (Sandbox Code Playgroud)

由于输入字符串是一个只包含数字('0..9')和可选的负号('-')的数字,因此字符只是以UTT-8编码的1对1方式映射到字节(这就是Go存储字符串的方式)记忆).所以我们可以简单地使用字节而不是符文.因此,位数是输入字符串长度,可选地减去1符号数字'-'.

如果有一个符号数字,它将在in[0].数值'-'45,而数字字符的数值'0'..'9'48..57.因此符号字符小于可能的数字.因此,如果我们将第一个字符(总是至少有1个字符)除以'0',我们得到0它是否为负号并且1是否为数字(整数除法).

所以输入字符串中的位数是:

numOfDigits = len(in) - 1 + int(in[0]/'0')
Run Code Online (Sandbox Code Playgroud)

因此分组标志的数量:

numOfCommas = (len(in) - 2 + int(in[0]/'0')) / 3
Run Code Online (Sandbox Code Playgroud)

因此输出切片将是:

out := make([]byte, len(in)+(len(in)-2+int(in[0]/'0'))/3)
Run Code Online (Sandbox Code Playgroud)

处理负号字符:

如果数字为负数,我们只需将输入字符串切片以将其排除在处理之外,我们手动将符号位复制到输出:

if in[0] == '-' {
    in, out[0] = in[1:], '-'
}
Run Code Online (Sandbox Code Playgroud)

因此,函数的其余部分不需要知道/关心可选的负号字符.

该函数的其余部分是一个for循环,它只是将数字的字节(数字)从输入字符串复制到输出,','如果有更多数字,则在每组3个数字后插入分组符号().循环向下,因此更容易跟踪3位数的组.完成后(不再有数字),输出字节片作为a返回string.

变化

使用递归处理否定

如果你不太关心效率和更多可读性,你可能会喜欢这个版本:

func Format2(n int64) string {
    if n < 0 {
        return "-" + Format2(-n)
    }
    in := strconv.FormatInt(n, 10)
    out := make([]byte, len(in)+(len(in)-1)/3)

    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] = ','
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上,它使用递归调用处理负数:如果数字为负数,则使用绝对(正)值调用自身(递归),并使用"-"字符串预先设置结果.

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)
}
Run Code Online (Sandbox Code Playgroud)

第一个if语句处理第一个可选的"不完整"组,如果存在则小于3位,后续for循环处理其余的,在每次迭代中复制3个数字,','如果有更多数字则附加逗号()分组符号.

  • 谢谢你的详细描述.用"0"除以前导连字符的划分很可爱,也许太聪明了一半.我更喜欢在辅助函数中引入一个显式分支,如下所示,使用`commaCount`函数.我还提供了`altCommaCount`来计算计数而不先使用字符串转换,但在你的情况下你还是要创建字符串,所以它不值得.https://play.golang.org/p/NO5bAHs1lo (2认同)

jch*_*nes 10

这是一个使用正则表达式的简单函数:

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
}
Run Code Online (Sandbox Code Playgroud)

例子:

fmt.Println(formatCommas(1000))
fmt.Println(formatCommas(-1000000000))
Run Code Online (Sandbox Code Playgroud)

输出:

1,000
-1,000,000,000
Run Code Online (Sandbox Code Playgroud)

https://play.golang.org/p/vnsAV23nUXv


小智 8

这是一个函数,它接受一个整数和分组分隔符,并返回一个用指定分隔符分隔的字符串.我试图优化效率,在紧密循环中没有字符串连接或mod/division.从我的分析来看,它比我的Mac上的人体化速度快了两倍.在我的Mac上实现(~680ns对比1642ns).我是Go的新手,很想看到更快的实现!

用法:s:= NumberToString(n int,sep rune)

例子

说明使用不同的分隔符(','vs''),使用int值范围进行验证.

s:= NumberToString(12345678,',')

>>"12,345,678"

s:= NumberToString(12345678,'')

>"12 345 678"

s:= NumberToString(-9223372036854775807,',')

=>" - 9,223,372,036,854,775,807"

功能实现

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()
}
Run Code Online (Sandbox Code Playgroud)


Fer*_*ntl 8

我对早期答案中提供的解决方案的性能感兴趣,并为它们编写了带有基准的测试,包括我的两个代码片段。以下结果是在 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 |
+---------------------+-------------------------------------------+--------------+
Run Code Online (Sandbox Code Playgroud)
  • 如果您想要最快的解决方案,请获取@icza 的代码片段。尽管它是逐位计算而不是三位数一组,但它是最快的。
  • 如果您想要最短的合理代码片段,请查看下面的我的代码片段。它增加了最快解决方案一半以上的时间,但代码却缩短了三倍。
  • 如果您想要简单且不介意使用外部库,请访问github.com/dustin/go- humanize。它比最快的解决方案慢两倍多,但该库可能会帮助您进行其他格式设置。
  • 如果您想要本地化输出,请选择golang.org/x/text/message。它比最快的解决方案慢七倍,但匹配消费者语言的奢侈并不是免费的。

其他手工编码的解决方案也很快,除了使用正则表达式之外,您不会后悔选择其中任何一个。使用regexp需要最短的代码片段,但性能如此惨烈,不值得。

我对此主题的贡献,您可以尝试在操场上运行

+---------------------+-------------------------------------------+--------------+
|       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 |
+---------------------+-------------------------------------------+--------------+
Run Code Online (Sandbox Code Playgroud)