How to fmt.Printf an integer with thousands comma
Asked Answered
P

15

96

Does Go's fmt.Printf support outputting a number with the thousands comma?

fmt.Printf("%d", 1000) outputs 1000, what format can I specify to output 1,000 instead?

The docs don't seem to mention commas, and I couldn't immediately see anything in the source.

Polyadelphous answered 22/10, 2012 at 21:38 Comment(1)
The comma is a US tradition, but the US standard is to use space for thousands separators: physics.nist.gov/cuu/pdf/sp811.pdf#10.5.3Nucleoplasm
S
32

None of the fmt print verbs support thousands separators.

Sorci answered 22/10, 2012 at 21:54 Comment(4)
Instead, use golang.org/x/text/message. See my own answer.Bouse
It indeed answers the question, but doesn't help at all. -1Tarratarradiddle
@BoraM.Alper it answers the question, and helped me!Tartary
I wrote tests and benchmarks for earlier answers in my answer.Bronez
B
151

Use golang.org/x/text/message to print using localized formatting for any language in the 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
}
Bouse answered 18/10, 2017 at 13:33 Comment(5)
Does this just print out, or is there a way to get this into a variable?Cheliform
@Cheliform If you want to get string, use Sprintf instead of Printf just like you would with standard library.Faux
Why do we need to initialize stuff here? I thought this would behave like Sprintf without having to allocate memory ?Fathead
@TheRealChx101, at first glance was wondering the same, but the behavior of the printer is customizable, even after instantiation. Hence the constructor cs.opensource.google/go/x/text/+/refs/tags/v0.11.0:message/…Intaglio
Is this thread safe though? IFathead
R
59

I wrote a library for this as well as a few other human-representation concerns.

Example results:

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

Example Usage:

fmt.Printf("You owe $%s.\n", humanize.Comma(6582491))
Recrement answered 23/10, 2012 at 0:24 Comment(1)
Well, your definition of "human" seems to be limited to people who are used to read numbers in English-style. For a wider definition, see my own answer that uses golang.org/x/text/message.Bouse
S
32

None of the fmt print verbs support thousands separators.

Sorci answered 22/10, 2012 at 21:54 Comment(4)
Instead, use golang.org/x/text/message. See my own answer.Bouse
It indeed answers the question, but doesn't help at all. -1Tarratarradiddle
@BoraM.Alper it answers the question, and helped me!Tartary
I wrote tests and benchmarks for earlier answers in my answer.Bronez
E
25

Foreword: I released this utility with more customization in github.com/icza/gox, see fmtx.FormatInt().


The fmt package does not support grouping decimals.

We have to implement one ourselves (or use an existing one).

The Code

Here is a compact and really efficient solution (see explanation after):

Try it on the 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] = ','
        }
    }
}

Testing it:

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

Output:

         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

Explanation:

Basically what the Format() function does is it formats the number without grouping, then creates a big enough other slice and copies the digits of the number inserting comma (',') grouping symbol when necessary (after groups of digits of 3 if there are more digits) meanwhile taking care of the negative sign to be preserved.

The length of the output:

It is basically the length of the input plus the number of grouping signs to be inserted. The number of grouping signs is:

numOfCommas = (numOfDigits - 1) / 3

Since the input string is a number which may only contain digits ('0..9') and optionally a negative sign ('-'), the characters are simply mapped to bytes in a 1-to-1 fashion in UTF-8 encoding (this is how Go stores strings in memory). So we can simply work with bytes instead of runes. So the number of digits is the input string length, optionally minus 1 if the number is negative:

numOfDigits := len(in)
if n < 0 {
    numOfDigits-- // First character is the - sign (not a digit)
}

And therefore the number of grouping signs:

numOfCommas := (numOfDigits - 1) / 3

Therefore the output slice will be:

out := make([]byte, len(in)+numOfCommas)

Handling the negative sign character:

If the number is negative, we simply slice the input string to exclude it from processing and we manually copy the sign bit to the output:

if n < 0 {
    in, out[0] = in[1:], '-'
}

And therefore the rest of the function does not need to know/care about the optional negative sign character.

The rest of the function is a for loop which just copies the bytes (digits) of the number from the input string to the output, inserting a grouping sign (',') after every group of 3 digits if there are more digits. The loop goes downward so it's easier to track the groups of 3 digits. Once done (no more digits), the output byte slice is returned as a string.

Variations

Handling negative with recursion

If you're less concerned with efficiency and more about readability, you might like this version:

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] = ','
        }
    }
}

Basically this handles negative numbers with a recursive call: if the number is negative, calls itself (recursive) with the absolute (positive) value and prepends the result with a "-" string.

With append() slices

Here's another version using the builtin append() function and slice operations. Somewhat easier to understand but not so good performance-wise:

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

The first if statement takes care of the first optional, "incomplete" group which is less than 3 digits if exists, and the subsequent for loop handles the rest, copying 3 digits in each iteration and appending a comma (',') grouping sign if there are more digits.

Evadne answered 25/6, 2015 at 9:27 Comment(3)
Thank you for the thorough description. The division by 0 to detect the leading hyphen is cute, perhaps too clever by half. I'd prefer to just introduce an explicit branch within a helper function, as shown here with the commaCount function. I also provided altCommaCount to compute the count without using string conversion first, though in your case you're going to create the string anyway, so it's not worth it. play.golang.org/p/NO5bAHs1loJanie
@sen I agree, I would also add an if in my code, purpose here was to be short, compact and efficient.Evadne
@Janie I reworked the example, and removed the "clever" parts, striving for readability.Evadne
M
16

I published a Go snippet over at Github of a function to render a number (float64 or int) according to user-specified thousand separator, decimal separator and decimal precision.

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
Mceachern answered 1/4, 2013 at 14:30 Comment(0)
C
10

Here is a function that takes an integer and grouping separator and returns a string delimited with the specified separator. I have tried to optimize for efficiency, no string concatenation or mod/division in the tight loop. From my profiling it is more than twice as fast as the humanize.Commas implementation (~680ns vs 1642ns) on my Mac. I am new to Go, would love to see faster implementations!

Usage: s := NumberToString(n int, sep rune)

Examples

Illustrates using different separator (',' vs ' '), verified with int value range.

s:= NumberToString(12345678, ',')

=> "12,345,678"

s:= NumberToString(12345678, ' ')

=> "12 345 678"

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

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

Function Implementation

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()
}
Comp answered 14/11, 2014 at 21:22 Comment(1)
Here is a rewrite of your function that uses []byte instead of a bytes.Buffer: play.golang.org/p/fkg7FsquIIBouse
C
10

Here's a simple function using regex:

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
}

Example:

fmt.Println(formatCommas(1000))
fmt.Println(formatCommas(-1000000000))

Output:

1,000
-1,000,000,000

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

Columella answered 27/8, 2016 at 21:2 Comment(5)
That's going to allocate and copy various substrings ⌈(⌊log10 num⌋+1)/3⌉-1 extra times, not to mention the cost of scanning the string to match the regular expression repeatedly.Janie
It'll run a maximum of 3 times which should be negligible for pretty much all use cases.Columella
Three times? How do you figure? Also, how do you know all of these use cases for something so general? play.golang.org/p/MqbdnCkgQhJanie
The cost of compiling the regexp on every call is also not negligible at all.Bouse
I don't think regex is going to cause performance issues for many people. If it's really an issue you can move the MustCompile out of the function so it only occurs once. Or if you're really concerned with performance use one of the other lower level more verbose answers.Columella
B
8

I got interested in the performance of solutions offered in earlier answers and wrote tests with benchmarks for them, including two code snippets of mine. The following results were measured on 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 |
+---------------------+-------------------------------------------+--------------+
  • If you want the fastest solution, grab @icza's code snippet. Although it goes digit by digit and not by groups of three digits, it emerged as the fastest.
  • If you want the shortest reasonable code snippet, look at mine below. It adds more than half of the time of the fastest solution, but the code is three times shorter.
  • If you want a one-liner and do not mind using an external library, go for github.com/dustin/go-humanize. It is more than twice slower as the fastest solution, but the library might help you with other formatting.
  • If you want localized output, choose golang.org/x/text/message. It is seven times slower than the fastest solution, but the luxury of matching the consumer's language does not come free.

Other hand-coded solutions are fast too and you will not regret choosing any of them, except for the usage of regexp. Using regexp needs the shortest code snippet, but the performance is so tragic, that it is not worth it.

My contribution to this topic, which you can try running in the 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
}
Bronez answered 2/5, 2020 at 14:12 Comment(0)
C
3

This is definitely not a leader of the benchmarks but who cares if code is clear and the performance is not critical?

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

And this is for the benchmarks: implementation is very similar to that of 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
                }
    }
}

With input -1234567890123456789 it is about 15% faster than icza's

Conformable answered 18/12, 2020 at 11:13 Comment(1)
If you want clear code and performance is not critical, just use a proven library such as golang.org/x/text/message.Bouse
S
1

Use https://github.com/dustin/go-humanize .. it has a bunch of helpers to deal with those things. In addition to bytes as MiB, MB, and other goodies.

Silvanasilvano answered 12/6, 2015 at 1:20 Comment(0)
A
0

The package humanize can do the magic! Refer the documentation of this package here. To use this package, install it first by using a tool like Git SCM. If you are using Git Bash, open the shell window and type:

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

Once this is done, you can use the following solution code (Say, 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)));
}

There are other variations to Commaf like BigComma, Comma, BigCommaf etc. which depends on the data type of your input.

So, on running this program using the command:

go run main.go

You will see an output such as this:

123,456,789
-1,000,000,000
-100,000.005
100,000
Assignation answered 9/10, 2018 at 10:6 Comment(0)
S
0

You can also use this small package: https://github.com/floscodes/golang-thousands.

Just convert your number to a string an then use the Separate-function like this:

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

thousands.Separate(n, "en") // adds thousands separators. the second argument sets the language mode.
Sotted answered 4/8, 2020 at 9:39 Comment(0)
S
0

This solution worked for me

printer := message.NewPrinter(language.English)
printer.Sprintf("%d", item.ItemTotal)
Sliwa answered 31/12, 2023 at 18:23 Comment(1)
This answers uses golang.org/x/text/message like the top answer.Bouse
B
-2

If you don't want to use a library (for whatever reason), I knocked this up. It seems to work and can use any specified rune as a delimiter:

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
}

Note: after further testing, this function doesn't work perfectly. I would suggest using the solution posted by @IvanTung, and welcome any edits from anyone who can get mine to work perfectly.

Breban answered 2/6, 2015 at 15:58 Comment(4)
this algo is broken, it displays ",123" when passing only three numbers, and src is never declared.Silvanasilvano
You're right, but it was a rushed job. I would suggest the solution posted by @IvanTung.Breban
strconv.Itoa returns a string where each rune is made of only one byte. It is overkill to all utf8.RunCountInString.Bouse
@Breban You could also just delete your answer as you realized it is just wrong.Bouse
B
-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]
}
Bodwell answered 18/11, 2016 at 11:52 Comment(1)
Wow, that sure creates a lot of garbage strings.Janie

© 2022 - 2024 — McMap. All rights reserved.