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


86

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


20
중학교 때 종이에서 하듯이 십진수 대신 이진수 만 사용합니다.
Pascal Cuoq 2010

1
@mtk : 이 답변 에서 누락 된 것은 무엇입니까 ? C 또는 어셈블리 구현, 특정 피연산자 너비, 특정 분할 방법 (예 : 복원 대 복원하지 않음)을 찾고 있습니까?
njuffa 2015 년

빼기가 괜찮습니까? 모든 것이 덮힌 것 같습니다
mksteve 2015-09-09

이 질문 뒤에 무엇이 필요합니까? CPU는 곱하기 및 나누기 연산을 이미 비트 시프 팅과 더하기 또는 빼기로 변환합니다.
켈리 S. 프랑스어

@ KellyS.French 호기심 만 있으면 컴파일러가 제한된 명령어 세트로 작동하는 방법을 상상하는 방법에 더 가깝습니다.
Spidfire

답변:


77

더하기와 이동의 관점에서 곱하려면 다음과 같이 숫자 중 하나를 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 (산술 단위)의 활용을 망쳐 서 덧셈만큼 빠르게 곱셈을 할 수 있습니다.


5
오래 전 일이라는 건 알지만, 부서의 예를 들어 주실 수 있나요? 감사합니다
GniruT

42

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으로 나누는 가장 빠른 방법은 무엇입니까?


Hacker 's Delight 참조에 감사드립니다!
alecxe

2
예,이 답변 (상수로 나누기)은 부분적으로 만 정확합니다. '3/3'을 시도하면 0이됩니다. Hacker 's Delight에서는 실제로 보상해야하는 오류가 있다고 설명합니다. 이 경우 : b += r * 11 >> 5r = a - q * 3. 링크 : hackersdelight.org/divcMore.pdf 페이지 2+.
atlaste

31

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


4
add X마지막 을 의미 합니까?
Mark Byers

1
여전히 잘못되었습니다. 마지막 줄에 "X * 3 = 왼쪽으로 1 비트 이동 한 다음 X 추가"
Paul R

1
"X / 2 = 1 비트 오른쪽으로 시프트", 전적으로는 아니지만 0 (음수의 경우)이 아닌 무한대로 내림합니다. 이는 일반적인 나눗셈 구현입니다 (적어도 내가 본 것).
Leif Andersen

25

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


@IVlad : 위의 작업을 결합하여 예를 들어 3으로 나누는 방법은 무엇입니까?
Paul R

@Paul R-사실, 더 어렵습니다. 내 대답을 명확히했습니다.
IVlad 2010

상수로 나누는 것은 너무 어렵지 않지만 (마법 상수로 곱한 다음 2의 거듭 제곱으로 나누기) 변수로 나누는 것은 약간 까다 롭습니다.
Paul R

1
x * 14 == x * 16-x * 2 == (x << 4)-(x << 2) 실제로 (x << 4)-(x << 1) x <이후 <1은 x에 2를 곱하는 것입니까?
Alex Spencer

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

9
+1 :하지만 왼쪽 Shift 2. 그것은 곱 단지 유사하지 않다 입니다 ... 오버 플로우 될 때까지 적어도 2로 곱
돈 로비

shift-division은 음수에 대해 잘못된 결과를 생성합니다.
David

6

파이썬 코드를 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;
}

음수는 어떻습니까? eclipse + CDT를 사용하여 -12345를 10으로 테스트했지만 그 결과는 좋지 않았습니다.
kenmux

루프를 ullDivisor >>= 1시작하기 전에 왜 그렇게했는지 말씀해 주 while시겠습니까? 또한 nPos >= 0트릭을하지 않겠습니까?
Vivekanand V

@kenmux 관련된 숫자의 크기 만 고려하고 먼저 알고리즘을 수행 한 다음 적절한 의사 결정 문을 사용하여 몫 / 나머지에 적절한 부호를 반환해야합니다!
Vivekanand V

1
@VivekanandV 기호 추가를 의미합니다-나중에? 예, 작동합니다.
kenmux

5

교대와 더하기를 사용하는 정수를 나누는 절차는 초등학교에서 가르치는 것처럼 십진수 장 나눗셈에서 간단하게 파생 될 수 있습니다. 숫자가 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

@greybeard 포인터 주셔서 감사합니다. 맞습니다. 배당금을 몫과 섞었습니다. 내가 고칠 게.
njuffa

4

두 개의 숫자, 즉 9와 10을 취하고 이진수로 작성하십시오-1001 및 1010.

0의 결과 R로 시작합니다.

이 경우에는 1010이라는 숫자 중 하나를 가져 와서 A라고 부르고 1 비트 씩 오른쪽으로 이동합니다. 1을 이동하면 첫 번째 숫자를 추가하고 B라고 부르겠습니다.

이제 B를 1 비트 왼쪽으로 이동하고 모든 비트가 A에서 이동 될 때까지 반복합니다.

기록 된 것을 보면 무슨 일이 일어나는지 더 쉽게 알 수 있습니다. 다음은 그 예입니다.

      0
   0000      0
  10010      1
 000000      0
1001000      1
 ------
1011010

이것은 가장 빠른 것처럼 보이며 가장 작은 숫자의 비트를 반복하고 결과를 계산하려면 약간의 추가 코딩이 필요합니다.
Hellonearthis

2

여기 에서 가져 왔습니다 .

이것은 부서에만 해당됩니다.

int add(int a, int b) {
        int partialSum, carry;
        do {
            partialSum = a ^ b;
            carry = (a & b) << 1;
            a = partialSum;
            b = carry;
        } while (carry != 0);
        return partialSum;
}

int subtract(int a, int b) {
    return add(a, add(~b, 1));
}

int division(int dividend, int divisor) {
        boolean negative = false;
        if ((dividend & (1 << 31)) == (1 << 31)) { // Check for signed bit
            negative = !negative;
            dividend = add(~dividend, 1);  // Negation
        }
        if ((divisor & (1 << 31)) == (1 << 31)) {
            negative = !negative;
            divisor = add(~divisor, 1);  // Negation
        }
        int quotient = 0;
        long r;
        for (int i = 30; i >= 0; i = subtract(i, 1)) {
            r = (divisor << i);
           // Left shift divisor until it's smaller than dividend
            if (r < Integer.MAX_VALUE && r >= 0) { // Avoid cases where comparison between long and int doesn't make sense
                if (r <= dividend) { 
                    quotient |= (1 << i);    
                    dividend = subtract(dividend, (int) r);
                }
            }
        }
        if (negative) {
            quotient = add(~quotient, 1);
        }
        return quotient;
}

2

기본적으로 기본 거듭 제곱 2로 곱하고 나눕니다.

왼쪽으로 이동 = x * 2 ^ y

오른쪽으로 이동 = x / 2 ^ y

shl eax, 2 = 2 * 2 ^ 2 = 8

shr eax, 3 = 2/2 ^ 3 = 1/4


eax과 같은 분수 값을 보유 할 수 없습니다 1/4. (정수 대신 고정 소수점을 사용하지만 지정하지 않은 경우)
Peter Cordes

1

이것은 곱셈에서 작동합니다.

.data

.text
.globl  main

main:

# $4 * $5 = $2

    addi $4, $0, 0x9
    addi $5, $0, 0x6

    add  $2, $0, $0 # initialize product to zero

Loop:   
    beq  $5, $0, Exit # if multiplier is 0,terminate loop
    andi $3, $5, 1 # mask out the 0th bit in multiplier
    beq  $3, $0, Shift # if the bit is 0, skip add
    addu $2, $2, $4 # add (shifted) multiplicand to product

Shift: 
    sll $4, $4, 1 # shift up the multiplicand 1 bit
    srl $5, $5, 1 # shift down the multiplier 1 bit
    j Loop # go for next  

Exit: #


EXIT: 
li $v0,10
syscall

조립의 어떤 맛?
Keith Pinson

1
이것이 당신이 요구하는 것이라면 MIPS 어셈블리입니다. MARS를 사용하여 작성 / 실행 한 것 같습니다.
Melsi

1

아래 방법은 두 숫자가 양수임을 고려하여 이진 나누기를 구현하는 것입니다. 빼기가 문제라면 이항 연산자를 사용하여 구현할 수도 있습니다.

암호

-(int)binaryDivide:(int)numerator with:(int)denominator
{
    if (numerator == 0 || denominator == 1) {
        return numerator;
    }

    if (denominator == 0) {

        #ifdef DEBUG
            NSAssert(denominator==0, @"denominator should be greater then 0");
        #endif
        return INFINITY;
    }

    // if (numerator <0) {
    //     numerator = abs(numerator);
    // }

    int maxBitDenom = [self getMaxBit:denominator];
    int maxBitNumerator = [self getMaxBit:numerator];
    int msbNumber = [self getMSB:maxBitDenom ofNumber:numerator];

    int qoutient = 0;

    int subResult = 0;

    int remainingBits = maxBitNumerator-maxBitDenom;

    if (msbNumber >= denominator) {
        qoutient |=1;
        subResult = msbNumber - denominator;
    }
    else {
        subResult = msbNumber;
    }

    while (remainingBits > 0) {
        int msbBit = (numerator & (1 << (remainingBits-1)))>0?1:0;
        subResult = (subResult << 1) | msbBit;
        if(subResult >= denominator) {
            subResult = subResult - denominator;
            qoutient= (qoutient << 1) | 1;
        }
        else{
            qoutient = qoutient << 1;
        }
        remainingBits--;

    }
    return qoutient;
}

-(int)getMaxBit:(int)inputNumber
{
    int maxBit = 0;
    BOOL isMaxBitSet = NO;
    for (int i=0; i<sizeof(inputNumber)*8; i++) {
        if (inputNumber & (1<<i)) {
            maxBit = i;
            isMaxBitSet=YES;
        }
    }
    if (isMaxBitSet) {
        maxBit+=1;
    }
    return maxBit;
}


-(int)getMSB:(int)bits ofNumber:(int)number
{
    int numbeMaxBit = [self getMaxBit:number];
    return number >> (numbeMaxBit - bits);
}

곱셈의 경우 :

-(int)multiplyNumber:(int)num1 withNumber:(int)num2
{
    int mulResult = 0;
    int ithBit;

    BOOL isNegativeSign = (num1<0 && num2>0) || (num1>0 && num2<0);
    num1 = abs(num1);
    num2 = abs(num2);


    for (int i=0; i<sizeof(num2)*8; i++)
    {
        ithBit =  num2 & (1<<i);
        if (ithBit>0) {
            mulResult += (num1 << i);
        }

    }

    if (isNegativeSign) {
        mulResult =  ((~mulResult)+1);
    }

    return mulResult;
}

이 구문은 무엇입니까? -(int)multiplyNumber:(int)num1 withNumber:(int)num2?
SS Anne

0

16 비트 x86 솔루션에 관심있는 사람들을위한,에 의해 코드 조각이 JasonKnight 여기 1 (그는 또한 내가 테스트하지 않은 서명 곱셈 조각을 포함). 그러나이 코드에는 "add bx, bx"부분이 오버플로되는 큰 입력 문제가 있습니다.

고정 버전 :

softwareMultiply:
;    INPUT  CX,BX
;   OUTPUT  DX:AX - 32 bits
; CLOBBERS  BX,CX,DI
    xor   ax,ax     ; cheap way to zero a reg
    mov   dx,ax     ; 1 clock faster than xor
    mov   di,cx
    or    di,bx     ; cheap way to test for zero on both regs
    jz    @done
    mov   di,ax     ; DI used for reg,reg adc
@loop:
    shr   cx,1      ; divide by two, bottom bit moved to carry flag
    jnc   @skipAddToResult
    add   ax,bx
    adc   dx,di     ; reg,reg is faster than reg,imm16
@skipAddToResult:
    add   bx,bx     ; faster than shift or mul
    adc   di,di
    or    cx,cx     ; fast zero check
    jnz   @loop
@done:
    ret

또는 GCC 인라인 어셈블리에서도 동일합니다.

asm("mov $0,%%ax\n\t"
    "mov $0,%%dx\n\t"
    "mov %%cx,%%di\n\t"
    "or %%bx,%%di\n\t"
    "jz done\n\t"
    "mov %%ax,%%di\n\t"
    "loop:\n\t"
    "shr $1,%%cx\n\t"
    "jnc skipAddToResult\n\t"
    "add %%bx,%%ax\n\t"
    "adc %%di,%%dx\n\t"
    "skipAddToResult:\n\t"
    "add %%bx,%%bx\n\t"
    "adc %%di,%%di\n\t"
    "or %%cx,%%cx\n\t"
    "jnz loop\n\t"
    "done:\n\t"
    : "=d" (dx), "=a" (ax)
    : "b" (bx), "c" (cx)
    : "ecx", "edi"
);

-1

이 시도. https://gist.github.com/swguru/5219592

import sys
# implement divide operation without using built-in divide operator
def divAndMod_slow(y,x, debug=0):
    r = 0
    while y >= x:
            r += 1
            y -= x
    return r,y 


# implement divide operation without using built-in divide operator
def divAndMod(y,x, debug=0):

    ## find the highest position of positive bit of the ratio
    pos = -1
    while y >= x:
            pos += 1
            x <<= 1
    x >>= 1
    if debug: print "y=%d, x=%d, pos=%d" % (y,x,pos)

    if pos == -1:
            return 0, y

    r = 0
    while pos >= 0:
            if y >= x:
                    r += (1 << pos)                        
                    y -= x                
            if debug: print "y=%d, x=%d, r=%d, pos=%d" % (y,x,r,pos)

            x >>= 1
            pos -= 1

    return r, y


if __name__ =="__main__":
    if len(sys.argv) == 3:
        y = int(sys.argv[1])
        x = int(sys.argv[2])
     else:
            y = 313271356
            x = 7

print "=== Slow Version ...."
res = divAndMod_slow( y, x)
print "%d = %d * %d + %d" % (y, x, res[0], res[1])

print "=== Fast Version ...."
res = divAndMod( y, x, debug=1)
print "%d = %d * %d + %d" % (y, x, res[0], res[1])

5
이것은 파이썬처럼 보입니다. 이 질문은 조립 및 / 또는 C에 대해 요청되었습니다.
void
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.