C ++에서 big int를 구현하는 방법 있습니다. 일반적인 전략은 숫자를

프로그래밍 연습으로 C ++에서 큰 int 클래스를 구현하고 싶습니다.이 클래스는 long int보다 큰 숫자를 처리 할 수 ​​있습니다. 이미 몇 가지 오픈 소스 구현이 있다는 것을 알고 있지만 직접 작성하고 싶습니다. 나는 올바른 접근 방식이 무엇인지 느끼려고 노력하고 있습니다.

일반적인 전략은 숫자를 문자열로 얻은 다음 더 작은 숫자 (예 : 단일 숫자)로 나누고 배열에 배치하는 것임을 이해합니다. 이 시점에서 다양한 비교 연산자를 구현하는 것은 비교적 간단해야합니다. 내 주요 관심사는 덧셈과 곱셈과 같은 것들을 어떻게 구현할 것인가입니다.

실제 작업 코드가 아닌 일반적인 접근 방식과 조언을 찾고 있습니다.



답변

큰 int 클래스에 대해 고려할 사항 :

  1. 수학 연산자 : +,-, /, *, % 클래스가 연산자의 양쪽에있을 수 있고, 연산자가 연결될 수 있으며, 피연산자 중 하나가 int, float, double 등일 수 있음을 잊지 마십시오. .

  2. I / O 연산자 : >>, << 여기에서는 사용자 입력에서 클래스를 올바르게 생성하는 방법과 출력용으로 형식을 지정하는 방법을 알아 봅니다.

  3. 변환 / 캐스트 : big int 클래스를 변환 할 수 있어야하는 유형 / 클래스를 파악하고 변환을 올바르게 처리하는 방법을 알아 봅니다. 빠른 목록에는 double 및 float가 포함되며 int (적절한 경계 검사 포함) 및 complex (범위를 처리 할 수 ​​있다고 가정)가 포함될 수 있습니다.


답변

재미있는 도전. 🙂

임의의 길이의 정수를 원한다고 가정합니다. 다음 접근 방식을 제안합니다.

데이터 유형 “int”의 이진 특성을 고려하십시오. 간단한 이진 연산을 사용하여 CPU의 회로가 무언가를 추가 할 때 수행하는 작업을 에뮬레이션하는 것을 고려하십시오. 더 심도있는 관심이 있다면 반가산기 및 완전 가산기에 대한이 위키피디아 기사를 읽어보십시오 . 당신은 그와 비슷한 일을하게 될 것입니다.하지만 당신은 그렇게 낮은 수준으로 내려갈 수 있습니다.하지만 게으 르기 때문에 저는 그냥 포기하고 더 간단한 해결책을 찾을 것이라고 생각했습니다.

그러나 더하기, 빼기, 곱하기에 대한 알고리즘 세부 정보로 들어가기 전에 데이터 구조를 찾아 보겠습니다. 물론 간단한 방법은 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-functor std :: plus 및 std :: minus, std :: times 및 std :: divides, …를 사용할 수도 있지만 캐리에 유의하십시오. 🙂 더하기 및 빼기 연산자를 사용하여 곱셈과 나눗셈을 구현할 수도 있지만, 각 반복에서 더하기 및 빼기에 대한 이전 호출에서 이미 계산 한 결과를 다시 계산하기 때문에 매우 느립니다. 이 간단한 작업을위한 좋은 알고리즘이 많이 있습니다. 위키피디아 나 웹을 사용하세요.

물론, 당신은 다음과 같은 표준 통신 구현해야합니다 operator<<단지에 VALUE_에 각 값을 이동을 (상기에서 시작, n 비트 떠났다 value_.size()-1… 오와 캐리 🙂 기억 operator<– 당신도 확인, 여기에 약간을 최적화 할 수 있습니다를 size()첫 번째 와 대략적인 자릿수 . 등등. 그런 다음 befriendig std :: ostream으로 클래스를 유용하게 만드십시오 operator<<.

이 접근 방식이 도움이되기를 바랍니다!


답변

이것에 대한 완전한 섹션이 있습니다 : [The Art of Computer Programming, vol.2 : Seminumerical Algorithms, section 4.3 Multiple Precision Arithmetic, pp. 265-318 (ed.3)]. 4 장, 산술에서 다른 흥미로운 자료를 찾을 수 있습니다.

정말로 다른 구현을보고 싶지 않다면 무엇을 배우고 싶습니까? 무수히 많은 실수를 저지르고이를 발견하는 것은 유익하고 위험합니다. 또한 중요한 컴퓨팅 경제를 식별하고 심각한 성능 문제를 방지하기위한 적절한 스토리지 구조를 갖추는 데 어려움이 있습니다.

당신을위한 도전 질문 : 당신의 구현을 어떻게 테스트 할 계획이고 그것이 산술이 옳다는 것을 증명하기 위해 어떻게 제안합니까?

(어떻게 작동하는지 보지 않고) 다른 구현을 테스트하기를 원할 수 있지만, 엄청난 수준의 테스트를 기대하지 않고 일반화 할 수 있으려면 그 이상이 필요합니다. 실패 모드 (메모리 부족 문제, 스택 부족, 너무 오래 실행 등)를 고려하는 것을 잊지 마십시오.

즐기세요!


답변

추가는 아마도 표준 선형 시간 알고리즘에서 수행되어야
하지만 곱셈을 위해 http://en.wikipedia.org/wiki/Karatsuba_algorithm을 시도 할 수 있습니다 .


답변

배열에있는 숫자의 자릿수를 얻으면 긴 손으로하는 것과 똑같이 덧셈과 곱셈을 할 수 있습니다.


답변

0-9를 숫자로 제한 할 필요가 없다는 것을 잊지 마십시오. 즉, 바이트를 숫자 (0-255)로 사용하고 십진 숫자와 동일한 방식으로 장수 연산을 수행 할 수 있습니다. long 배열을 사용할 수도 있습니다.


답변

문자열을 사용하는 것이 올바른 방법이라고 확신하지 않습니다. 코드를 직접 작성 해본 적이 없지만 기본 숫자 유형의 배열을 사용하는 것이 더 나은 해결책이 될 수 있다고 생각합니다. 아이디어는 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;
}

약간의 단순한 예이지만 사용중인 기본 숫자 클래스의 가변 번호를 가진 구조로 확장하는 방법은 상당히 분명합니다.