在C语言中,如何将大数字格式化为例如 1123456789
到 1,123,456,789
?
我尝试使用 printf("%'10d\n", 1123456789)
,但这并不起作用。
你有什么建议吗?解决方案越简单越好。
在C语言中,如何将大数字格式化为例如 1123456789
到 1,123,456,789
?
我尝试使用 printf("%'10d\n", 1123456789)
,但这并不起作用。
你有什么建议吗?解决方案越简单越好。
如果您的printf支持'
标志(根据POSIX 2008 printf()
的要求),您可能只需适当设置您的区域设置即可实现此目的。例如:
#include <stdio.h>
#include <locale.h>
int main(void)
{
setlocale(LC_NUMERIC, "");
printf("%'d\n", 1123456789);
return 0;
}
然后构建并运行:
$ ./example
1,123,456,789
在 Mac OS X 和 Linux(Ubuntu 10.10)上进行了测试。
sprintf()
进行了测试,但它无法工作(显然,因为正如你所说,它不支持'标志)。 - gbmhunter'
修饰符。从头文件可以看出:版权所有... 2007 Joerg Wunsch ... 1993加州大学理事会,即BSD衍生版本。 - Bob Steinsetlocale(LC_NUMERIC, "");
做到了这一点。没有这行代码,%'d
格式化符号无法按预期工作。 - Telvas你可以按照以下递归方法实现(如果使用二进制补码,请注意 INT_MIN
,你需要额外的代码来处理):
void printfcomma2 (int n) {
if (n < 1000) {
printf ("%d", n);
return;
}
printfcomma2 (n/1000);
printf (",%03d", n%1000);
}
void printfcomma (int n) {
if (n < 0) {
printf ("-");
n = -n;
}
printfcomma2 (n);
}
简要说明:
printfcomma
,并传入一个整数,对于特殊情况的负数,会简单地打印“-”并将数字转换为正数(这是无法使用INT_MIN
的部分)。printfcomma2
时,小于1,000的数字将只打印并返回。还有一种更简洁的版本,但在每个级别都需要检查负数的不必要处理(鉴于递归级别的限制,这并不重要)。这是一个完整的测试程序:
#include <stdio.h>
void printfcomma (int n) {
if (n < 0) {
printf ("-");
printfcomma (-n);
return;
}
if (n < 1000) {
printf ("%d", n);
return;
}
printfcomma (n/1000);
printf (",%03d", n%1000);
}
int main (void) {
int x[] = {-1234567890, -123456, -12345, -1000, -999, -1,
0, 1, 999, 1000, 12345, 123456, 1234567890};
int *px = x;
while (px != &(x[sizeof(x)/sizeof(*x)])) {
printf ("%-15d: ", *px);
printfcomma (*px);
printf ("\n");
px++;
}
return 0;
}
输出结果为:
-1234567890 : -1,234,567,890
-123456 : -123,456
-12345 : -12,345
-1000 : -1,000
-999 : -999
-1 : -1
0 : 0
1 : 1
999 : 999
1000 : 1,000
12345 : 12,345
123456 : 123,456
1234567890 : 1,234,567,890
以下是一种迭代解决方案,适用于那些不信任递归的人(尽管递归唯一的问题通常是栈空间,但对于此处的情况不会成为问题,因为即使对于一个64位整数,它也只会有几层深度):
void printfcomma (int n) {
int n2 = 0;
int scale = 1;
if (n < 0) {
printf ("-");
n = -n;
}
while (n >= 1000) {
n2 = n2 + scale * (n % 1000);
n /= 1000;
scale *= 1000;
}
printf ("%d", n);
while (scale != 1) {
scale /= 1000;
n = n2 / scale;
n2 = n2 % scale;
printf (",%03d", n);
}
}
这两个都为 INT_MAX
生成 2,147,483,647
。
上面所有的代码都是用逗号分隔三位数字组,但你也可以使用其他字符,比如空格:
void printfspace2 (int n) {
if (n < 1000) {
printf ("%d", n);
return;
}
printfspace2 (n/1000);
printf (" %03d", n%1000);
}
void printfspace (int n) {
if (n < 0) {
printf ("-");
n = -n;
}
printfspace2 (n);
}
这是一个非常简单的实现。该函数不包含任何错误检查,缓冲区大小必须由调用者验证。它也不能处理负数。这些改进留给读者自己去练习。
void format_commas(int n, char *out)
{
int c;
char buf[20];
char *p;
sprintf(buf, "%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;
}
天哪!我经常使用Linux上的gcc/g++和glibc,虽然 ' 运算符可能不是标准的,但我喜欢它的简洁性。
#include <stdio.h>
#include <locale.h>
int main()
{
int bignum=12345678;
setlocale(LC_ALL,"");
printf("Big number: %'d\n",bignum);
return 0;
}
输出结果如下:
大数字:12,345,678
只需要记住在其中调用“setlocale”,否则它不会格式化任何内容。
printf()
的库函数使用格式字符串;由库函数来解释它。在Windows上,CRT库可能不提供所需的支持 - 无论使用哪个编译器都是如此。 - Jonathan Leffler#include <stdlib.h>
#include <locale.h>
#include <string.h>
#include <limits.h>
static int next_group(char const **grouping) {
if ((*grouping)[1] == CHAR_MAX)
return 0;
if ((*grouping)[1] != '\0')
++*grouping;
return **grouping;
}
size_t commafmt(char *buf, /* Buffer for formatted string */
int bufsize, /* Size of buffer */
long N) /* Number to convert */
{
int i;
int len = 1;
int posn = 1;
int sign = 1;
char *ptr = buf + bufsize - 1;
struct lconv *fmt_info = localeconv();
char const *tsep = fmt_info->thousands_sep;
char const *group = fmt_info->grouping;
char const *neg = fmt_info->negative_sign;
size_t sep_len = strlen(tsep);
size_t group_len = strlen(group);
size_t neg_len = strlen(neg);
int places = (int)*group;
if (bufsize < 2)
{
ABORT:
*buf = '\0';
return 0;
}
*ptr-- = '\0';
--bufsize;
if (N < 0L)
{
sign = -1;
N = -N;
}
for ( ; len <= bufsize; ++len, ++posn)
{
*ptr-- = (char)((N % 10L) + '0');
if (0L == (N /= 10L))
break;
if (places && (0 == (posn % places)))
{
places = next_group(&group);
for (int i=sep_len; i>0; i--) {
*ptr-- = tsep[i-1];
if (++len >= bufsize)
goto ABORT;
}
}
if (len >= bufsize)
goto ABORT;
}
if (sign < 0)
{
if (len >= bufsize)
goto ABORT;
for (int i=neg_len; i>0; i--) {
*ptr-- = neg[i-1];
if (++len >= bufsize)
goto ABORT;
}
}
memmove(buf, ++ptr, len + 1);
return (size_t)len;
}
#ifdef TEST
#include <stdio.h>
#define elements(x) (sizeof(x)/sizeof(x[0]))
void show(long i) {
char buffer[32];
commafmt(buffer, sizeof(buffer), i);
printf("%s\n", buffer);
commafmt(buffer, sizeof(buffer), -i);
printf("%s\n", buffer);
}
int main() {
long inputs[] = {1, 12, 123, 1234, 12345, 123456, 1234567, 12345678 };
for (int i=0; i<elements(inputs); i++) {
setlocale(LC_ALL, "");
show(inputs[i]);
}
return 0;
}
#endif
这确实存在一个错误(但我认为相当小)。在二进制补码硬件上,它无法正确地转换最负数,因为它试图使用N = -N;
将负数转换为其等效正数。在二进制补码中,最大的负数没有对应的正数,除非您将其提升到较大的类型。解决此问题的一种方法是通过将数字提升为相应的无符号类型(但这有点复杂)。
'
-flag 的问题。我认为这个答案完美地解决了这个问题,现在正在进行更多的测试。如果是这样,我应该将那个问题标记为重复了,对吧? - Jonathan Meetsep
、place_str
和neg_str
呢?为什么不直接使用fmt_info
的成员呢? - Jonathan Mee不使用递归或字符串处理,采用数学方法:
#include <stdio.h>
#include <math.h>
void print_number( int n )
{
int order_of_magnitude = (n == 0) ? 1 : (int)pow( 10, ((int)floor(log10(abs(n))) / 3) * 3 ) ;
printf( "%d", n / order_of_magnitude ) ;
for( n = abs( n ) % order_of_magnitude, order_of_magnitude /= 1000;
order_of_magnitude > 0;
n %= order_of_magnitude, order_of_magnitude /= 1000 )
{
printf( ",%03d", abs(n / order_of_magnitude) ) ;
}
}
原理类似于Pax的递归解决方案,但通过提前计算数量级,避免了递归(可能会付出相当大的代价)。
还要注意,用于分隔千位的实际字符是与语言环境相关的。
编辑:请查看@Chux下面的评论以获取改进意见。
abs(n)
改为 fabs(n)
可以避免在执行 print_number(INT_MIN)
时出现二进制补码错误。 - chux - Reinstate Monicalog10(abs(n))
而不是其他地方。有趣的是,你的解决方案只需要将 log10(fabs(n))
和 print_number(INT_MIN)
进行单一更改就可以工作了,因为 printf(..., abs(n / order_of_magnitude))
意味着 n = abs(INT_MIN) % order_of_magnitude
是负数也没关系。如果我们放弃 INT_MIN,那么 printf(..., abs(n / order_of_magnitude))
可以变成 printf(..., n / order_of_magnitude)
。但我想,与 "abs(INT_MIN)" 打交道通常是一件坏事。 - chux - Reinstate Monicalog10(fabs(n))
,n = abs(n% order_of_magnitude)
和 printf(",%03d", n/order_of_magnitude)
。顺便说一句:我不会花费这种努力,除非我认为你的解决方案很好。即使是 INT_MIN,也没有 UB。 - chux - Reinstate Monicastatic void printNumber (int numbers[8], int loc, int negative)
{
if (negative)
{
printf("-");
}
if (numbers[1]==-1)//one number
{
printf("%d ", numbers[0]);
}
else
{
printf("%d,", numbers[loc]);
while(loc--)
{
if(loc==0)
{// last number
printf("%03d ", numbers[loc]);
break;
}
else
{ // number in between
printf("%03d,", numbers[loc]);
}
}
}
}
static void getNumWcommas (long long int n, int numbers[8])
{
int i;
int negative=0;
if (n < 0)
{
negative = 1;
n = -n;
}
for(i = 0; i < 7; i++)
{
if (n < 1000)
{
numbers[i] = n;
numbers[i+1] = -1;
break;
}
numbers[i] = n%1000;
n/=1000;
}
printNumber(numbers, i, negative);// non recursive print
}
测试输出
-9223372036854775807: -9,223,372,036,854,775,807
-1234567890 : -1,234,567,890
-123456 : -123,456
-12345 : -12,345
-1000 : -1,000
-999 : -999
-1 : -1
0 : 0
1 : 1
999 : 999
1000 : 1,000
12345 : 12,345
123456 : 123,456
1234567890 : 1,234,567,890
9223372036854775807 : 9,223,372,036,854,775,807
int numberSeparated[8];
long long int number = 1234567890LL;
getNumWcommas(number, numberSeparated);
int numberSeparated[8];
移动到函数 getNumWcommas
内,并以以下方式调用该函数 getNumWcommas(number)
。基于@Greg Hewgill的代码,但考虑了负数并返回字符串大小。
size_t str_format_int_grouped(char dst[16], int num)
{
char src[16];
char *p_src = src;
char *p_dst = dst;
const char separator = ',';
int num_len, commas;
num_len = sprintf(src, "%d", 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 (size_t)(p_dst - dst);
}
我自己也需要做类似的事情,但不是直接打印,而是需要进入缓冲区。以下是我想出来的方法。从后往前工作。
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;
}
请注意,此代码仅适用于无符号整数,并且您必须确保缓冲区足够大。
#include <stdio.h>
void punt(long long 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(){
punt(2134567890);
punt(987);
punt(9876);
punt(-987);
punt(-9876);
punt(-654321);
punt(0);
punt(1000000000);
punt(0x7FFFFFFFFFFFFFFF);
punt(0x8000000000000001); // -max + 1 ...
}
printf()
系列格式化IO函数的“千位分隔符”标志(单引号字符:')是一个非标准标志,仅受到少数库实现的支持。很遗憾它不是标准的。 - Michael BurrLC_NUMERIC
。然而,我不知道有哪个区域支持这个。 - Joey AdamsLC_NUMERIC
区域设置为当前的""
可以使得'
在我的Mac和我刚刚检查过的Linux机器上正常工作。 - Carl Norumprintf()
函数系列标准化了使用'
(单引号或撇号)字符与十进制数格式转换规范,以指定应使用千位分隔符格式化数字。 - Jonathan Lefflersetlocale(LC_ALL, "");
可以完成这项工作 - 除了空字符串之外,语言环境名称的其他值都是实现定义的。 - Jonathan Leffler