C语言如何打印超大的数字?

4

这段代码如何能输出如此大的数字?我已经在Ubuntu 14.04(gcc 4.8.2)上尝试过它,但在任何编译器上的MS Windows中都无法正常工作(即使是被称为“Windows版gcc”的MinGW)。为什么会这样?

#include <stdio.h>
#include <math.h>
int main()
{
    printf("%.0f\n",pow(2,500));
}

Ubuntu输出:

3273390607896141870013189696827599152216642046043064789483291368096133796404
674554883270092325904157150886684127560071009217256545885393053328527589376

Windows 输出:

3273390607896141900000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000

(The line break is added for clarity only.)


4
也许GCC使用它的依赖项MPFR来对浮点表达式进行任意精度计算,因为它是一个常数? - Iwillnotexist Idonotexist
1
强制转换为某个特定类型,例如(long double)pow(2,500),则差异应该消失。 - maxpovver
printf("%.0lf\n",power(2,500)); 这行代码有什么不同的作用吗? - kenny
3个回答

7

正如OP所暗示并由@user300234评论,2^500是一个501位数,但这不是问题所在。

pow(2,500)返回一个double,约为3.27e150,通常是binary64。该类型支持大约15-17个十进制数字的精度。因此,对于接近3.27e150的数字,打印超过17位的有效数字通常并不重要。它比可能约为1.798e308DBL_MAX要小得多。

这里的诀窍在于pow(2,500)可以被表示为浮点double(binary64)。这可能会让人产生double具有数百位精度的错觉 - 它并没有。

两种不同的编译方式在打印17位或更多位数字时处理将double转换为文本的方式有所不同,这是C规范允许的。正确数字的最小数量是DBL_DECIMAL_DIG,在两个系统上可能都是17。
考虑打印下一个更大的double。虽然下一个double可以打印150个或更多位数字,但对于许多应用程序来说,这些额外的超过DBL_DECIMAL_DIG的数字通常只是噪音。
// 2 ^ 500
327339060789614 187001318969682759915221664...
// next
327339060789614 259685191399243448970154045...

2^500可以被精确地表示为500位二进制,而不是501位:https://www.google.com/search?q=ln(2%5E500)+%2F+ln(2) - specializt
5
@specializt 的意思是:2^3在十进制中为8,在二进制中为1000,需要4位二进制数字而不是3位。同样地,表示2^500需要501位二进制数字。请注意不要改变原文的意思。 - chux - Reinstate Monica
好的,2^8可以用3位表示 - 只需从数据字中删除数字0并将所有内容“向右移动”,这意味着二进制001将变为十进制2而不是十进制1(而二进制000将变为十进制1) - 这是工业通信协议中常见的编码方式,例如 - 数字0必须单独传输/信号。该编码等于数字中实际的二进制信息量,对于高频数据传输非常有用,其中每个值的范围相对较小(主要为16-32位)。 - specializt
6
@specializt,恐怕这不是你所想象的那样。即使您选择了一种算法,可以用n位表示2^n,但仅使用n位表示无符号整数只能表示0到2^n-1之间的数字。这意味着当您使用n+1位时,2^n将成为第一个候选进入该组的数字。如果您正在使用禁止使用0作为有效值的协议,则可以使用这些技巧,但这并不意味着2^n是由n+1个二进制数字组成的二进制数是错误的。 - Luis Colorado

4
msvcrt.dll 是由MinGW使用的 Microsoft Visual C运行时库,它仅支持最多17位数字精度,因为这已足以唯一标识(IEEE 754)double。但好消息是:如果您使用VS2015 CTP4编译您的代码,则生成与Ubuntu示例相同的输出。如果您正在使用MinGW-w64,则可以在包含<stdio.h>之前定义__USE_MINGW_ANSI_STDIO1,或通过 -D编译器选项获得相同的输出。

什么是msvcrt.dll?VS2015 CTP4如何产生不同的输出?Ubuntu如何产生输出? - adarsh

0

这不是一个新的答案,而是对其他人已经说过的内容略微不同的解释:

区别不在于 pow() 函数:区别在于 printf() 函数。Ubuntu 的 printf() 函数打印了 pow(2,500) 的精确十进制值。Windows 的 printf() 函数则打印了一个正确到 17 个小数位的近似值。

从某种意义上来说,两种实现都是正确的;但我认为 Windows 的 printf() 更加正确。原因如下:

如果你关心超过 17 位精度,那么你不应该使用 double

通常,大多数双精度计算结果只在前 17 位正确。(pow(2, n) 是一个非常特殊的情况,其中任何范围内的 n 都是完全正确的答案。)通过打印非常大的双精度数的精确十进制值,Ubuntu 库正在欺骗你;假设你有

double a = ...;
double b = ...;
double c = a*b;

printf("%f", c);

你不需要知道 c 的确切值,因为对于大多数的 abc不等于 a*b:它只是 a*b 的一个近似值——这个近似值精确到约17位有效数字。

如果你需要超过17位有效数字的精度,那么你应该使用一个扩展精度的数学库,比如 GMP (https://gmplib.org/)。


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