将天文数字转换为人类可读形式的C/C++代码

9

我的程序打印出非常大的数字,比如100363443,甚至高达一万亿,这样的数字有些难以阅读,因此我希望能够以易于阅读的形式打印任何数字。

目前我使用的方法是:

printf ("%10ld", number);

格式化

我希望使用printf函数得到一个结果数字。我的大部分代码是用C++编写的,但我不想引入std::cout,因为我已经在使用printf函数了。

谢谢


你希望如何打印数字以使其更易于阅读?每三位数字分组?用逗号分隔?还是用空格? - Martin B
我没有严格的要求;我假设在数字每 3 组中使用逗号... - vehomzzz
这个功能已经内置在std::ostream对象中了。你只需要为流设置正确的属性即可。请参考下面Patrick的回答。 - Martin York
也许把这个重新标签为仅限于C问题? - Patrick
我记得有一种方法可以将iostream与stdio同步,但是我忘记了具体的操作。 - vehomzzz
显示剩余3条评论
6个回答

13

如果你有这个选项并且不介意失去一些可移植性,可以在printf格式字符串中使用非标准的apostrophe标志。

根据我的文档,在POSIX系统自1997年以来就已经支持'标志。

如果你在Unix、Linux、Mac等系统上,应该没有问题。
但如果你在Windows、DOS、iSeries、Android等系统上,则无法保证(但也许可以在系统上安装一个POSIX层)。

#include <locale.h>
#include <stdio.h>

int main(void) {
  long int x = 130006714000000;

  setlocale(LC_NUMERIC, "en_US.utf-8"); /* important */
  while (x > 0) {
    printf("# %%'22ld: %'22ld\n", x); /* apostrophe flag */
    x *= 2; /* on my machine, the Undefined Behaviour for overflow
            // makes the number become negative with no ill effects */
  }
  return 0;
}

在我的系统上,此程序产生:

# %'22ld:    130,006,714,000,000
# %'22ld:    260,013,428,000,000
# %'22ld:    520,026,856,000,000
# %'22ld:  1,040,053,712,000,000
# %'22ld:  2,080,107,424,000,000
# %'22ld:  4,160,214,848,000,000
# %'22ld:  8,320,429,696,000,000
# %'22ld: 16,640,859,392,000,000
# %'22ld: 33,281,718,784,000,000
# %'22ld: 66,563,437,568,000,000
# %'22ld: 133,126,875,136,000,000
# %'22ld: 266,253,750,272,000,000
# %'22ld: 532,507,500,544,000,000
# %'22ld: 1,065,015,001,088,000,000
# %'22ld: 2,130,030,002,176,000,000
# %'22ld: 4,260,060,004,352,000,000
# %'22ld: 8,520,120,008,704,000,000

这是唯一一个完全符合Andrei要求,使用他想要使用的工具的答案。 - Massa
1
我收到一个警告 - 警告#269:无效的格式字符串转换: printf(“\ n \ t%'12ld \ n”,total); - vehomzzz
@Andrei:抱歉,你需要将内容打印到字符串中,格式化该字符串,最后输出格式化后的字符串。 - pmg
我的意思是,新的格式化字符串类似于Lance Rushing的答案。 - pmg
@Andrei - 你可能会收到警告,因为撇号修饰符是非标准的。例如,MSVC不支持它。即使你使用的库支持它,你的编译器也会给你一个很好的提醒。 - Michael Burr
@Michae -- 这就是我想的,孩子。 - vehomzzz

9
你可以使用humanize_number()函数,它使用像k、m等后缀来省略低位数。这不是一个标准例程,所以你应该下载我链接的源代码。(2条款BSD许可证,允许任何形式的使用。) Humanize_number man page
NetBSD下载Humanize_number source code
HUMANIZE_NUMBER(3)      NetBSD Library Functions Manual     HUMANIZE_NUMBER(3)

NAME
     dehumanize_number, humanize_number -- format a number into a human read-
     able form and viceversa

SYNOPSIS
     #include <stdlib.h>

     int
     dehumanize_number(const char *str, int64_t *result);

     int
     humanize_number(char *buf, size_t len, int64_t number,
         const char *suffix, int scale, int flags);

这是通过以下方式添加后缀来实现的:
       Suffix    Description    Multiplier
       k         kilo           1024
       M         mega           1048576
       G         giga           1073741824
       T         tera           1099511627776
       P         peta           1125899906842624
       E         exa            1152921504606846976

1
这可能更好。我那个小技巧只有在你够奇怪,认为科学计数法是“人类可读”的情况下才有效。 ;) - Daniel Bingham
2
一个非常小的挑剔: “prefixes”是在事物之前的(这就是“pre-”的意思)。 “Suffixes”是在事物之后的。 110K使用K后缀表示110,000。 - John R. Strohm
只是补充一下,这是一个仅适用于整数的函数——无法处理浮点数... - sdaau

7

简单的方法可能是在输出前将其转换为double,然后使用%e,它将以指数科学计数法打印。试试这个:

double n = (double)number;
printf("%10.0e", n);

1
+1 给我。这将是最简单的查看它们的方式。我唯一要添加的是设置一个断点。低于断点的任何内容,您应该像普通数字一样进行格式化。 - Aaron M

6
std::cout << std::setprecision(5) << std::scientific << 100363443.0;

请注意,这个数字是浮点数。
编辑:如果您不喜欢科学计数法,我在网上找到了这个:
struct comma : public std::numpunct<char>
{ 
    protected: std::string do_grouping() const { return "\003" ; } 
};

std::cout.imbue( std::locale( std::cout.getloc(), new comma ) );
std::cout << 100363443 << std::endl;

编辑2:正如Jerry所指出的,您不需要像上面那样使用逗号类,这本身似乎就足够了(尽管可能有一些语言环境根本不会格式化大数字):

std::cout.imbue( std::locale( "" ) );
std::cout << 100363443 << std::endl;

是的。使用本地化是一致的做法。 - Martin York
1
好主意,但你应该更懒:std::cout.imbue(std::locale("")); std::cout << 123456789 << std::endl;没有名称的区域设置使用用户配置的任何区域设置,因此(例如)计算机配置为美国惯例的用户将获得: 123,456,789 但是使用德国惯例的用户将获得: 123.456.789 等等。在某些语言环境中(例如印度,我IRC),您甚至通常不会看到三个数字一组--它类似于前五个(最低有效)数字作为一组,然后从那里以两个数字一组。在C中,查找localeconv;这总比没有好。 - Jerry Coffin

3

记得本地化(尤其是当你在编写库时)。
在欧洲(英国除外),数字的表示方法为 1.000.000 而不是 1,000,000。


1

这是我用纯C编写的一个示例,没有使用locale。仅适用于正数。(得到了“DiscoVlad”的很多帮助)

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <strings.h>


void my_reverse ( char* s ) {
    int c, i, j;
    for (i=0, j= strlen(s)-1;i<j;i++,j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}


char* insert_commas(unsigned long long input ) {
    int i, intlen;
    char* buffer;
    char* formatted;

    intlen = (int) ceil(log10(input * 1.0));
    buffer = (char *) malloc((intlen + 1) * sizeof(char));

    sprintf(buffer, "%llu", input);  // build buffer
    formatted = (char *) malloc((intlen + (int) ceil(intlen/3.0)) * sizeof(char));  // malloc output buffer
    my_reverse(buffer);

    for(i=intlen; i>=0; i--) {
        formatted[strlen(formatted)] = buffer[i];
        if (i%3 == 0 && i<intlen && i > 0) {
            formatted[strlen(formatted)] = ',';
        }
    }
    free(buffer);

    return formatted;
}


int main() {
    char* formatted;

    // don't forget to free(formatted) after each call.
    formatted = insert_commas(123);
    printf("output %s\n", formatted);
    // output 123

    formatted = insert_commas(1234);
    printf("output %s\n", formatted);
    // output 1,234

    formatted = insert_commas(123456);
    printf("output %s\n", formatted);
    // output 123,456

    formatted = insert_commas(1234567);
    printf("output %s\n", formatted);
    // output 1,234,567

    formatted = insert_commas(123456789);
    printf("output %s\n", formatted);
    // output 123,456,789

    formatted = insert_commas(12345678901234567890ull);
    printf("output %s\n", formatted);
    // output 12,345,678,901,234,567,890

}

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