如何在C++中打印非常大的数字

6
我有这段代码。
#include <iostream>

using namespace std;

int main(int argc,char **argv) {

    unsigned long long num1 = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999995LL;
    unsigned long long num2 = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999996LL;
    unsigned long long num3 = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999997LL;
    unsigned long long num4 = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999998LL;
    unsigned long long num5 = 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999LL;

    cout << (unsigned long long)(num1 * num2 * num3 * num4 * num5) << endl;
    return 0;
}

如您所见,这些数字是巨大的,但是当我进行计算时,结果为:

18446744073709551496

在编译时,我收到了以下警告:

warning: integer constant is too large for its type|
In function `int main(int, char**)':|
warning: this decimal constant is unsigned only in ISO C90|
...

1
我为这个问题添加了两个标签,它们似乎与问题的内容相符。 - Onorio Catenacci
你真的认为那会起作用吗?在你的系统上,unsigned long long 的最大大小可能是 2^64 - 1 = 18446744073709551615 - 即使是 2^1024 - 1 也远远不足以容纳你的常量,更别说乘法的结果了。我建议使用Python。 - We Are All Monica
8个回答

22

你的结果超出了long long类型 - 你需要考虑使用BigInteger或类似gmp这样的高精度库。


7

这些数字无法适应任何C++数据类型。如果你只是想打印它们,可以将数字存储在字符串中。如果你想对其进行数学计算,请查找任意精度数学库并使用它。


3
如果你想在代码中使用这么大的字面量,你需要将它们输入为字符串字面量,并加载到某种BigInt类中。目前没有办法在源代码中表达那么大的整数字面量(尽管C++0x有望解决这个缺陷)。
如果你正在使用BigInteger库,请查看BigIntegerUtils.hh中的stringToBigUnsigned函数,以从字符串构建一个大整数。
#include "BigUnsigned.hh"
#include "BigIntegerUtils.hh"     

 BigUnsigned  num1 = stringToBigUnsigned (
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999999999999999999999999999999999999999999999999"
    "99999999999999999999999999999999999995"
    );

3
你想做什么?你是否了解二进制和十进制数的基础知识?为什么8位只能存储0到255的值,12位只能存储0到4095的值等等?你需要多少位才能存储你感兴趣的数字?或者更好的是,你有多大的数字需要创建?你是否使用9来增加数字的大小?那么使用十六进制的0xF...呢?如果你想要最大的无符号数字(在标准整数类型之一内),为什么不这样做:
无符号长长整型a、b;
a = -1; //这似乎是混合有符号和无符号的,但它是有效的,在存储之前将数字转换为无符号
b = 0; b--; //与上面的代码相同
你真的需要那个级别的精度吗?你意识到乘法可能需要比每个操作数两倍的结果吗?0xFF * 0xFF = 0xFE01,在这种情况下,如果你使用8位整数,你无法进行计算。当你继续乘以0xFF * 0xFF * 0xFF = 0xFD02FF时,情况只会变得更糟。
你想做什么?

看到你的回复:
我以前没有见过欧拉数8。听起来像是一个很好的面试问题,因为只需要几行代码就可以解决。

你的其他回复:
数字……
可能是因为我们有10个手指(也许还有10个脚趾),我们从“十进制”开始成长。我们的时钟大多是基于60进制的,但它已经与十进制混合在一起,使它更加混乱。无论如何,十进制意味着对于每个数字占位符,你有10个唯一的数字,在达到该位置的最大值时,你会滚动到下一个位置。这都是小学的东西。
000
001
002
003
...
008
009
010
011
012
...
看到最右边的数字有10个符号(0,1,2,3,4,5,6,7,8,9),当它达到最后一个符号时,它重新开始,并且左边的数字增加了1。所有基数编号系统都遵循此规则。
对于二进制也是如此,只有两个符号,0和1
000
001
010
011
100
101
...
八进制也是如此,但有8个符号(0,1,2,3,4,5,6,7)
000
001
002
003
004
005
006
007
010
011
012
013
...
同样适用于十六进制,16个符号(0、1、2、3、4、5、6、7、8、9、a、b、c、d、e、f)。
000
001
002
003
004
005
006
007
008
009
00a
00b
00c
00d
00e 00f
010
011
012
013
......
在计算机中,为什么要使用二进制而不是其他进制(如10进制),这也适用于十六进制。最重要的原因是容易具有两种状态:开或关,高或低。在电子设备中,试图将电压调整到超过两种状态以上是很困难的,至少曾经是这样,保持在接近零伏特或在某些小于几伏特的数字上方是相对容易的,因此数字电子设备使用二进制。即使对人来说,二进制也是一个漫长的任务。在二进制下进行简单的二年级数学仍然需要大量的1和0。因此,八进制变得流行起来,因为它允许您以三位比特为一组进行思考,并且您可以使用我们熟悉的数字符号0,1,2,3,4,5,6,7。但是,四位比特的群组(另一个2的指数)比八进制给人类带来了更多的心理计算能力,因此十六进制基于4位比特。我们必须添加更多符号到传统阿拉伯数字基数10中借用的10个符号之外,所以字母表的前6个字母被使用。八进制很少甚至从不使用,如果某人认为八进制而非十六进制,则可以判断出他们的年龄(我来自十六进制一代,但曾经与那些从八进制一代中努力学习十六进制的人们共事)。
在计算机中,10进制就像普通人思考16进制。计算机不使用10进制(对于懒惰的人类,它们曾经使用BCD),它们使用2进制。在计算机中,十进制数1234实际上是0x4D2或0b010011010010。这是一个值,例如你想要将1234加上另一个数字,你需要该值,该值与符号1、2、3和4无关。但是,在stackoverflow上发布此答案时,我们不使用该数字,而是使用ASCII码,因此,1234的ascii码为0x31、0x32、0x33、0x34,对于您的euler解决方案非常重要,假设1000位数以ascii字符串形式提供,否则您需要将其从二进制转换为ascii,因为按照定义该问题是一个10进制问题而不是2进制问题。
回到我之前的问题。假设你有4位内存来存储一个数字,你能存储多大的数字?如果你只考虑十进制,你可能会认为这个数字是9,因为你习惯于在每个存储位置使用最大的符号,如果你有5个十进制的存储位置,99999是最大的数字。但回到4位二进制,单个bit的最大符号是1,把这个数字放在每个存储位置上,你就得到了1111(四个1)。仅仅看这四个1,你应该能够很容易地在脑海中看到这个数字的八进制和十六进制版本,分别是17八进制或F十六进制。要看到十进制需要数学计算,或者在这种情况下记忆,这个数字是15。所以你可以存储的最大的四位二进制数是0xF或15,而不是9。那么8位二进制数呢?0xFF或255(2的8次方减1)。最大的16位数字是65535,以此类推。
所以当我问你要使用多少位时,我的意思是这样的。看看这个数字99999。同样是十进制,你会认为这是最大的数字,但对于计算机来说,它只是部分完成了任务。99999十进制是0x1869F,需要17位内存来存储,你可以存储的最大的17位数字是0x1FFFF,即131071,比99999稍微大一点。因此,当你想在计算机上进行大数和数学运算时,你必须考虑二进制(或十六进制)。
最初你在做乘法,这仍然是欧拉问题的一部分,但我的问题与精度和位存储有关。以下是一些基础知识,我不会深入讨论,但你可以看到为什么我们依赖于计算机中的浮点单元。
取最大的4位二进制数1111,即15十进制。将它与最大的四位二进制数相加,你得到15+15=30=0x1E或11110二进制。因此,要添加两个四位二进制数,你需要五位来存储答案。计算机会为这个额外的位保留一个“进位”位。实际上,在计算机中,加/减整数函数允许你拥有N+1位。因此,如果是32位计算机,你基本上有33位用于加/减数学运算。
问题在于乘法和除法,即使今天仍有许多处理器不支持(是的,许多处理器没有浮点单元,只执行加法和减法,有时进行乘法,但除法很少。乘法和除法需要大量的电子学,折衷方案是可以通过软件使用加法和减法来完成)。以四位系统为最坏情况乘法为例:1111 * 1111 = 11100001,因此需要8位来存储4位乘法的结果,您很快会发现,如果您有一个4位系统,您想要执行的大多数乘法将得到无法存储在4位中的数字。所以当我看到你使用64位整数(无符号长整型通常为64位)并乘以四次时,这意味着您需要64 * 5或320位整数来存储您的答案,您试图将该答案放入一个64位的结果中,这很常见,取决于编译器和计算机,它会愉快地截断上位,并留下结果的低64位,这很容易看起来比任何操作数都小,这就是我一开始认为您可能已经做过的事情。
浮点数实际上不过是二进制科学计数法,如果您想要使用科学计数法将数字1234和5678相乘,则需要取1.234 * 10 ^ 3乘以5.678 * 10 ^ 3,得到7.007 * 10 ^ 6。您保留了精度并能够表示更广泛的数字范围。我不会深入讨论这在二进制中是如何工作的,但它对于您最初的问题没有用。
啊,最后要澄清一下我在我的问题/回答中所做的事情。二进制中的负整数。由于加法和减法与基本系统之间的关系,您可以玩一些技巧。例如,如果我想从数字7(十进制)中减去1,使用二进制,那么就没有减法电路,而是加上一个负数,因此7-1实际上是7 +(-1),这很重要:0111 +???? = 0110 您可以添加哪个数字以得到6 ... 在二进制中?
0111 + 1111 = 0110
二进制中的负数称为“二进制补码”,长话短说,答案是“反转并加1”。如何用二进制表示减1?取正1 0001,然后反转它,即使将1变成0,将0变成1(也称为一补数)1110,然后加1 1111。负1是计算机中的特殊数字(以及任何地方),因为无论您有多少位,它都表示为所有1。因此,当您看到有人这样做时: unsigned char a;
a = -1;
编译器首先看到-1,然后将其视为...11111(二进制)。然后它看到等号和另一侧,哦,你想让a变成全是1,它发现你有一个带符号的整数和一个无符号的整数,但转换只是移动位,所以你实际上是要求a = 0xFF;(假设是8位无符号字符)。
一些编译器可能会抱怨你试图在无符号数字中存储负数。其他编译器将查看该-1并将其视为32位或这些天也许是64位的有符号整数常量,然后当它将等式计算为8位无符号数时,你将得到一个警告,即你不能存储-1在有符号或无符号字符中而不使用类型转换。但如果你这样做:
a = 0; a--;
所有编译器都会喜欢它,并且不会抱怨,它只是在运行时烧掉计算周期,而不是在编译时。
现在我有个朋友告诉过我一本按顺序进行二进制数学运算的书。例如,要对一个数取反,通常你会使用反码加一的技巧,但有些人可能会告诉你另一个技巧。从右边开始复制零,直到包括第一个1,然后在此之后取反,所以减去2:
0010 1110
从右边开始复制0和第一个1,然后向左翻转其余位。
减6:
0110 1010
减4:
0100 1100
据说有一些技巧可以进行加法和减法(好吧,这些很容易),但也可以进行乘法和除法。如果你按顺序执行它们,那么你可以使用相同的ALU在二进制中执行无限长的数学运算。如果你知道如何做到这一点,你可以在软件中实现它,那么你最初的问题——乘以大常量(假设保留所有精度)——在任何计算机上都是微不足道的。

1
你得到的答案是18446744073709551496,这是因为当你将999...9赋值给long long时被截断了,再加上多次操作溢出。它是确定性的,但实际上只是一堆随机的位集合。

并不是真正的随机数。它们是乘法的模64结果。 - Ferruccio
我想我没有表达得够清楚。它是确定性的,意味着你可以算出来,但实际上是随机的,也就是说,计算它不值得花费这个精力。 - KeithB

0

数字无法适应于unsigned long long范围,因此您可以使用GMP库或使用字符串表示大数,就像我计算50的阶乘一样:

http://codepad.org/bkWNV0JC

#include <cmath>
#include <iostream>
using namespace std;
int main()
{
  unsigned int nd, nz;   
  unsigned char *ca;   
  unsigned int j, n=50, q, temp;
  int i;
  double p;
    p = 0.0;
    for(j = 2; j <= n; j++)
    {
      p += log10((double)j);  
    }
    nd = (int)p + 1;

    ca = new unsigned char[nd+1];
    if (!ca)
    {
      cout << "Could not allocate memory!!!";
      exit(0);
    }
    for (i = 1; (unsigned)i < nd; i++)
    {
      ca[i] = 0;
    }
    ca[0] = 1;

    p = 0.0;
    for (j = 2; j <= n; j++)
    {
      p += log10((double)j);   
      nz = (int)p + 1;        
      q = 0;                  
      for (i = 0;(unsigned) i <= nz; i++)
      {
        temp = (ca[i] * j) + q;
        q = (temp / 10);
        ca[i] = (char)(temp % 10);
      }
    }

    cout << "\nThe Factorial of " << n << " is: ";
    for( i = nd - 1; i >= 0; i--)
    {
      cout << (int)ca[i];
    }
  //  delete []ca;    
  return 0;
}

0

无符号整数表示系统字。今天,该字将达到2^32-1或2^64-1的最大值,具体取决于您的系统是32位还是64位。您已经达到了上限。

您必须编写一个bignum类或使用网络上的一个。

您为什么要解决这个问题呢?


http://projecteuler.net/index.php?section=problems&id=8 我认为这是最简单的方法 :) - AntonioCS
long long 在 C 中是存在的,所以你的观点还是有一定道理的。但在 C (以及下一个版本的 C++)中,long long 至少为 64 位。 - Martin York
如果您需要尝试Euler 8,则实际上不需要BigInteger库。因为您只需要将字符串的每个数字转换为数字,并将最后5个数字相乘即可。但是,BigInteger库确实有助于解决其他Euler问题。 - KTC

0
如果您可以使用Boost,可以尝试cpp_int。它可能比GMP慢一些,但它是一个仅包含头文件的库。
#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

int main()
{
   using namespace boost::multiprecision;
// Repeat at arbitrary precision:
   cpp_int u = 1;
   for(unsigned i = 1; i <= 100; ++i)
      u *= i;

   // prints 93326215443944152681699238856266700490715968264381621468592963895217599993229915608941463976156518286253697920827223758251185210916864000000000000000000000000 (i.e. 100!)
   std::cout << u << std::endl;

   return 0;
}

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