73

Go 是否fmt.Printf支持输出带有千位逗号的数字?

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

文档似乎没有提到逗号,我无法立即在 source 中看到任何内容

4

14 回答 14

113

用于使用Unicode CLDRgolang.org/x/text/message中任何语言的本地化格式进行打印:

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
}
于 2017-10-18T13:33:30.283 回答
54

我为此编写了一个库以及其他一些人类表示问题。

示例结果:

0 -> 0
100 -> 100
1000 -> 1,000
1000000000 -> 1,000,000,000
-100000 -> -100,000

示例用法:

fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
于 2012-10-23T00:24:24.313 回答
26

fmt 打印动词都不支持千位​​分隔符。

于 2012-10-22T21:54:41.603 回答
18

前言:我在 中发布了这个具有更多自定义功能的实用程序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如果数字是负数,可以选择减去:

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循环,它只是将数字的字节(数字)从输入字符串复制到输出,','如果有更多数字,则在每组 3 位数字之后插入一个分组符号 ( )。循环向下,因此更容易跟踪 3 位数字组。完成后(不再有数字),输出字节切片作为string.

变化

递归处理负数

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

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 位,','如果有更多位,则附加一个逗号 ( ) 分组符号.

于 2015-06-25T09:27:43.583 回答
14

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

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

用法:s := RenderFloat(format, n)

格式参数告诉如何渲染数字 n。

给定 n = 12345.6789 的格式字符串示例:

"#,###.##" => "12,345.67"
“#,###。” => "12,345"
"#,###" => "12345,678"
"#\u202F###,##" => "12 345,67"
"#.###,###### => 12.345,678900
"" (又名默认格式) => 12,345.67
于 2013-04-01T14:30:50.113 回答
9

这是一个函数,它接受一个整数和分组分隔符,并返回一个用指定分隔符分隔的字符串。我试图优化效率,在紧密循环中没有字符串连接或 mod/division。根据我的分析,它比我的 Mac 上的 humanize.Commas 实现(~680ns vs 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()
}
于 2014-11-14T21:22:34.730 回答
5

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

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

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

于 2016-08-27T21:02:34.550 回答
4

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

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

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

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
}
于 2020-05-02T14:12:35.583 回答
2

使用https://github.com/dustin/go-humanize .. 它有一堆帮手来处理这些事情。除了字节,如 MiB、MB 和其他好东西。

于 2015-06-12T01:20:29.750 回答
2

这绝对不是基准测试的领导者,但谁在乎代码是否清晰且性能是否不重要?

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%

于 2020-12-18T11:13:14.273 回答
0

人性化的包装可以创造奇迹!请在此处参考此软件包的文档。要使用这个包,首先使用像 Git SCM 这样的工具安装它。如果您使用的是 Git Bash,请打开 shell 窗口并键入:

go get -u github.com/dustin/go-humanize

完成后,您可以使用以下解决方案代码(例如 main.go):

package main

import (
    "fmt"
    "github.com/dustin/go-humanize"
)

func main() {
    fmt.Println(humanize.Commaf(float64(123456789)));
    fmt.Println(humanize.Commaf(float64(-1000000000)));
    fmt.Println(humanize.Commaf(float64(-100000.005)));
    fmt.Println(humanize.Commaf(float64(100000.000)));
}

还有其他Commaf喜欢BigComma, Comma, BigCommaf等的变化,这取决于您输入的数据类型。

因此,在使用以下命令运行此程序时:

go run main.go

您将看到如下输出

123,456,789
-1,000,000,000
-100,000.005
100,000
于 2018-10-09T10:06:02.040 回答
-1

你也可以使用这个小包:https ://github.com/floscodes/golang-thousands 。

只需将您的号码转换为字符串,然后使用Separate-function 如下:

n:="3478686" // your number as a string

thousands.Separate(n, "en") // adds thousands separators. the second argument sets the language mode.
于 2020-08-04T09:39:42.467 回答
-2

如果你不想使用图书馆(无论出于何种原因),我把它搞砸了。它似乎可以工作并且可以使用任何指定的符文作为分隔符:

import (
    "strconv"
)

func delimitNumeral(i int, delim rune) string {

    src := strconv.Itoa(i)
    strLen := utf8.RuneCountInString(src)
    outStr := ""
    digitCount := 0
    for i := strLen - 1; i >= 0; i-- {

        outStr = src[i:i+1] + outStr
        if digitCount == 2 {
            outStr = string(delim) + outStr
            digitCount = 0
        } else {
            digitCount++
        }
    }

    return outStr
}

注意:经过进一步测试,此功能不能完美运行。我建议使用@IvanTung 发布的解决方案,并欢迎任何可以让我完美工作的人进行任何编辑。

于 2015-06-02T15:58:00.870 回答
-3
import ("fmt"; "strings")

func commas(s string) string {
    if len(s) <= 3 {
        return s
    } else {
        return commas(s[0:len(s)-3]) + "," + s[len(s)-3:]
    }
}

func toString(f float64) string {
    parts := strings.Split(fmt.Sprintf("%.2f", f), ".")
    if parts[0][0] == '-' {
        return "-" + commas(parts[0][1:]) + "." + parts[1]
    }
    return commas(parts[0]) + "." + parts[1]
}
于 2016-11-18T11:52:56.753 回答