【问题标题】:How can C print a super-large number?C如何打印超大数?
【发布时间】:2015-03-16 01:25:15
【问题描述】:

这段代码怎么能打印出这么大的数字?我已经在 Ubuntu 14.04 (gcc 4.8.2) 上试过了。 它在 MS Windows 上与任何编译器(甚至是 MinGW,称为“gcc for Windows”)都不相同。为什么?

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

Ubuntu 输出:

3273390607896141870013189696827599152216642046043064789483291368096133796404
674554883270092325904157150886684127560071009217256545885393053328527589376

Windows 输出:

3273390607896141900000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000

(为了清楚起见,添加了换行符。)

【问题讨论】:

  • 也许 GCC 使用它的依赖项 MPFR 来将浮点表达式计算为任意精度,因为它是一个常数?
  • 转换为某种特定类型,例如 (long double)pow(2,500),差异应该会消失
  • 执行 printf("%.0lf\n",power(2,500));做点不一样的事?

标签: c linux windows gcc compilation


【解决方案1】:

这不是一个新的答案,而是对其他人已经说过的话略有不同:

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

从某种意义上说,这两种实现都是正确的;但我会说 Windows printf() more 是正确的。原因如下:

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

一般来说,大部分 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/)

【讨论】:

    【解决方案2】:

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

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

    这里的技巧是pow(2,500)完全可以表示为浮点double(二进制64)。这可能会让人产生double 具有数百位精度的错觉——它没有。

    一旦打印了 17 个左右的数字,这两种不同的编译会以不同的方式处理 double 到文本的转换 - 这是 C 规范允许的。正确数字的最小数量是 DBL_DECIMAL_DIG - 在两个系统上都可能是 17。

    考虑打印 next 更大的double。虽然下一个 double 可以打印 150 个左右的数字,但通常这些额外的数字通过 DBL_DECIMAL_DIG 对许多应用程序来说只是噪音。

    // 2 ^ 500
    327339060789614 187001318969682759915221664...
    // next
    327339060789614 259685191399243448970154045...
    

    【讨论】:

    • 2^500 可以完全用 500 位表示,而不是 501:google.com/search?q=ln(2%5E500)+%2F+ln(2)
    • @specializt 2^3 是十进制的8 或二进制的1000,它是 4 个二进制数字而不是 3。2^500 需要 501 个二进制数字来表示。
    • 好吧:2^8 可以用 3 位表示 - 只需从数据字中删除数字 0 并将所有内容“向右”移动,这意味着二进制 001 将是十进制 2 而不是十进制1(二进制 000 将是十进制 1) - 例如,这是工业消息传递协议中的常见编码 - 数字 0 必须单独传输/发出信号。这种编码等于一个数字中二进制信息的实际数量,它对于高频数据传输非常有用,而每个值的范围相当小(主要是 16-32 位)
    • @specializt,恐怕这不是你想的那样。即使您选择的算法只允许您用 n 位表示 2^n,用 n 位表示无符号整数也只能表示从 0 到 2^n-1 的数字。当您使用 n+1 位时,这会将 2^n 之外的数字作为第一个进入俱乐部的候选人。如果您使用的协议禁止使用 0 作为正确的值,那么您可以使用这些技巧,但这并不意味着 2^n 是 n+1 个二进制数字的二进制数。
    【解决方案3】:

    MinGW 也使用的 Microsoft Visual C 运行时库 (msvcrt.dll) 仅支持最多 17 位精度,因为这足以唯一标识 (IEEE 754) double

    但好消息:您使用 VS2015 CTP4 编译的代码会产生与您的 Ubuntu 示例相同的输出。如果您使用的是 MinGW-w64,您可以在包含 &lt;stdio.h&gt; 之前将 __USE_MINGW_ANSI_STDIO 定义为 1 或通过 -D 编译器选项获得相同的输出。

    【讨论】:

    • 什么是 msvcrt.dll ? VS2015 CTP4如何产生差异输出? ubuntu 是如何产生输出的?
    猜你喜欢
    • 2018-08-27
    • 1970-01-01
    • 1970-01-01
    • 2017-06-16
    • 2011-08-22
    • 2018-02-15
    • 2016-03-25
    • 1970-01-01
    • 2018-06-08
    相关资源
    最近更新 更多