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.