[c] 비트 이동 및 더하기 만 사용하여 곱하고 나누는 방법은 무엇입니까?

비트 이동 및 더하기 만 사용하여 곱하고 나누는 방법은 무엇입니까?



답변

더하기와 이동의 관점에서 곱하려면 다음과 같이 숫자 중 하나를 2의 거듭 제곱으로 분해해야합니다.

21 * 5 = 10101_2 * 101_2             (Initial step)
       = 10101_2 * (1 * 2^2  +  0 * 2^1  +  1 * 2^0)
       = 10101_2 * 2^2 + 10101_2 * 2^0
       = 10101_2 << 2 + 10101_2 << 0 (Decomposed)
       = 10101_2 * 4 + 10101_2 * 1
       = 10101_2 * 5
       = 21 * 5                      (Same as initial expression)

( _2베이스 2를 의미)

보시다시피 곱셈은 덧셈과 시프트로 분해 될 수 있습니다. 이것이 곱셈이 비트 시프트 나 더하기보다 더 오래 걸리는 이유이기도합니다. 비트 수에서 O (n)보다는 O (n ^ 2)입니다. 실제 컴퓨터 시스템 (이론적 컴퓨터 시스템과 반대)은 제한된 수의 비트를 가지고 있으므로 곱셈은 더하기 및 이동에 비해 일정한 배수가 걸립니다. 내가 올바르게 기억한다면, 현대 프로세서는 적절하게 파이프 라인을 사용한다면 프로세서에서 ALU (산술 단위)의 활용을 망쳐 서 덧셈만큼 빠르게 곱셈을 할 수 있습니다.


답변

Andrew Toulouse의 답변은 부서로 확장 될 수 있습니다.

정수 상수로 나누는 방법은 Henry S. Warren (ISBN 9780201914658)의 “Hacker ‘s Delight”책에서 자세히 설명합니다.

나누기를 구현하는 첫 번째 아이디어는 분모의 역값을 2 진법으로 쓰는 것입니다.

예 :
1/3 = (base-2) 0.0101 0101 0101 0101 0101 0101 0101 0101 .....

따라서
a/3 = (a >> 2) + (a >> 4) + (a >> 6) + ... + (a >> 30)
32 비트 산술의 경우.

명백한 방식으로 용어를 결합하여 작업 수를 줄일 수 있습니다.

b = (a >> 2) + (a >> 4)

b += (b >> 4)

b += (b >> 8)

b += (b >> 16)

나눗셈과 나머지를 계산하는 더 흥미로운 방법이 있습니다.

EDIT1 :

OP가 상수로 나누는 것이 아니라 임의의 숫자의 곱셈과 나눗셈을 의미하는 경우 다음 스레드를 사용할 수 있습니다. https://stackoverflow.com/a/12699549/1182653

EDIT2 :

정수 상수로 나누는 가장 빠른 방법 중 하나는 모듈 식 산술과 몽고메리 감소를 이용하는 것입니다. 정수를 3으로 나누는 가장 빠른 방법은 무엇입니까?


답변

X * 2 = 1 비트 왼쪽으로 이동
X / 2 = 1 비트 오른쪽으로 이동
X * 3 = 1 비트 왼쪽으로 이동 한 다음 X 추가


답변

x << k == x multiplied by 2 to the power of k
x >> k == x divided by 2 to the power of k

이러한 시프트를 사용하여 곱셈 연산을 수행 할 수 있습니다. 예를 들면 :

x * 14 == x * 16 - x * 2 == (x << 4) - (x << 1)
x * 12 == x * 8 + x * 4 == (x << 3) + (x << 2)

숫자를 2의 제곱이 아닌 값으로 나누기 위해 저수준 논리를 구현하고 다른 이진 연산을 사용하고 어떤 형태의 반복을 사용하지 않는 한 쉬운 방법을 알지 못합니다.


답변

  1. 왼쪽으로 1 위치 이동은 2를 곱하는 것과 유사합니다. 오른쪽 이동은 2로 나누는 것과 유사합니다.
  2. 루프를 추가하여 곱할 수 있습니다. 루프 변수와 더하기 변수를 올바르게 선택하면 성능을 제한 할 수 있습니다. 그것을 살펴본 후에는 농민 곱셈 을 사용해야합니다.


답변

파이썬 코드를 C로 번역했습니다. 주어진 예제에는 사소한 결함이있었습니다. 32 비트를 모두 차지하는 피제수 값이 있으면 시프트가 실패합니다. 문제를 해결하기 위해 내부적으로 64 비트 변수를 사용했습니다.

int No_divide(int nDivisor, int nDividend, int *nRemainder)
{
    int nQuotient = 0;
    int nPos = -1;
    unsigned long long ullDivisor = nDivisor;
    unsigned long long ullDividend = nDividend;

    while (ullDivisor <  ullDividend)
    {
        ullDivisor <<= 1;
        nPos ++;
    }

    ullDivisor >>= 1;

    while (nPos > -1)
    {
        if (ullDividend >= ullDivisor)
        {
            nQuotient += (1 << nPos);
            ullDividend -= ullDivisor;
        }

        ullDivisor >>= 1;
        nPos -= 1;
    }

    *nRemainder = (int) ullDividend;

    return nQuotient;
}


답변

교대와 더하기를 사용하는 정수를 나누는 절차는 초등학교에서 가르치는 것처럼 십진수 장 나눗셈에서 간단하게 파생 될 수 있습니다. 숫자가 0과 1 중 하나이므로 각 몫 숫자의 선택이 단순화됩니다. 현재 나머지가 제수보다 크거나 같으면 부분 몫의 최하위 비트는 1입니다.

십진수 긴 나눗셈과 마찬가지로 피제수는 최상위에서 최하위까지 한 번에 한 숫자로 간주됩니다. 이진 나눗셈의 왼쪽 이동으로 쉽게 수행 할 수 있습니다. 또한 현재 몫 비트를 한 위치만큼 왼쪽으로 이동 한 다음 새 몫 비트를 추가하여 몫 비트를 수집합니다.

고전적인 배열에서 이러한 두 개의 왼쪽 시프트는 하나의 레지스터 쌍의 왼쪽 시프트로 결합됩니다. 위쪽 절반에는 현재 나머지가 있고 아래쪽 절반에는 배당금이 있습니다. 배당 비트가 왼쪽 시프트에 의해 나머지 레지스터로 전송되므로 사용되지 않은 하위 절반의 최하위 비트가 몫 비트를 누적하는 데 사용됩니다.

아래는 x86 어셈블리 언어와이 알고리즘의 C 구현입니다. 시프트 및 더하기 나누기의이 특정 변형은 현재 나머지에서 제수를 빼는 것이 나머지가 제수보다 크거나 같지 않으면 수행되지 않기 때문에 때때로 “실패하지 않는”변형이라고도합니다. C에서는 레지스터 쌍 왼쪽 시프트의 어셈블리 버전에서 사용하는 캐리 플래그 개념이 없습니다. 대신, 모듈로 2 n 덧셈의 ​​결과가 수행이있는 경우에만 덧셈 보다 더 작을 수 있다는 관찰을 기반으로 에뮬레이션 됩니다.

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#define USE_ASM 0

#if USE_ASM
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
    uint32_t quot;
    __asm {
        mov  eax, [dividend];// quot = dividend
        mov  ecx, [divisor]; // divisor
        mov  edx, 32;        // bits_left
        mov  ebx, 0;         // rem
    $div_loop:
        add  eax, eax;       // (rem:quot) << 1
        adc  ebx, ebx;       //  ...
        cmp  ebx, ecx;       // rem >= divisor ?
        jb  $quot_bit_is_0;  // if (rem < divisor)
    $quot_bit_is_1:          // 
        sub  ebx, ecx;       // rem = rem - divisor
        add  eax, 1;         // quot++
    $quot_bit_is_0:
        dec  edx;            // bits_left--
        jnz  $div_loop;      // while (bits_left)
        mov  [quot], eax;    // quot
    }
    return quot;
}
#else
uint32_t bitwise_division (uint32_t dividend, uint32_t divisor)
{
    uint32_t quot, rem, t;
    int bits_left = CHAR_BIT * sizeof (uint32_t);

    quot = dividend;
    rem = 0;
    do {
            // (rem:quot) << 1
            t = quot;
            quot = quot + quot;
            rem = rem + rem + (quot < t);

            if (rem >= divisor) {
                rem = rem - divisor;
                quot = quot + 1;
            }
            bits_left--;
    } while (bits_left);
    return quot;
}
#endif