7

I wish to output large numbers with thousand-separators (commas or spaces) — basically the same as in How to display numeric in 3 digit grouping but using printf in C (GNU, 99).

If printf does not support digit grouping natively, how can I achieve this with something like printf("%s", group_digits(number))?

It must support negative integers and preferably floats, too.

Community
  • 1
  • 1
Gnubie
  • 2,587
  • 4
  • 25
  • 38
  • http://newsgroups.derkeiler.com/Archive/Comp/comp.sys.hp.mpe/2006-02/msg00074.html – stacker May 15 '12 at 14:19
  • 1
    Possible duplicate of [How to format a number from 1123456789 to 1,123,456,789 in C?](http://stackoverflow.com/questions/1449805/how-to-format-a-number-from-1123456789-to-1-123-456-789-in-c) – Dan Dascalescu Feb 01 '16 at 23:28
  • Beware "strange" grouping methods, there may be more than [indian numbering system](https://en.wikipedia.org/wiki/Indian_numbering_system). – pmg Feb 12 '19 at 17:13
  • @pmg it reminds me of the US date format... – Matthieu May 19 '19 at 16:30

7 Answers7

13

If you can use POSIX printf, try

#include <locale.h>
setlocale(LC_ALL, ""); /* use user selected locale */
printf("%'d", 1000000);
pmg
  • 106,608
  • 13
  • 126
  • 198
  • 1
    Not even something like "de_DE". Basically if you want the program to run anywhere, it has to be "", meaning whatever the user has selected. You can also limit yourself to LC_NUMERIC locale category. – Jan Hudec May 15 '12 at 14:27
  • 1
    I don't really like this method because it doesn't seem to port to other languages. Take Python, for example, I tried `print "%'d" % 1000` and it immediately threw an error. `'` seems to be a non-standard format specifier. For that reason, this answer should be taken with a grain of salt if one programs in more languages than just C, and more environments than just the POSIX-compliant ones. Considering the narrow scope of the question, though, I'll give an upvote. – Braden Best May 31 '16 at 16:43
3

Here is a compact way of doing it:

// format 1234567.89 -> 1 234 567.89
extern char *strmoney(double value){
    static char result[64];
    char *result_p = result;
    char separator = ' ';
    size_t tail;

    snprintf(result, sizeof(result), "%.2f", value);

    while(*result_p != 0 && *result_p != '.')
        result_p++;

    tail = result + sizeof(result) - result_p;

    while(result_p - result > 3){
        result_p -= 3;
        memmove(result_p + 1, result_p, tail);
        *result_p = separator;
        tail += 4;
    }

    return result;
}

For example, a call to strmoney(1234567891.4568) returns the string "1 234 567 891.46". You can easily replace the space with another separator (such as a comma) by changing the separator variable at the top of the function.

Braden Best
  • 8,830
  • 3
  • 31
  • 43
  • 1
    Thanks, Erick; that's brilliant use of a static to avoid having to declare a separate buffer, though one must be careful to avoid constructs like printf("%s|%s\n", strmoney(1234567890.1234567890), strmoney(-9876543210.9876543210)) which print the same value twice. +1 for using memmove too. Hope you don't mind my beautifying and annotating your code! – Gnubie Dec 09 '13 at 20:26
  • @Gnubie I don't wanna be *that guy*, but comments are bad. They represent a failure to clearly write code. If you have to explain how something works, then it needs to be **re-written**, not explained. Clever code is error-prone, and comments used to describe said clever code both create more work for a maintainer, and represent a lie waiting to happen. – Braden Best May 31 '16 at 15:14
  • 1
    I took the liberty of refactoring the function and tidying up the language. Honestly, though, this function needs more than just a refactor. For example, returning a static char array is problematic because any pointer you have to the returned value will change every time the function runs, making it impossible to use this function in parallel with itself. A simpler way would be to pass in a buffer from the `caller`, rather than create it in the `callee`, and change the return type to `void`. That's one example of something that could be improved. – Braden Best May 31 '16 at 16:30
  • Rather than magic number 64, use a value to handle all `double`. Perhaps : `result[DBL_MAX_10_EXP*4/3 + 7];` – chux - Reinstate Monica Apr 19 '22 at 19:43
  • `strmoney(-123.45)` --> `"- 123.45"`. `"-123.45"` expected. – chux - Reinstate Monica Apr 20 '22 at 23:15
2

A secure way to format thousand separators, with support for negative numbers:

Because VS < 2015 doesn't implement snprintf, you need to do this

#if defined(_WIN32)
    #define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__)
#endif

And then

char* format_commas(int n, char *out)
{
    int c;
    char buf[100];
    char *p;
    char* q = out; // Backup pointer for return...

    if (n < 0)
    {
        *out++ = '-';
        n = abs(n);
    }


    snprintf(buf, 100, "%d", n);
    c = 2 - strlen(buf) % 3;

    for (p = buf; *p != 0; p++) {
        *out++ = *p;
        if (c == 1) {
            *out++ = '\'';
        }
        c = (c + 1) % 3;
    }
    *--out = 0;

    return q;
}

Example usage:

size_t currentSize = getCurrentRSS();
size_t peakSize = getPeakRSS();


printf("Current size: %d\n", currentSize);
printf("Peak size: %d\n\n\n", peakSize);

char* szcurrentSize = (char*)malloc(100 * sizeof(char));
char* szpeakSize = (char*)malloc(100 * sizeof(char));

printf("Current size (f): %s\n", format_commas((int)currentSize, szcurrentSize));
printf("Peak size (f): %s\n", format_commas((int)currentSize, szpeakSize));

free(szcurrentSize);
free(szpeakSize);
Stefan Steiger
  • 78,642
  • 66
  • 377
  • 442
2

My own version for unsigned int64:

char* toString_DigitGrouping( unsigned __int64 val )
{
    static char result[ 128 ];
    _snprintf(result, sizeof(result), "%lld", val);

    size_t i = strlen(result) - 1;
    size_t i2 = i + (i / 3);
    int c = 0;
    result[i2 + 1] = 0;

    for( ; i != 0; i-- )
    {
        result[i2--] = result[i];
        c++;
        if( c % 3 == 0 )
            result[i2--] = '\'';
    } //for

    return result;  
} //toString_DigitGrouping
TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
2
#include <stdio.h>

int main() {
    char str[50];
    int len = 0;   
    scanf("%48[^\n]%n", str, &len);

    int start = len % 3;

    for(int i = 0; i < len; i++) {        
        if(i == start && i != 0) {
            printf(" ");
        } else if((i - start) % 3 == 0 && i != 0) {
            printf(" ");
        }    
        printf("%c", str[i]);
    }   

   return 0;
}
Legioneroff
  • 139
  • 5
2
#include <stdio.h>

void punt(int n){
    char s[28];
    int i = 27;
    if(n<0){n=-n; putchar('-');} 
    do{
        s[i--] = n%10 + '0';
        if(!(i%4) && n>9)s[i--]=' ';
        n /= 10;
    }while(n);
    puts(&s[++i]);
}


int main(){

    int a;
    scanf("%d",&a);
    punt(a);

}
Legioneroff
  • 139
  • 5
1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

char *commify(char *numstr){
    char *wk, *wks, *p, *ret=numstr;
    int i;

    wks=wk=strrev(strdup(numstr));
    p = strchr(wk, '.');
    if(p){//include '.' 
        while(wk != p)//skip until '.'
            *numstr++ = *wk++;
        *numstr++=*wk++;
    }
    for(i=1;*wk;++i){
        if(isdigit(*wk)){
            *numstr++=*wk++;
            if(isdigit(*wk) && i % 3 == 0)
                *numstr++ = ',';
        } else {
            break;
        }
    }
    while(*numstr++=*wk++);

    free(wks); 
    return strrev(ret);
}


int main(){
    char buff[64];//To provide a sufficient size after conversion.
    sprintf(buff, "%d", 100);
    printf("%s\n", commify(buff));
    sprintf(buff, "%d", 123456);
    printf("%s\n", commify(buff));
    sprintf(buff, "%.2f", 1234.56f);
    printf("%s\n", commify(buff));
    sprintf(buff, "%d", -123456);
    printf("%s\n", commify(buff));
    sprintf(buff, "%.2lf", -12345678.99);
    printf("%s\n", commify(buff));
    return 0;
}

ADD:

/*
char *strrev(char *str){
    char c,*front,*back;

    for(front=str,back=str+strlen(str)-1;front < back;front++,back--){
        c=*front;*front=*back;*back=c;
    }
    return(str);
}
*/
BLUEPIXY
  • 39,699
  • 7
  • 33
  • 70
  • implementation of the printf option is not supported in many compilers, no choice but to implement it yourself. – BLUEPIXY May 15 '12 at 22:37
  • `strrev` is unavailable on some compilers but can be implemented as in the answer to http://stackoverflow.com/questions/8534274/is-strrev-function-not-available-in-linux . With it, the code gave me the correct answers, so I've nullified someone's downvote. Thanks, BLUEPIXY. – Gnubie May 17 '12 at 14:56