How to fmt.Printf an integer with thousands comma

43,556

Solution 1

None of the fmt print verbs support thousands separators.

Solution 2

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
}

Solution 3

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

Solution 4

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.

Solution 5

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
Share:
43,556
BrandonAGr
Author by

BrandonAGr

Updated on July 05, 2022

Comments

  • BrandonAGr
    BrandonAGr almost 2 years

    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.

  • abourget
    abourget about 9 years
    this algo is broken, it displays ",123" when passing only three numbers, and src is never declared.
  • leylandski
    leylandski about 9 years
    You're right, but it was a rushed job. I would suggest the solution posted by @IvanTung.
  • subrat71
    subrat71 over 7 years
    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/NO5bAHs1lo
  • icza
    icza over 7 years
    @sen I agree, I would also add an if in my code, purpose here was to be short, compact and efficient.
  • subrat71
    subrat71 over 7 years
    Wow, that sure creates a lot of garbage strings.
  • subrat71
    subrat71 about 7 years
    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.
  • jchavannes
    jchavannes about 7 years
    It'll run a maximum of 3 times which should be negligible for pretty much all use cases.
  • subrat71
    subrat71 about 7 years
    Three times? How do you figure? Also, how do you know all of these use cases for something so general? play.golang.org/p/MqbdnCkgQh
  • dolmen
    dolmen over 6 years
    Instead, use golang.org/x/text/message. See my own answer.
  • dolmen
    dolmen over 6 years
    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.
  • dolmen
    dolmen over 6 years
    Here is a rewrite of your function that uses []byte instead of a bytes.Buffer: play.golang.org/p/fkg7FsquII
  • Bora M. Alper
    Bora M. Alper about 6 years
    It indeed answers the question, but doesn't help at all. -1
  • dolmen
    dolmen about 6 years
    strconv.Itoa returns a string where each rune is made of only one byte. It is overkill to all utf8.RunCountInString.
  • dolmen
    dolmen about 6 years
    The cost of compiling the regexp on every call is also not negligible at all.
  • jchavannes
    jchavannes about 6 years
    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.
  • Trent
    Trent over 5 years
    @BoraM.Alper it answers the question, and helped me!
  • dolmen
    dolmen over 4 years
    @leylandski You could also just delete your answer as you realized it is just wrong.
  • icza
    icza over 4 years
    @seh I reworked the example, and removed the "clever" parts, striving for readability.
  • Ferdinand Prantl
    Ferdinand Prantl about 4 years
    I wrote tests and benchmarks for earlier answers in my answer.
  • dolmen
    dolmen over 3 years
    If you want clear code and performance is not critical, just use a proven library such as golang.org/x/text/message.
  • zimdanen
    zimdanen about 3 years
    Does this just print out, or is there a way to get this into a variable?
  • Eagle
    Eagle about 3 years
    @zimdanen If you want to get string, use Sprintf instead of Printf just like you would with standard library.