C语言中printf函数的数字分组

7
我希望能够使用千位分隔符(逗号或空格)输出大数字,与在如何显示3位数字分组中相同。但是我需要在C语言中使用printf(GNU,99)函数进行操作。
如果printf不支持数字分组,那么我该如何通过像printf("%s", group_digits(number))这样的方法来实现?
它必须支持负整数,并最好也支持浮点数。

http://newsgroups.derkeiler.com/Archive/Comp/comp.sys.hp.mpe/2006-02/msg00074.html - stacker
1
可能是重复的问题,参考如何在C语言中将数字从1123456789格式化为1,123,456,789? - Dan Dascalescu
注意“奇怪”的分组方法,可能不止印度数字系统 - pmg
@pmg 这让我想起了美国的日期格式... - Matthieu
7个回答

13

如果你可以使用POSIX printf,请尝试如下:

#include <locale.h>
setlocale(LC_ALL, ""); /* use user selected locale */
printf("%'d", 1000000);

1
如果您希望程序在任何地方运行,那么它必须是“”,也就是说无论用户选择什么语言,程序都能正常运行。您还可以将自己限制在LC_NUMERIC语言环境类别中。 - Jan Hudec
1
我并不是很喜欢这种方法,因为它似乎无法移植到其他语言。以Python为例,我尝试了print "%'d" % 1000,但它立即抛出了一个错误。'似乎是一个非标准的格式说明符。因此,如果一个人在编写比C更多的语言和比POSIX兼容的环境更多的程序,那么这个答案应该带着一些保留。考虑到问题的狭窄范围,我会点赞。 - Braden Best

3
这是一种简洁的实现方式:
// 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;
}

例如,调用strmoney(1234567891.4568)会返回字符串"1 234 567 891.46"。您可以通过更改函数顶部的separator变量来轻松替换空格为其他分隔符(例如逗号)。

1
谢谢,Erick;这是使用静态变量的绝妙方法,避免了声明单独缓冲区的麻烦,但必须小心避免像printf("%s|%s\n", strmoney(1234567890.1234567890), strmoney(-9876543210.9876543210))这样的结构,它会打印相同的值两次。+1也使用了memmove。希望你不介意我美化和注释你的代码! - Gnubie
@Gnubie,我不想成为“那个人”,但注释是不好的。它们代表了无法清晰编写代码的失败。如果你必须解释某些东西的工作原理,那么它需要被重新编写,而不是解释。聪明的代码容易出错,用于描述这种聪明代码的注释会给维护者带来更多的工作,并且代表着一个等待发生的谎言。 - Braden Best
1
我冒昧地重构了这个函数并整理了语言。但说实话,这个函数需要更多的改进。例如,返回静态字符数组是有问题的,因为每次函数运行时,对返回值的任何指针都会改变,使得无法同时使用该函数。一个更简单的方法是从“调用者”传入缓冲区,而不是在“被调用者”中创建它,并将返回类型更改为“void”。这是一个可以改进的例子。 - Braden Best
不要使用魔数64,而是使用一个值来处理所有的“double”。例如:result[DBL_MAX_10_EXP*4/3 + 7]; - chux - Reinstate Monica
strmoney(-123.45) --> "- 123.45". 预期结果应为 "-123.45" - chux - Reinstate Monica

2

我自己的无符号 64 位整数版本:

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

1
“%lld”与“unsigned __int64”的符号不同。我建议使用匹配的格式说明符和类型。 - chux - Reinstate Monica

2

一种安全的千位分隔符格式化方式,支持负数:

由于VS < 2015没有实现snprintf,所以您需要执行以下操作

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

然后

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

使用示例:

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

对于format_commas(INT_MIN, buf)会失败。示例:"--2'147'483'648"abs(INT_MIN)是未定义行为。 - chux - Reinstate Monica
代码可以使用snprintf()的返回值,而不是strlen(buf) - chux - Reinstate Monica

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

1
输入“-123”输出“- 123”。我期望的输出是“-123”。 - chux - Reinstate Monica
尝试一下:https://pastebin.com/raw/Y6vRfRuz - Legioneroff
输入“-123456”会得到什么结果? - chux - Reinstate Monica

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

}

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

地址:

/*
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);
}
*/

许多编译器不支持printf选项的实现,只能自己实现。 - BLUEPIXY
strrev 在一些编译器上不可用,但可以按照 https://dev59.com/qmoy5IYBdhLWcg3wdt4L 中的答案进行实现。有了它,代码给出了正确的答案,因此我抵消了某人的负评。谢谢,BLUEPIXY。 - Gnubie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接