如何在C ++中实现big int


80

我想在C ++中实现一个大型int类作为编程练习,该类可以处理大于long int的数字。我知道已经有几种开源实现,但是我想写自己的。我正在尝试了解正确的方法是什么。

我知道一般的策略是将数字作为字符串获取,然后将其分解为较小的数字(例如,单个数字),然后将它们放置在数组中。在这一点上,实现各种比较运算符应该相对简单。我主要关心的是如何实现加法和乘法。

我正在寻找一种通用的方法和建议,而不是实际的工作代码。


4
第一件事-数字字符串很好,但请以2 ^ 32(40亿个奇数个不同的数字)为基数。这些天甚至可能以2 ^ 64为底。其次,始终使用无符号整数“数字”。您可以自己对带符号的大整数进行二进制补码,但是如果尝试对带符号的整数进行溢出处理等,则会遇到标准未定义的行为问题。
Steve314 2011年

3
至于算法-对于基本库,您在学校学到的算法是正确的。
Steve314 2011年

1
如果您想自己执行多精度数学,那么建议您看看Donald Knuth的“计算机编程艺术”。我相信您对第二卷《半数值算法》第4章“多精度算术”感兴趣。另请参阅如何在C ++中添加2个任意大小的整数?,其中提供了一些C ++库和OpenSSL的代码。
jww

Answers:


36

大int类要考虑的事项:

  1. 数学运算符:+,-,/,*,%不要忘记您的类可能在运算符的任一侧,可以将运算符链接起来,其中一个操作数可以是int,float,double等。

  2. I / O运算符:>>,<<在这里,您可以找到如何根据用户输入正确创建类以及如何格式化输出类的方法。

  3. 转换/转换:弄清楚您的大int类应转换为什么类型/类,以及如何正确处理转换。快速列表将包括double和float,并且可能包括int(使用适当的边界检查)和complex(假设它可以处理范围)。


1
有关操作员的惯用方法,请参见此处
Mooing Duck 2012年

5
对于整数,运算符<<和>>是位移操作。将它们解释为I / O是不好的设计。
戴夫

3
@Dave:除了使用标准C ++operator<<operator>>使用iostreams进行I / O之外。

9
@Dave您仍可以为流操作定义<<和>>以及流的I / O ...
miguel.martin 2013年

45

一个有趣的挑战。:)

我假设您想要任意长度的整数。我建议采用以下方法:

考虑数据类型“ int”的二进制性质。考虑使用简单的二进制操作来模拟CPU在添加东西时的电路功能。如果您有更深入的兴趣,请考虑阅读此Wikipedia文章,关于半加法器和全加法器。您将执行类似的操作,但是您可以降低到最低水平-但要懒惰,我想我会放弃并找到一个更简单的解决方案。

但是在讨论有关加,减,乘的任何算法细节之前,让我们先找到一些数据结构。当然,一种简单的方法是将内容存储在std :: vector中。

template< class BaseType >
class BigInt
{
typedef typename BaseType BT;
protected: std::vector< BaseType > value_;
};

您可能要考虑是否要制作固定大小的向量,以及是否要对其进行预分配。原因是对于多种运算,您将必须遍历向量的每个元素-O(n)。您可能想立即了解一个操作的复杂程度,而固定n可以做到这一点。

但是现在就对一些算法进行数字运算。您可以在逻辑级别上做到这一点,但是我们将使用该神奇的CPU功能来计算结果。但是,我们将从Half-和FullAdders的逻辑图示中接管的是它处理进位的方式。例如,考虑如何实现+ =运算符。对于BigInt <> :: value_中的每个数字,您需要将其相加,然后看结果是否产生某种形式的进位。我们不会按位进行操作,而是依靠我们的BaseType的性质(它是long还是int或short等等):它会溢出。

当然,如果您将两个数字相加,则结果必须大于这些数字中的较大者,对吗?如果不是,则结果溢出。

template< class BaseType >
BigInt< BaseType >& BigInt< BaseType >::operator += (BigInt< BaseType > const& operand)
{
  BT count, carry = 0;
  for (count = 0; count < std::max(value_.size(), operand.value_.size(); count++)
  {
    BT op0 = count < value_.size() ? value_.at(count) : 0, 
       op1 = count < operand.value_.size() ? operand.value_.at(count) : 0;
    BT digits_result = op0 + op1 + carry;
    if (digits_result-carry < std::max(op0, op1)
    {
      BT carry_old = carry;
      carry = digits_result;
      digits_result = (op0 + op1 + carry) >> sizeof(BT)*8; // NOTE [1]
    }
    else carry = 0;
  }

  return *this;
}
// NOTE 1: I did not test this code. And I am not sure if this will work; if it does
//         not, then you must restrict BaseType to be the second biggest type 
//         available, i.e. a 32-bit int when you have a 64-bit long. Then use
//         a temporary or a cast to the mightier type and retrieve the upper bits. 
//         Or you do it bitwise. ;-)

其他算术运算类似。哎呀,您甚至可以使用stl-functors std :: plus和std :: minus,std :: times和std :: divides ... ...,但要注意进位。:)您还可以使用加号和减号运算符来实现乘法和除法,但这非常慢,因为那样会重新计算您在每次迭代中对加号和减号的先前调用中已经计算出的结果。对于这个简单的任务,有很多好的算法,可以使用 维基百科或网络。

当然,您应该实现标准的运算符,例如operator<<(只需将value_中的每个值向左移动n位,从value_.size()-1...处开始,并记住进位:),operator<-您甚至可以在此处进行一些优化,检查size()第一位的粗略数字。等等。然后通过befriendig std :: ostream使您的课程有用operator<<

希望这种方法有用!


6
“ int”(如已签名)是一个坏主意。标准的未定义行为(如果不是不可能的话)将导致(即使不是不可能)使逻辑正确,至少是可移植的。但是,与无符号整数进行二进制补码很容易,因为溢出行为严格定义为给出模2 ^ n结果。
Steve314 2011年

28

有一个完整的章节:[计算机编程的艺术,第2卷:半数值算法,第4.3节“多精度算术”,第265-318页(第3版)]。您可能会在第4章“算术”中找到其他有趣的材料。

如果您真的不想查看其他实现,是否考虑过要学习的内容?犯了无数错误,发现这些错误是有益的,也是危险的。在确定重要的计算经济性和具有适当的存储结构以避免严重的性能问题方面也存在挑战。

您面临的一个挑战问题:您打算如何测试实现,以及如何建议证明它的算法正确?

您可能希望对另一种实现进行测试(而无需查看它的工作方式),但是要进行概括而又不期望进行过多的测试,这将花费更多。不要忘记考虑故障模式(内存不足问题,堆栈不足,运行时间过长等)。

玩得开心!


2
与某些参考实现进行比较不会让您更进一步,因为那样您就会遇到另一个问题:如何测试参考实现是否还正确?一般而言,测试知识也存在相同的问题:如果一个人必须测试另一个人,谁来测试前一个人?除了很久以前发明的一个问题以外,没有其他方法可以解决这个问题:从公理证明。如果公理集被认为是正确的(没有矛盾),并且根据逻辑规则正确地得出了证明,那它就不会是错误的,即使对于无数人无法检验的情况。
2014年


5

将数字放在数组中后,就可以进行加法和乘法运算,就像进行长时一样。


4

不要忘记,您不需要将自己限制为0-9(例如数字),即使用字节作为数字(0-255),并且仍然可以像对十进制数字一样进行长手算术运算。您甚至可以使用long数组。


如果要用十进制表示数字(即仅是凡人),则每个半字节算法0-9更容易。只是放弃存储。
dmckee ---前主持人小猫,

您知道做BCD算法比常规的二进制算法容易吗?

2
AFAIK基座10通常被使用,因为从/在基座255(或任何不为10的功率)转换大数字到基座10是昂贵的,并且您的程序的输入和输出通常在基体10
托比

@Tobi:我可能会建议以10000为基数保存在unsigned,这是快速的IO,并且易于进行乘法操作,其缺点是浪费了59%的存储空间。我建议使用基数(2 ^ 32)进行更高级的学习,这很多比基10/10000一切除IO快,但更难实现乘法/除法。
Mooing Duck 2012年

3

我不认为使用字符串是正确的方法-尽管我自己从未编写过代码,但我认为使用基本数字类型的数组可能是更好的解决方案。这个想法是,您只需扩展已有的内容,就像CPU将一位扩展为整数一样。

例如,如果您有一个结构

typedef struct {
    int high, low;
} BiggerInt;

然后,请注意溢出条件,然后可以对每个“数字”(在本例中为高和低)手动执行本机操作:

BiggerInt add( const BiggerInt *lhs, const BiggerInt *rhs ) {
    BiggerInt ret;

    /* Ideally, you'd want a better way to check for overflow conditions */
    if ( rhs->high < INT_MAX - lhs->high ) {
        /* With a variable-length (a real) BigInt, you'd allocate some more room here */
    }

    ret.high = lhs->high + rhs->high;

    if ( rhs->low < INT_MAX - lhs->low ) {
        /* No overflow */
        ret.low = lhs->low + rhs->low;
    }
    else {
        /* Overflow */
        ret.high += 1;
        ret.low = lhs->low - ( INT_MAX - rhs->low ); /* Right? */
    }

    return ret;
}

这只是一个简单的示例,但是如何扩展到具有可变数量的结构(无论您使用什么基数类)的结构应该很明显。


通过字符串,OP表示采用一个字符串,该字符串包含其数字表示形式中的所需数字(使用任何基数),并使用该值初始化BigInt。
KTC

STLPLUS使用字符串保存大整数。
lsalamon

2

使用您从1年级到4年级学习的算法。
从“ ones”列开始,然后是“十”,依此类推。


2

就像其他人所说的那样,使用老式的老式方法,但不要在base 10中进行所有操作。我建议在65536 base中进行所有操作,并将其存储在long中。


1

如果您的目标体系结构支持数字的BCD(二进制编码的十进制)表示形式,则可以为所需的长手乘法/加法获得一些硬件支持。让编译器发出BCD指令是您必须阅读的内容...

摩托罗拉68K系列芯片具有此功能。并不是说我很苦或什么。


0

我的出发点是拥有一个任意大小的整数数组,使用31位和32n'd作为溢出。

起始运算符为ADD,然后使用2的补码进行负运算。之后,减法就变得微不足道了,一旦您进行了加/减,其他所有操作都是可行的。

可能有更复杂的方法。但这将是数字逻辑的幼稚方法。


0

可以尝试实现以下内容:

http://www.docjar.org/html/api/java/math/BigInteger.java.html

您只需要4位即可获得0-9的单个数字

因此,一个Int值最多允许8位数字。我决定坚持使用字符数组,因此我使用了两倍的内存,但对我来说,它只被使用过一次。

同样,当将所有数字存储在单个int中时,它会使它过于复杂,甚至可能使它变慢。

我没有任何速度测试,但看着BigInteger的Java版本,似乎正在做很多工作。

对我来说我做下面

//Number = 100,000.00, Number Digits = 32, Decimal Digits = 2.
BigDecimal *decimal = new BigDecimal("100000.00", 32, 2);
decimal += "1000.99";
cout << decimal->GetValue(0x1 | 0x2) << endl; //Format and show decimals.
//Prints: 101,000.99

OP从未说过他/她想关注十进制数字。
einpoklum '16

-1

从整数字符串中减去48并打印以获取大数位数。然后执行基本的数学运算。否则,我将提供完整的解决方案。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.