C ++에서 big int를 구현하는 방법


80

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

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

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


4
첫 번째-숫자 문자열은 괜찮지 만 기본 2 ^ 32 (4 억 개의 홀수)로 생각하십시오. 요즘에는 2 ^ 64를 기준으로 할 수도 있습니다. 둘째, 항상 부호없는 정수 "숫자"로 작업하십시오. 부호있는 큰 정수에 대해 두 가지 보완을 직접 수행 할 수 있지만 부호있는 정수로 오버플로 처리 등을 수행하려고하면 표준 정의되지 않은 bahaviour 문제가 발생합니다.
Steve314

3
알고리즘에 관해서는-기본 도서관의 경우 학교에서 배운 것이 맞습니다.
Steve314

1
다 정밀도 수학을 직접 수행하려면 Donald Knuth의 컴퓨터 프로그래밍 기술을 살펴 보시기 바랍니다 . 저는 Volume II, Seminumerical Algorithms, Chapter 4, Multiple Precision Arithmetic에 관심이 있다고 생각합니다. 또한 C ++에서 2 개의 임의 크기 정수를 추가하는 방법을 참조하십시오. , 일부 C ++ 라이브러리 및 OpenSSL에 대한 코드를 제공합니다.
jww

답변:


37

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

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

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

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


1
연산자를 수행하는 관용적 방법 은 여기 를 참조 하십시오 .
Mooing Duck

5
정수의 경우 연산자 << 및 >>는 비트 시프트 연산입니다. I / O로 해석하는 것은 잘못된 설계입니다.
Dave

3
@ 데이브 : 그것의 표준 C ++에서 사용하는 것을 제외 operator<<하고 operator>>iostreamI / O에 대한의.

9
당신은 여전히 I / 스트림 O ...와 함께 비트 시프트 작업 >> 정의 << 수 @ 데이브
miguel.martin

46

재미있는 도전. :)

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

데이터 유형 "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<<.

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


6
"int"(서명 됨)는 나쁜 생각입니다. 오버플로에 대한 표준 정의되지 않은 동작은 적어도 이식 가능한 논리를 올바르게 가져 오는 것을 어렵게 만듭니다 (불가능하지는 않더라도). 그러나 오버플로 동작이 모듈로 2 ^ n 결과를 제공하는 것으로 엄격하게 정의되는 부호없는 정수로 2의 보수로 작업하는 것은 매우 쉽습니다.
Steve314

29

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

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

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

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

즐기세요!


2
일부 참조 구현과 비교해도 더 이상 얻을 수 없습니다. 다른 문제가 있기 때문입니다. 참조 구현이 올바른지 테스트하는 방법? 일반적인 지식을 테스트하는데도 같은 문제가 있습니다. 한 사람이 다른 사람을 테스트해야한다면 누가 전자를 테스트할까요? 오래 전에 발명 된 공리에서 증명하는 것 외에는이 문제에서 벗어날 방법이 없습니다. 공리의 집합이 올바른 것으로 간주되고 (모순이 없음) 논리의 규칙에 따라 증명이 적절하게 도출되면 아무도 테스트 할 수없는 무한한 수의 경우에도 틀릴 수 없습니다.
SasQ


5

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


4

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


숫자를 십진수로 표현하고 싶다면 (즉, 단순한 필사자의 경우), 0-9 per nibble 알고리즘이 더 쉽습니다. 저장 공간을 포기하십시오.
dmckee --- 전 중재자 새끼 고양이

일반 바이너리보다 BCD 알고리즘을 수행하는 것이 더 쉽다고 생각하십니까?
Eclipse

2
AFAIK 10 진법은 자주 사용됩니다. 255 진법 (또는 10의 거듭 제곱이 아닌 숫자)을 10 진법으로 변환하는 것은 비용이 많이 들고 프로그램의 입력 및 출력은 일반적으로 10 진법이기 때문입니다.
Tobi

@Tobi : 나는 아마도 unsigned저장 공간의 59 %를 낭비한다는 단점 인 빠른 IO이고 곱하기 쉬운. 고급 학습을 위해서는 기본 (2 ^ 32)을 권장합니다. 이는 IO를 제외한 모든 것에 대해 기본 10/10000보다 훨씬 빠르지 만 곱셈 / 나눗셈을 구현하기가 훨씬 더 어렵습니다.
무 잉덕

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 학년까지 배운 알고리즘을 사용하십시오.
1 열부터 시작하여 10 열 등으로 시작합니다.


2

다른 사람들이 말했듯이, 구식의 긴 방법을 사용하되, 10 진법에서이 모든 작업을하지 마십시오. 65536 진법에서 모두 수행하고 일련의 long에 저장하는 것이 좋습니다.


1

대상 아키텍처가 숫자의 BCD (이진 코드 십진수) 표현을 지원하는 경우 수행해야하는 긴 곱셈 / 더하기에 대한 하드웨어 지원을받을 수 있습니다. 컴파일러가 BCD 명령어를 내보내도록하는 것은 당신이 읽어야 할 것입니다.

모토로라 68K 시리즈 칩에는이 기능이 있습니다. 내가 쓴 것도 아니에요.


0

내 시작은 31 비트와 32n을 오버플로로 사용하여 임의의 크기의 정수 배열을 갖는 것입니다.

스타터 작업은 ADD가되고 2의 보수를 사용하여 MAKE-NEGATIVE가됩니다. 그 후, 빼기는 사소하게 흐르고 일단 더하기 / 빼기를하면 다른 모든 것이 가능합니다.

아마도 더 정교한 접근 방식이있을 것입니다. 그러나 이것은 디지털 로직의 순진한 접근 방식입니다.


0

다음과 같이 구현해 볼 수 있습니다.

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

한 자리 0-9에는 4 비트 만 필요합니다.

따라서 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

-1

정수 문자열에서 48을 빼고 인쇄하여 큰 자릿수를 얻습니다. 그런 다음 기본적인 수학 연산을 수행합니다. 그렇지 않으면 완전한 솔루션을 제공 할 것입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.