How to format a number using comma as thousands separator in C?

Viewed 114269

In C, how can I format a large number from e.g. 1123456789 to 1,123,456,789?

I tried using printf("%'10d\n", 1123456789), but that doesn't work.

Could you advise anything? The simpler the solution the better.

25 Answers

Needed to do something similar myself but rather than printing directly, needed to go to a buffer. Here's what I came up with. Works backwards.

unsigned int IntegerToCommaString(char *String, unsigned long long Integer)
{
    unsigned int Digits = 0, Offset, Loop;
    unsigned long long Copy = Integer;

    do {
        Digits++;
        Copy /= 10;
    } while (Copy);

    Digits = Offset = ((Digits - 1) / 3) + Digits;
    String[Offset--] = '\0';

    Copy = Integer;
    Loop = 0;
    do {
        String[Offset] = '0' + (Copy % 10);
        if (!Offset--)
            break;
        if (Loop++ % 3 == 2)
            String[Offset--] = ',';
        Copy /= 10;
    } while (1);

    return Digits;
}

Be aware that it's only designed for unsigned integers and you must ensure that the buffer is large enough.

This is old and there are plenty of answers but the question was not "how can I write a routine to add commas" but "how can it be done in C"? The comments pointed to this direction but on my Linux system with GCC, this works for me:

#include <stdio.h>
#include <stdlib.h>
#include <locale.h>
int main()
{
    unsetenv("LC_ALL");
    setlocale(LC_NUMERIC, "");
    printf("%'lld\n", 3141592653589);
}

When this is run, I get:

$ cc -g comma.c -o comma && ./comma
3,141,592,653,589

If I unset the LC_ALL variable before running the program the unsetenv is not necessary.

Require: <stdio.h> + <string.h>.
Advantage: short, readable, based on the format of scanf-family. And assume no comma on the right of decimal point.

void add_commas(char *in, char *out) {
    int len_in = strlen(in);
    int len_int = -1;                              /* len_int(123.4) = 3 */
        for (int i = 0; i < len_in; ++i) if (in[i] == '.') len_int = i;
    int pos = 0;
    for (int i = 0; i < len_in; ++i) {
        if (i>0 && i<len_int && (len_int-i)%3==0)
            out[pos++] = ',';
        out[pos++] = in[i];
    }
    out[pos] = 0;                                  /* Append the '\0' */
}

Example, to print a formatted double:

#include <stdio.h>
#include <string.h>
#define COUNT_DIGIT_MAX 100
int main() {
    double sum = 30678.7414;
    char input[COUNT_DIGIT_MAX+1] = { 0 }, output[COUNT_DIGIT_MAX+1] = { 0 };
    snprintf(input, COUNT_DIGIT_MAX, "%.2f", sum/12);
    add_commas(input, output);
    printf("%s\n", output);
}

Output:

2,556.56

Using C++'s std::string as return value with possibly the least overhead and not using any std library functions (sprintf, to_string, etc.).

string group_digs_c(int num)
{
    const unsigned int BUF_SIZE = 128;
    char buf[BUF_SIZE] = { 0 }, * pbuf = &buf[BUF_SIZE - 1];
    int k = 0, neg = 0;
    if (num < 0) { neg = 1; num = num * -1; };

    while(num)
    {
        if (k > 0 && k % 3 == 0)
            *pbuf-- = ',';
        *pbuf-- = (num % 10) + '0';
        num /= 10;
        ++k;
    }

    if (neg)
        *pbuf = '-';
    else
        ++pbuf;

    int cc = buf + BUF_SIZE - pbuf;
    memmove(buf, pbuf, cc);
    buf[cc] = 0;
    string rv = buf;
    return rv;
}

Here is a simple portable solution relying on sprintf:

#include <stdio.h>

// assuming out points to an array of sufficient size
char *format_commas(char *out, int n, int min_digits) {
    int len = sprintf(out, "%.*d", min_digits, n);
    int i = (*out == '-'), j = len, k = (j - i - 1) / 3;
    out[j + k] = '\0';
    while (k-- > 0) {
        j -= 3;
        out[j + k + 3] = out[j + 2];
        out[j + k + 2] = out[j + 1];
        out[j + k + 1] = out[j + 0];
        out[j + k + 0] = ',';
    }
    return out;
}

The code is easy to adapt for other integer types.

There are many interesting contributions here. Some covered all cases, some did not. I picked four of the contributions to test, found some failure cases during testing and then added a solution of my own.

I tested all methods for both accuracy and speed. Even though the OP only requested a solution for one positive number, I upgraded the contributions that didn't cover all possible numbers (so the code below may be slightly different from the original postings). The cases that weren't covered include: 0, negative numbers and the minimum number (INT_MIN).

I changed the declared type from "int" to "long long" since it's more general and all ints will get promoted to long long. I also standardized the call interface to include the number as well as a buffer to contain the formatted string (like some of the contributions) and returned a pointer to the buffer:

char* funcName(long long number_to_format, char* string_buffer);

Including a buffer parameter is considered by some to be "better" than having the function: 1) contain a static buffer (would not be re-entrant) or 2) allocate space for the buffer (would require caller to de-allocate the memory) or 3) print the result directly to stdout (would not be as generally useful since the output may be targeted for a GUI widget, file, pty, pipe, etc.).

I tried to use the same function names as the original contributions to make it easier to refer back to the originals. Contributed functions were modified as needed to pass the accuracy test so that the speed test would be meaningful. The results are included here in case you would like to test more of the contributed techniques for comparison. All code and test code used to generate the results are shown below.

So, here are the results:

Accuracy Test (test cases: LLONG_MIN, -999, -99, 0, 99, 999, LLONG_MAX):
----------------------------------------------------

print_number:
 -9,223,372,036,854,775,808, -999, -99, 0, 99, 999, 9,223,372,036,854,775,807

fmtLocale:
 -9,223,372,036,854,775,808, -999, -99, 0, 99, 999, 9,223,372,036,854,775,807

fmtCommas:
 -9,223,372,036,854,775,808, -999, -99, 0, 99, 999, 9,223,372,036,854,775,807

format_number:
 -9,223,372,036,854,775,808, -999, -99, 0, 99, 999, 9,223,372,036,854,775,807

itoa_commas:
 -9,223,372,036,854,775,808, -999, -99, 0, 99, 999, 9,223,372,036,854,775,807

Speed Test: (1 million calls, values reflect average time per call)
----------------------------------------------------
 print_number:   0.747 us (microsec) per call
    fmtLocale:   0.222 us (microsec) per call
    fmtCommas:   0.212 us (microsec) per call
format_number:   0.124 us (microsec) per call
  itoa_commas:   0.085 us (microsec) per call

Since all contributed techniques are fast (< 1 microsecond on my laptop), unless you need to format millions of numbers, any of the techniques should be acceptable. It's probably best to choose the technique that is most readable to you.

Here is the code:

#line 2 "comma.c"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <time.h>
#include <math.h>
#include <locale.h>
#include <limits.h>

// ----------------------------------------------------------
char* print_number( long long  n, char buf[32] ) {
    long long order_of_magnitude = (n == 0) ? 1
        : (long long)pow( 10, ((long long)floor(log10(fabs(n))) / 3) * 3 ) ;

    char *ptr = buf;
    sprintf(ptr, "%d", n / order_of_magnitude ) ;
    for( n %= order_of_magnitude, order_of_magnitude /= 1000;
            order_of_magnitude > 0;
            n %= order_of_magnitude, order_of_magnitude /= 1000 )
    {
        ptr += strlen(ptr);
        sprintf(ptr, ",%03d", abs(n / order_of_magnitude) );
    }
    return buf;
}

// ----------------------------------------------------------
char* fmtLocale(long long i, char buf[32]) {
    sprintf(buf, "%'lld", i);      // requires setLocale in main
    return buf;
}

// ----------------------------------------------------------
char* fmtCommas(long long num, char dst[32]) {
    char src[27];
    char *p_src = src;
    char *p_dst = dst;

    const char separator = ',';
    int num_len, commas;

    num_len = sprintf(src, "%lld", num);

    if (*p_src == '-') {
        *p_dst++ = *p_src++;
        num_len--;
    }

    for (commas = 2 - num_len % 3;
            *p_src;
            commas = (commas + 1) % 3)
    {
        *p_dst++ = *p_src++;
        if (commas == 1) {
            *p_dst++ = separator;
        }
    }
    *--p_dst = '\0';

    return dst;
}

// ----------------------------------------------------------
char* format_number(long long n, char out[32]) {
    int digit;
    int out_index = 0;
    long long i = (n < 0) ? -n : n;
    if (i == LLONG_MIN) i = LLONG_MAX;      // handle MIN, offset by 1

    if (i == 0) { out[out_index++] = '0'; } // handle 0

    for ( ; i != 0; i /= 10) {
        digit = i % 10;

        if ((out_index + 1) % 4 == 0) {
            out[out_index++] = ',';
        }
        out[out_index++] = digit + '0';
    }

    if (n == LLONG_MIN) { out[0]++; }       // correct for offset
    if (n < 0) { out[out_index++] = '-'; }
    out[out_index] = '\0';

    // then you reverse the out string 
    for (int i=0, j = strlen(out) - 1; i<=j; ++i, --j) {
        char tmp = out[i];
        out[i] = out[j];
        out[j] = tmp;
    }
    return out;
}

// ----------------------------------------------------------
char* itoa_commas(long long i, char buf[32]) {
    char* p = buf + 31;
    *p = '\0';                               // terminate string
    if (i == 0) { *(--p) = '0'; return p; }  // handle 0
    long long n = (i < 0) ? -i : i;
    if (n == LLONG_MIN) n = LLONG_MAX;       // handle MIN, offset by 1

    for (int j=0; 1; ++j) {
        *--p = '0' + n % 10;                 // insert digit
        if ((n /= 10) <= 0) break;
        if (j % 3 == 2) *--p = ',';          // insert a comma
    }

    if (i == LLONG_MIN) { p[24]++; }         // correct for offset
    if (i < 0) { *--p = '-'; }
    return p;
}

// ----------------------------------------------------------
// Test Accuracy
// ----------------------------------------------------------
void test_accuracy(char* name, char* (*func)(long long n, char* buf)) {
    char sbuf[32]; // string buffer

    long long nbuf[] = { LLONG_MIN, -999, -99, 0, 99, 999, LLONG_MAX };
    printf("%s:\n", name);
    printf(" %s", func(nbuf[0], sbuf));
    for (int i=1; i < sizeof(nbuf) / sizeof(long long int); ++i) {
        printf(", %s", func(nbuf[i], sbuf));
    }
    printf("\n");
}

// ----------------------------------------------------------
// Test Speed
// ----------------------------------------------------------
void test_speed(char* name, char* (*func)(long long n, char* buf)) {
    int cycleCount = 1000000;
    //int cycleCount = 1;
    clock_t start;
    double elapsed;
    char sbuf[32]; // string buffer

    start = clock();
    for (int i=0; i < cycleCount; ++i) {
        char* s = func(LLONG_MAX, sbuf);
    }
    elapsed = (double)(clock() - start) / (CLOCKS_PER_SEC / 1000000.0);

    printf("%14s: %7.3f us (microsec) per call\n", name, elapsed / cycleCount);
}

// ----------------------------------------------------------
int main(int argc, char* argv[]){
    setlocale(LC_ALL, "");

    printf("\nAccuracy Test: (LLONG_MIN, -999, 0, 99, LLONG_MAX)\n");
    printf("----------------------------------------------------\n");
    test_accuracy("print_number", print_number);
    test_accuracy("fmtLocale", fmtLocale);
    test_accuracy("fmtCommas", fmtCommas);
    test_accuracy("format_number", format_number);
    test_accuracy("itoa_commas", itoa_commas);

    printf("\nSpeed Test: 1 million calls\n\n");
    printf("----------------------------------------------------\n");
    test_speed("print_number", print_number);
    test_speed("fmtLocale", fmtLocale);
    test_speed("fmtCommas", fmtCommas);
    test_speed("format_number", format_number);
    test_speed("itoa_commas", itoa_commas);

    return 0;
}
Related