C의 정수에서 가장 높은 설정 비트 (msb)를 찾는 가장 빠르고 효율적인 방법은 무엇입니까?


119

정수 n이 있고 최상위 비트의 위치를 ​​알고 싶다면 (즉, 최하위 비트가 오른쪽에 있으면 가장 먼 왼쪽 비트 인 1의 위치를 ​​알고 싶습니다), 가장 빠르고 효율적인 방법은 무엇입니까?

POSIX가 ffs()첫 번째 세트 비트를 찾기 위해 strings.h 의 메서드를 지원한다는 것을 알고 있지만 해당하는 것 같지 않습니다.fls() 메서드 .

내가 놓친 정말 명백한 방법이 있습니까?

이식성을 위해 POSIX 기능을 사용할 수없는 경우는 어떻습니까?

편집 : 32 비트 및 64 비트 아키텍처 모두에서 작동하는 솔루션은 어떻습니까 (많은 코드 목록이 32 비트 정수에서만 작동하는 것처럼 보입니다).


여기에 몇 가지 구현이 있습니다. graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear (편집 : 귀하의 질문을 다시 읽은 후 위의 링크는 필요에 따라 가장 왼쪽이 아닌 가장 오른쪽 세트 비트를 찾는 것입니다. 워드 크기의 감각, 그것은 대답 까다로운 일)입니다
쓰셨

2
Hacker 's Delight의 "수행 0 알고리즘 "을 참조하십시오 .
Darius Bacon

오른쪽에 0이 표시됩니다 . 질문은 왼쪽의 0에 관한 것이 었습니다. 최소한 훑어 보면 나는 그것을 보지 못한다.
Darius Bacon

2
비트 번호 'n'을 구체적으로 원합니까, 아니면 2 ^ n으로 충분합니까?
Alnitak

1
"Log Base 2"알고리즘을 살펴보십시오. Anderson이 기사에서 "정수의 log base 2는 최상위 비트 세트 (또는 최상위 비트 세트, MSB)의 위치와 동일합니다."
Michael Burr

답변:


64

GCC에는 다음 이 있습니다 .

 -내장 함수 : int __builtin_clz (unsigned int x)
     X의 선행 0 비트 수를 반환합니다.
     중요한 비트 위치. X가 0이면 결과는 정의되지 않습니다.

 -내장 함수 : int __builtin_clzl (unsigned long)
     인수 유형이 'unsigned'라는 점을 제외하면`__builtin_clz '와 유사합니다.
     긴'.

 -내장 함수 : int __builtin_clzll (unsigned long long)
     인수 유형이 'unsigned'라는 점을 제외하면`__builtin_clz '와 유사합니다.
     long long '.

나는 그것들이 당신의 현재 플랫폼에 대해 합리적으로 효율적인 것으로 번역되기를 기대합니다. 그것이 멋진 비트 트위들 링 알고리즘 중 하나이든 단일 명령이든 상관 없습니다.


입력 이 0 일 있는 경우 유용한 트릭 은 __builtin_clz(x | 1)다음과 같습니다 . 다른 입력 을 수정하지 않고 낮은 비트를 무조건 설정하면 다른 입력에 대한 출력을 변경하지 않고에 대한 출력 31을 만듭니다 x=0.

그렇게 할 필요가 없도록 다른 옵션은 ARM GCC __clz(헤더 필요 없음) 또는 명령어 _lzcnt_u32를 지원하는 CPU 의 x86 과 같은 플랫폼 별 내장 함수 lzcnt입니다. ( 0이 아닌 입력에 대해 31-lzcnt를 제공하는 오류 대신 이전 CPU에서 lzcnt와 같이 디코딩 bsr합니다.)

불행히도 입력 = 0에 대한 결과를 32 또는 64 (피연산자 너비에 따라)로 정의하는 x86이 아닌 플랫폼에서 다양한 CLZ 명령어를 이식 가능하게 활용할 수있는 방법이 없습니다. x86 lzcnt도 그렇게하는 반면, bsr를 사용하지 않는 한 컴파일러가 뒤집어 야하는 비트 인덱스를 생성합니다 31-__builtin_clz(x).

( "정의되지 않은 결과"는 C Undefined Behavior가 아니라 정의되지 않은 값입니다. 실제로 명령이 실행될 때 대상 레지스터에 있던 모든 것입니다. AMD는이를 문서화하고 Intel은 그렇지 않지만 Intel의 CPU는 해당 동작을 구현합니다. . 그러나 그것은 당신이 할당하고있는 C 변수에 이전에 있던 것이 아닙니다 . 그것은 gcc가 C를 asm 으로 바꿀 때 일반적으로 작동하는 방식이 아닙니다. 또한 LZCNT의 "출력 의존성"을 깨는 것이 왜 중요합니까? )



1
0에서 정의되지 않은 동작을 사용하면 LZCNT를 사용할 수없는 경우에도 x86에서 단일 BSR 명령어로 컴파일 할 수 있습니다. 이것은 __builtin_ctzover ffs에서 큰 이점이며 , 입력이 0이었던 경우를 처리하기 위해 BSF 및 CMOV로 컴파일됩니다. 충분히 짧은 구현이없는 아키텍처 (예 : clz명령어가 없는 오래된 ARM )에서 gcc는 libgcc 도우미 함수에 대한 호출을 내 보냅니다.
Peter Cordes

41

약간의 인라인 어셈블러를 위해 x86 및 게임을 사용하고 있다고 가정하면 인텔은 BSR지침 ( "비트 스캔 역방향")을 제공합니다. 그것은의 빠른일부 (다른 사람에 microcoded) x86s. 매뉴얼에서 :

소스 피연산자에서 최상위 세트 비트 (1 비트)를 검색합니다. 최상위 1 비트가 발견되면 해당 비트 인덱스가 대상 피연산자에 저장됩니다. 소스 피연산자는 레지스터 또는 메모리 위치 일 수 있습니다. 대상 피연산자는 레지스터입니다. 비트 인덱스는 소스 피연산자의 비트 0에서 부호없는 오프셋입니다. 콘텐츠 소스 피연산자가 0이면 대상 피연산자의 콘텐츠가 정의되지 않습니다.

(PowerPC를 사용하는 경우 유사한 cntlz( "선행 0 카운트") 명령이 있습니다.)

gcc의 예제 코드 :

#include <iostream>

int main (int,char**)
{
  int n=1;
  for (;;++n) {
    int msb;
    asm("bsrl %1,%0" : "=r"(msb) : "r"(n));
    std::cout << n << " : " << msb << std::endl;
  }
  return 0;
}

인라인 어셈블러 튜토리얼을 참조하십시오. 이것은 루핑 코드보다 훨씬 빠르다는 것을 보여줍니다 (섹션 9.4).


4
실제로이 명령어는 일반적으로 루프로 마이크로 코딩되며 다소 느립니다.
rlbond

2
어느 것 ? BSR 또는 CNTLZ? 위에서 언급 한 x86-timing.pdf를 읽었을 때 BSR은 Netburst Pentium에서만 느립니다. 나는 PowerPC에 대해 아무것도 모른다.
timday

5
... 좋습니다. 자세히 살펴보면 "BSR은 P3 / Pentium-M / Core2 x86s에서만 빠릅니다"라고합니다. Netburst 및 AMD에서는 느립니다.
timday

1
알림 : 마지막 두 링크가 작동하지 않습니다.
Baum mit Augen

2
@rlbond : 허, P4 Prescott의 BSR은 16주기 지연 (!)으로 2uops이며 4c 처리량 당 하나입니다. 그러나 이전 Netburst에서는 4주기 대기 시간 (여전히 2 uops)이고 2c 처리량 당 1 개입니다. (출처 : agner.org/optimize ). 대부분의 CPU에서는 gcc가 설명하지 않는 출력에 대한 종속성도 있습니다 (입력이 0 일 때 실제 동작은 대상을 변경하지 않은 상태로 두는 것입니다). 이로 인해 stackoverflow.com/questions/25078285/… 와 같은 문제가 발생할 수 있습니다 . gcc가이를 고칠 때 BSR을 놓친 이유.
피터 코르

38

2 ^ N은 N 번째 비트 세트 (1 << N) 만있는 정수이므로 가장 높은 세트 비트의 위치 (N)를 찾는 것은 해당 정수의 정수 로그 밑이 2입니다.

http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious

unsigned int v;
unsigned r = 0;

while (v >>= 1) {
    r++;
}

이 "명백한"알고리즘은 모든 사람에게 투명하지 않을 수 있지만 가장 왼쪽 비트가 제거 될 때까지 코드가 오른쪽으로 1 비트 씩 반복적으로 이동한다는 것을 알게되면 (C는 0이 아닌 값을 모두 true로 처리 함) 숫자를 반환합니다. 교대 근무의 완벽한 의미입니다. 또한 두 개 이상의 비트가 설정된 경우에도 작동 함을 의미합니다. 결과는 항상 최상위 비트에 대한 것입니다.

해당 페이지에서 아래로 스크롤하면 더 빠르고 복잡한 변형이 있습니다. 그러나 선행 0이 많은 숫자를 처리하고 있다는 것을 알고 있다면 C에서 비트 이동이 다소 빠르며 간단한 알고리즘이 배열을 인덱싱 할 필요가 없기 때문에 순진한 접근 방식이 허용 가능한 속도를 제공 할 수 있습니다.

참고 : 64 비트 값을 사용할 때는 매우 영리한 알고리즘 사용에 대해 매우주의해야합니다. 대부분은 32 비트 값에 대해서만 올바르게 작동합니다.


2
@Johan 디버거를 단계별로 실행하면 루프가 종료되는 이유를 설명 할 수 있습니다. 기본적으로 그 '는 마지막 1 비트가 오른쪽으로 이동하면 조건의 표현식이 0 (거짓으로 처리됨)으로 평가되기 때문입니다.
Quinn Taylor

2
최종 결과를 다음과 같이 사용하는 것이 좋습니다. :)
Johan

6
참고 : 부호가 없어야합니다. 부호있는 정수의 경우 음수에 대해서는 오른쪽 시프트가 실패합니다.
Xantix

2
Xantix : C / C ++의 변화는 논리적 변화이므로 잘 작동합니다. Java, JavaScript 또는 D의 경우 논리 시프트 연산자를 사용해야합니다 >>>. 또한 아마도 비교기 != 0및 일부 지정되지 않은 괄호 수입니다.
Chase

8
@ 체이스 : 아니에요. unsigned에 대한 논리적 이동 입니다 . 대한 서명 , 그것은 또는하지 않을 수있다 논리적 인 변화를 할 (그리고 사실, 일반적으로 산술입니다).
Tim Čas 2015

17

이것은 번개처럼 빠릅니다.

int msb(unsigned int v) {
  static const int pos[32] = {0, 1, 28, 2, 29, 14, 24, 3,
    30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
    16, 7, 26, 12, 18, 6, 11, 5, 10, 9};
  v |= v >> 1;
  v |= v >> 2;
  v |= v >> 4;
  v |= v >> 8;
  v |= v >> 16;
  v = (v >> 1) + 1;
  return pos[(v * 0x077CB531UL) >> 27];
}

25
7 비트 시프트, 5 또는 명령어, 다중 및 잠재적 캐시 미스. :) 벤치마킹 했습니까, 아니면 생성 된 어셈블러를 보셨습니까? 그것은 컴파일러가 제거 할 수있는 방법이 많은에 따라 아주 느린 결국.
jalf

5
나는 신입이다. 나는 반대표를 얻지 못합니다. 실제로 작동하는 소스 코드로 유일한 답변을 제공했습니다.
Protagonist

9
"가능한 캐시 미스"는 아마도 조회 테이블에 대한 액세스를 요구하는이 코드 때문일 것입니다. 이 테이블이 호출 될 때 캐시되지 않으면 가져 오는 동안 중단됩니다. 이로 인해 LUT를 사용하지 않는 솔루션보다 최악의 성능이 훨씬 더 나빠질 수 있습니다.
긴장

13
정말 요점이 아닙니다. 필요한 것보다 훨씬 많은 데이터 캐시 (1 개 이상의 캐시 라인, 짝수)와 필요한 것보다 많은 명령 캐시를 사용합니다. 함수를 처음 호출 할 때 피할 수 있었던 캐시 미스 가 발생하고 필요 이상으로 캐시를 오염 시키므로 호출 다른 코드에서 필요 이상으로 더 많은 미스가 발생할 수 있습니다. LUT는 캐시 미스가 비싸기 때문에 문제가되지 않는 경우가 많습니다. 그러나 나는 그것이 "번개가 빠르다"고 주장하기 전에 벤치마킹하고 싶은 것이라고 말했다. 확실히 문제 가 아닙니다 .
jalf

6
테이블에는 32 개의 항목이 있고 모든 값은 <255 (127)이므로 테이블을 unsigned char 유형으로 정의하면 단일 32 바이트 L1 캐시 라인에 맞습니다. 그리고 모든 것이 두 개의 캐시 라인에 맞습니다.
ChuckCottrill 2015 년

16

이것은 일종의 정수 로그를 찾는 것과 같습니다. 조금 뒤틀리는 트릭이 있지만이를위한 도구를 직접 만들었습니다. 물론 목표는 속도입니다.

내 깨달은 것은 CPU에 정수에서 부동으로 변환하는 데 사용되는 자동 비트 탐지기가 이미 있다는 것입니다! 그래서 그것을 사용하십시오.

double ff=(double)(v|1);
return ((*(1+(uint32_t *)&ff))>>20)-1023;  // assumes x86 endianness

이 버전은 값을 double로 캐스트 한 다음 지수를 읽어 비트가 어디에 있는지 알려줍니다. 멋진 변화와 빼기는 IEEE 값에서 적절한 부분을 추출하는 것입니다.

float를 사용하는 것이 약간 빠르지 만 float는 정밀도가 더 작기 때문에 처음 24 비트 위치 만 제공 할 수 있습니다.


이 작업을 안전하게 수행하려면 C ++ 또는 C에서 정의되지 않은 동작없이 memcpy형식 실행에 포인터 캐스팅 대신 사용하십시오 . 컴파일러는이를 효율적으로 인라인하는 방법을 알고 있습니다.

// static_assert(sizeof(double) == 2 * sizeof(uint32_t), "double isn't 8-byte IEEE binary64");
// and also static_assert something about FLT_ENDIAN?

double ff=(double)(v|1);

uint32_t tmp;
memcpy(&tmp, ((const char*)&ff)+sizeof(uint32_t), sizeof(uint32_t));
return (tmp>>20)-1023;

또는 C99 이상에서는 union {double d; uint32_t u[2];};. 그러나 C ++에서 공용체 유형 punning은 ISO C ++가 아닌 일부 컴파일러에서만 확장으로 지원됩니다.


이것은 일반적으로 선행 0 계수 명령에 대한 플랫폼 별 내장 함수보다 느리지 만 이식 가능한 ISO C에는 이러한 기능이 없습니다. 일부 CPU에는 선행 0 계산 명령이 없지만 일부 CPU는 정수를 다음으로 효율적으로 변환 할 수 있습니다.double . 하지만 FP 비트 패턴을 정수로 다시 유형 -punning하는 것은 느릴 수 있지만 (예 : PowerPC에서는 저장 / 다시로드가 필요하며 일반적으로로드 적중-저장 중단이 발생합니다).

이 알고리즘은 더 적은 수의 CPU에 SIMD가 있으므로 SIMD 구현에 잠재적으로 유용 할 수 있습니다 lzcnt. x86 은 AVX512CD 에서만 이러한 명령 을 받았습니다.


2
예. 그리고 gcc는 타입 앨리어싱 최적화로 인해 -O2로 이와 같은 코드로 불쾌한 일을 할 것입니다.
MSN

4
정수와 부동 소수점 사이의 캐스팅은 x86 CPU에서 놀랍도록 비쌀 수 있습니다
jalf

1
네, FPU 비용이 높습니다. 그러나 실제 시간 측정은 이것이 모든 비트 연산 또는 특히 모든 루프보다 빠르다는 것을 보여주었습니다. 그것을 시도하고 가장 빨리 취하는 것이 항상 최고의 조언입니다. 그래도 GCC와 -O2에 문제가 없었습니다.
SPWorley

1
이 정의되지 않은 동작 (호환되지 않는 유형의 포인터를 통해 값 읽기)이 아닌가요?
dreamlax 2011

3
Hacker 's Delight는 5-3 Counting Leading 0 's에서 32 비트 float의 오류를 수정하는 방법을 설명합니다. 다음은 익명 공용체를 사용하여 asFloat와 asInt를 겹치는 코드입니다. k = k & ~ (k >> 1); asFloat = (float) k + 0.5f; n = 158-(asInt >> 23); (그래,이 구현 정의 행동에 의존)
D 코찌

11

여기 Kaz Kylheku

나는 63 비트 숫자 (gcc x86_64의 long long 유형)에 대해 두 가지 접근 방식을 벤치마킹하여 부호 비트에서 멀리 떨어져 있습니다.

(뭔가를 위해 "가장 높은 비트 찾기"가 필요합니다.)

나는 데이터 기반 이진 검색을 구현했습니다 (위 답변 중 하나를 기반으로). 또한 직접 피연산자가있는 코드 인 완전히 펼쳐진 의사 결정 트리를 직접 구현했습니다. 루프도없고 테이블도 없습니다.

의사 결정 트리 (highest_bit_unrolled)는 이진 검색에 명시 적 테스트가있는 n = 0 사례를 제외하고 69 % 더 빠른 것으로 벤치마킹되었습니다.

0 사례에 대한 이진 검색의 특수 테스트는 특수 테스트가없는 의사 결정 트리보다 48 % 빠릅니다.

컴파일러, 기계 : (GCC 4.5.2, -O3, x86-64, 2867 Mhz Intel Core i5).

int highest_bit_unrolled(long long n)
{
  if (n & 0x7FFFFFFF00000000) {
    if (n & 0x7FFF000000000000) {
      if (n & 0x7F00000000000000) {
        if (n & 0x7000000000000000) {
          if (n & 0x4000000000000000)
            return 63;
          else
            return (n & 0x2000000000000000) ? 62 : 61;
        } else {
          if (n & 0x0C00000000000000)
            return (n & 0x0800000000000000) ? 60 : 59;
          else
            return (n & 0x0200000000000000) ? 58 : 57;
        }
      } else {
        if (n & 0x00F0000000000000) {
          if (n & 0x00C0000000000000)
            return (n & 0x0080000000000000) ? 56 : 55;
          else
            return (n & 0x0020000000000000) ? 54 : 53;
        } else {
          if (n & 0x000C000000000000)
            return (n & 0x0008000000000000) ? 52 : 51;
          else
            return (n & 0x0002000000000000) ? 50 : 49;
        }
      }
    } else {
      if (n & 0x0000FF0000000000) {
        if (n & 0x0000F00000000000) {
          if (n & 0x0000C00000000000)
            return (n & 0x0000800000000000) ? 48 : 47;
          else
            return (n & 0x0000200000000000) ? 46 : 45;
        } else {
          if (n & 0x00000C0000000000)
            return (n & 0x0000080000000000) ? 44 : 43;
          else
            return (n & 0x0000020000000000) ? 42 : 41;
        }
      } else {
        if (n & 0x000000F000000000) {
          if (n & 0x000000C000000000)
            return (n & 0x0000008000000000) ? 40 : 39;
          else
            return (n & 0x0000002000000000) ? 38 : 37;
        } else {
          if (n & 0x0000000C00000000)
            return (n & 0x0000000800000000) ? 36 : 35;
          else
            return (n & 0x0000000200000000) ? 34 : 33;
        }
      }
    }
  } else {
    if (n & 0x00000000FFFF0000) {
      if (n & 0x00000000FF000000) {
        if (n & 0x00000000F0000000) {
          if (n & 0x00000000C0000000)
            return (n & 0x0000000080000000) ? 32 : 31;
          else
            return (n & 0x0000000020000000) ? 30 : 29;
        } else {
          if (n & 0x000000000C000000)
            return (n & 0x0000000008000000) ? 28 : 27;
          else
            return (n & 0x0000000002000000) ? 26 : 25;
        }
      } else {
        if (n & 0x0000000000F00000) {
          if (n & 0x0000000000C00000)
            return (n & 0x0000000000800000) ? 24 : 23;
          else
            return (n & 0x0000000000200000) ? 22 : 21;
        } else {
          if (n & 0x00000000000C0000)
            return (n & 0x0000000000080000) ? 20 : 19;
          else
            return (n & 0x0000000000020000) ? 18 : 17;
        }
      }
    } else {
      if (n & 0x000000000000FF00) {
        if (n & 0x000000000000F000) {
          if (n & 0x000000000000C000)
            return (n & 0x0000000000008000) ? 16 : 15;
          else
            return (n & 0x0000000000002000) ? 14 : 13;
        } else {
          if (n & 0x0000000000000C00)
            return (n & 0x0000000000000800) ? 12 : 11;
          else
            return (n & 0x0000000000000200) ? 10 : 9;
        }
      } else {
        if (n & 0x00000000000000F0) {
          if (n & 0x00000000000000C0)
            return (n & 0x0000000000000080) ? 8 : 7;
          else
            return (n & 0x0000000000000020) ? 6 : 5;
        } else {
          if (n & 0x000000000000000C)
            return (n & 0x0000000000000008) ? 4 : 3;
          else
            return (n & 0x0000000000000002) ? 2 : (n ? 1 : 0);
        }
      }
    }
  }
}

int highest_bit(long long n)
{
  const long long mask[] = {
    0x000000007FFFFFFF,
    0x000000000000FFFF,
    0x00000000000000FF,
    0x000000000000000F,
    0x0000000000000003,
    0x0000000000000001
  };
  int hi = 64;
  int lo = 0;
  int i = 0;

  if (n == 0)
    return 0;

  for (i = 0; i < sizeof mask / sizeof mask[0]; i++) {
    int mi = lo + (hi - lo) / 2;

    if ((n >> mi) != 0)
      lo = mi;
    else if ((n & (mask[i] << lo)) != 0)
      hi = mi;
  }

  return lo + 1;
}

빠르고 더러운 테스트 프로그램 :

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

int highest_bit_unrolled(long long n);
int highest_bit(long long n);

main(int argc, char **argv)
{
  long long n = strtoull(argv[1], NULL, 0);
  int b1, b2;
  long i;
  clock_t start = clock(), mid, end;

  for (i = 0; i < 1000000000; i++)
    b1 = highest_bit_unrolled(n);

  mid = clock();

  for (i = 0; i < 1000000000; i++)
    b2 = highest_bit(n);

  end = clock();

  printf("highest bit of 0x%llx/%lld = %d, %d\n", n, n, b1, b2);

  printf("time1 = %d\n", (int) (mid - start));
  printf("time2 = %d\n", (int) (end - mid));
  return 0;
}

-O2 만 사용하면 차이가 커집니다. 의사 결정 트리는 거의 4 배 더 빠릅니다.

또한 순진한 비트 이동 코드에 대해 벤치마킹했습니다.

int highest_bit_shift(long long n)
{
  int i = 0;
  for (; n; n >>= 1, i++)
    ; /* empty */
  return i;
}

이것은 예상대로 작은 숫자에 대해서만 빠릅니다. n == 1에 대해 가장 높은 비트가 1이라고 판단 할 때 벤치 마크가 80 % 이상 빨라졌습니다. 그러나 63 비트 공간에서 무작위로 선택한 숫자의 절반은 63 번째 비트가 설정되어 있습니다!

입력 0x3FFFFFFFFFFFFFFF에서 의사 결정 트리 버전은 1보다 훨씬 빠르며 비트 시프터보다 1120 % 더 빠릅니다 (12.2 배).

또한 GCC 내장 기능에 대해 의사 결정 트리를 벤치마킹하고 동일한 숫자에 대해 반복하지 않고 입력을 혼합하여 시도 할 것입니다. 일부 고정 분기 예측이 진행될 수 있으며 반복시 인위적으로 더 빠르게 만드는 비현실적인 캐싱 시나리오가있을 수 있습니다.


9
나는 이것이 좋지 않다고 말하는 것이 아닙니다. 그러나 여기서 귀하의 테스트 프로그램은 동일한 숫자에 대해서만 테스트합니다. 2-3 번의 반복 후에 분기 예측자를 최종 위치로 설정하고 그 후에 완벽한 분기 예측을 수행합니다. 좋은 점은 완전히 무작위 분포를 사용하면 숫자의 절반이 완벽한 예측, 즉 bit63에 가깝다는 것입니다.
Surt


6
unsigned int
msb32(register unsigned int x)
{
        x |= (x >> 1);
        x |= (x >> 2);
        x |= (x >> 4);
        x |= (x >> 8);
        x |= (x >> 16);
        return(x & ~(x >> 1));
}

레지스터 1 개, 명령어 13 개. 믿거 나 말거나 이것은 일반적으로 위에서 언급 한 BSR 명령어보다 빠르며 선형 시간으로 작동합니다. 이것은 로그 시간입니다.

에서 http://aggregate.org/MAGIC/#Most%20Significant%201%20Bit


7
위의 코드는 질문에 답하지 않습니다. x의 최상위 비트는 계속 켜져 있고 다른 모든 비트는 꺼진 부호없는 정수를 반환합니다. 문제는 비트에서 가장 중요한 위치 를 반환하는 것이 었습니다 .
Protagonist

3
그런 다음 De Bruijn 시퀀스 접근 방식을 사용하여 설정된 비트의 인덱스를 찾을 수 있습니다. :-)
R .. GitHub STOP HELPING ICE

5
@ 주인공, 그는 어느 쪽이든 충분하다고 코멘트에서 말했다.
rlbond

이것은 (동일한 페이지에서) 필요한 작업을 수행하지만 추가 기능이 필요합니다. aggregate.org/MAGIC/#Log2%20of%20an%20Integer
Quinn Taylor

1
BSR은 적어도 Core2 이후 Intel CPU에서 빠릅니다. LZCNT는 AMD CPU에서 빠르며 gcc는이를 지원하는 모든 CPU에서 빠르기 때문에 또는 무언가로 __builtin_clz활성화 된 경우이를 사용합니다 -march=native. BSR이 "느린"AMD Bulldozer 제품군과 같은 CPU에서도 그렇게 느리지는 않습니다. 7m-ops (4주기 지연 시간 및 4c 처리량 당 1 개). Atom에서 BSR은 16 주기로 매우 느립니다. Silvermont에서는 10주기 대기 시간으로 10 uop입니다. 이것은 Silvermont의 BSR보다 약간 낮은 지연 시간 일 수 있지만 IDK입니다.
Peter Cordes

6

다음은 현재이 페이지에 제공된 알고리즘의 몇 가지 (간단한) 벤치 마크입니다 ...

알고리즘은 unsigned int의 모든 입력에 대해 테스트되지 않았습니다. 그래서 맹목적으로 무언가를 사용하기 전에 먼저 확인하십시오.)

내 컴퓨터에서 clz (__builtin_clz) 및 asm이 가장 잘 작동합니다. asm은 clz보다 더 빠르지 만 ... 간단한 벤치 마크 때문일 수 있습니다 ...

//////// go.c ///////////////////////////////
// compile with:  gcc go.c -o go -lm
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

/***************** math ********************/

#define POS_OF_HIGHESTBITmath(a) /* 0th position is the Least-Signif-Bit */    \
  ((unsigned) log2(a))         /* thus: do not use if a <= 0 */  

#define NUM_OF_HIGHESTBITmath(a) ((a)               \
                  ? (1U << POS_OF_HIGHESTBITmath(a))    \
                  : 0)



/***************** clz ********************/

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITclz(a))  \
                 : 0)


/***************** i2f ********************/

double FF;
#define POS_OF_HIGHESTBITi2f(a) (FF = (double)(ui|1), ((*(1+(unsigned*)&FF))>>20)-1023)


#define NUM_OF_HIGHESTBITi2f(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITi2f(a))  \
                 : 0)




/***************** asm ********************/

unsigned OUT;
#define POS_OF_HIGHESTBITasm(a) (({asm("bsrl %1,%0" : "=r"(OUT) : "r"(a));}), OUT)

#define NUM_OF_HIGHESTBITasm(a) ((a)                    \
                 ? (1U << POS_OF_HIGHESTBITasm(a))  \
                 : 0)




/***************** bitshift1 ********************/

#define NUM_OF_HIGHESTBITbitshift1(a) (({   \
  OUT = a;                  \
  OUT |= (OUT >> 1);                \
  OUT |= (OUT >> 2);                \
  OUT |= (OUT >> 4);                \
  OUT |= (OUT >> 8);                \
  OUT |= (OUT >> 16);               \
      }), (OUT & ~(OUT >> 1)))          \



/***************** bitshift2 ********************/
int POS[32] = {0, 1, 28, 2, 29, 14, 24, 3,
             30, 22, 20, 15, 25, 17, 4, 8, 31, 27, 13, 23, 21, 19,
             16, 7, 26, 12, 18, 6, 11, 5, 10, 9};

#define POS_OF_HIGHESTBITbitshift2(a) (({   \
  OUT = a;                  \
  OUT |= OUT >> 1;              \
  OUT |= OUT >> 2;              \
  OUT |= OUT >> 4;              \
  OUT |= OUT >> 8;              \
  OUT |= OUT >> 16;             \
  OUT = (OUT >> 1) + 1;             \
      }), POS[(OUT * 0x077CB531UL) >> 27])

#define NUM_OF_HIGHESTBITbitshift2(a) ((a)              \
                       ? (1U << POS_OF_HIGHESTBITbitshift2(a)) \
                       : 0)



#define LOOPS 100000000U

int main()
{
  time_t start, end;
  unsigned ui;
  unsigned n;

  /********* Checking the first few unsigned values (you'll need to check all if you want to use an algorithm here) **************/
  printf("math\n");
  for (ui = 0U; ui < 18; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITmath(ui));

  printf("\n\n");

  printf("clz\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  printf("\n\n");

  printf("i2f\n");
  for (ui = 0U; ui < 18U; ++ui)
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITi2f(ui));

  printf("\n\n");

  printf("asm\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITasm(ui));
  }

  printf("\n\n");

  printf("bitshift1\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift1(ui));
  }

  printf("\n\n");

  printf("bitshift2\n");
  for (ui = 0U; ui < 18U; ++ui) {
    printf("%i\t%i\n", ui, NUM_OF_HIGHESTBITbitshift2(ui));
  }

  printf("\n\nPlease wait...\n\n");


  /************************* Simple clock() benchmark ******************/
  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITmath(ui);
  end = clock();
  printf("math:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITclz(ui);
  end = clock();
  printf("clz:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITi2f(ui);
  end = clock();
  printf("i2f:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITasm(ui);
  end = clock();
  printf("asm:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift1(ui);
  end = clock();
  printf("bitshift1:\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  start = clock();
  for (ui = 0; ui < LOOPS; ++ui)
    n = NUM_OF_HIGHESTBITbitshift2(ui);
  end = clock();
  printf("bitshift2\t%e\n", (double)(end-start)/CLOCKS_PER_SEC);

  printf("\nThe lower, the better. Take note that a negative exponent is good! ;)\n");

  return EXIT_SUCCESS;
}

6

가능한 최고의 성능이 절대적으로 필요한 경우에만이 방법을 사용하지만 (예 : 비트 보드와 관련된 일종의 보드 게임 AI를 작성하는 경우) 가장 효율적인 솔루션은 인라인 ASM을 사용하는 것입니다. 설명이있는 코드 는 이 블로그 게시물 의 최적화 섹션을 참조하십시오 .

[...], bsrl어셈블리 명령어는 최상위 비트의 위치를 ​​계산합니다. 따라서 다음 asm문을 사용할 수 있습니다 .

asm ("bsrl %1, %0" 
     : "=r" (position) 
     : "r" (number));

확장하려면 표준 루프 솔루션 (왼쪽으로 이동하고 MSB 확인)이 아마도 가장 읽기 쉽습니다. 비트 트위들 링과 관련된 모든 경우와 마찬가지로, 필요하지 않는 한 코드를 복잡하게 만들 필요는 없지만 ASM의 속도는 이길 수 없습니다. 해킹은 중간 솔루션입니다.
Noldorin

로그를 취하는 것이 완벽하게 읽을 수있는 솔루션이라고 말하고 싶습니다 (컴파일러가이 asm 명령어를 사용하도록 최적화 할 수 있는지 확인하려면 생성 된 asm을 확인하십시오)
jalf

때때로 인라인 ASM 솔루션은 CPU 마이크로 코드의 구현에 따라 더 느립니다.
rlbond

5
@rlbound : 착각 할 수는 있지만 믿을 수는 없습니다. 현대의 CPU에 하나 ....이 단일 명령어로 변환 얻을 것이라고 생각
Noldorin

3
@Noldorin 조금 늦었지만 .. 정의상 단일 명령어이지만 rlbond가 제안한 것처럼 마이크로 코딩 된 경우 단일 명령어가 내부적으로 전체 µops로 디코딩 할 수 있습니다. 이는 AMD의 마이크로 아키텍처와 인텔 아톰의 경우에 해당하는 경향이 있지만 일반적인 인텔 마이크로 아키텍처에서는 끝까지 단일 작업입니다.
harold

4

이 작업을 수행하기위한 루틴이 필요했고 웹을 검색하고이 페이지를 찾기 전에 이진 검색을 기반으로하는 자체 솔루션을 생각해 냈습니다. 누군가가 전에이 일을했다고 확신하지만! 그것은 일정한 시간에 실행되며 게시 된 "명백한"솔루션보다 빠를 수 있습니다. 비록 내가 큰 주장을하지 않고 단지 관심을 위해 게시하는 것입니다.

int highest_bit(unsigned int a) {
  static const unsigned int maskv[] = { 0xffff, 0xff, 0xf, 0x3, 0x1 };
  const unsigned int *mask = maskv;
  int l, h;

  if (a == 0) return -1;

  l = 0;
  h = 32;

  do {
    int m = l + (h - l) / 2;

    if ((a >> m) != 0) l = m;
    else if ((a & (*mask << l)) != 0) h = m;

    mask++;
  } while (l < h - 1);

  return l;
}

4

그것은 일종의 이진 검색입니다. 모든 종류의 (부호없는!) 정수 유형에서 작동합니다.

#include <climits>
#define UINT (unsigned int)
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int msb(UINT x)
{
    if(0 == x)
        return -1;

    int c = 0;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x >> i))
    {
        x >>= i;
        c |= i;
    }

    return c;
}

완료하려면 :

#include <climits>
#define UINT unsigned int
#define UINT_BIT (CHAR_BIT*sizeof(UINT))

int lsb(UINT x)
{
    if(0 == x)
        return -1;

    int c = UINT_BIT-1;

    for(UINT i=UINT_BIT>>1; 0<i; i>>=1)
    if(static_cast<UINT>(x << i))
    {
        x <<= i;
        c ^= i;
    }

    return c;
}

4
typedefs 또는 실제로 전 처리기 매크로를 제외한 모든 것에 대해 ALL_CAPS를 사용하지 않는 것을 고려하십시오 . 이것은 널리 통용되는 규칙입니다.
underscore_d

4

여기에 지나치게 복잡한 답변이 있습니다. Debruin 기술은 입력이 이미 2의 거듭 제곱 일 때만 사용해야합니다. 그렇지 않으면 더 좋은 방법이 있습니다. 2 입력의 거듭 제곱에 대해 Debruin은 _BitScanReverse내가 테스트 한 어떤 프로세서 보다 훨씬 빠르고 절대적으로 빠릅니다 . 그러나 일반적인 경우 _BitScanReverse(또는 컴파일러에서 내장 함수가 호출되는 경우)가 가장 빠릅니다 (특정 CPU에서는 마이크로 코딩 될 수 있음).

내장 함수가 옵션이 아닌 경우 일반 입력 처리를위한 최적의 소프트웨어 솔루션이 있습니다.

u8  inline log2 (u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFu) { val >>= 16; k  = 16; }
    if (val > 0x000000FFu) { val >>= 8;  k |= 8;  }
    if (val > 0x0000000Fu) { val >>= 4;  k |= 4;  }
    if (val > 0x00000003u) { val >>= 2;  k |= 2;  }
    k |= (val & 2) >> 1;
    return k;
}

이 버전은 대부분의 다른 답변과 달리 끝에 Debruin 조회가 필요하지 않습니다. 제자리에서 위치를 계산합니다.

테이블이 더 바람직 할 수 있지만, 충분히 반복해서 호출하면 테이블 속도 향상으로 인해 캐시 미스 위험이 가려집니다.

u8 kTableLog2[256] = {
0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
};

u8 log2_table(u32 val)  {
    u8  k = 0;
    if (val > 0x0000FFFFuL) { val >>= 16; k  = 16; }
    if (val > 0x000000FFuL) { val >>=  8; k |=  8; }
    k |= kTableLog2[val]; // precompute the Log2 of the low byte

    return k;
}

이것은 여기에 제공된 소프트웨어 답변 중 가장 높은 처리량을 생성하지만 가끔 호출하는 경우 첫 번째 스 니펫과 같은 테이블없는 솔루션을 선호합니다.


1
일부 답변은 분기가 없지만 이것은 아마도 조건부 분기로 컴파일됩니다. 동일한 값으로 만 벤치마킹을 반복 했습니까, 아니면 단순한 패턴으로 만 벤치마킹 했습니까? 분기 예측 오류는 성능에 대한 킬러입니다. stackoverflow.com/questions/11227809/…
Peter Cordes

3

위의 답변에서 지적했듯이 최상위 비트를 결정하는 방법에는 여러 가지가 있습니다. 그러나 또한 지적했듯이 메서드는 32 비트 또는 64 비트 레지스터에 고유 할 수 있습니다. stanford.edu의 bithacks 페이지는 32 비트 및 64 비트 모두를위한 일을 계산하는 것이 솔루션을 제공합니다. 약간의 작업으로 이들을 결합하여 MSB를 얻기위한 견고한 교차 아키텍처 접근 방식을 제공 할 수 있습니다. 64 비트 및 32 비트 컴퓨터에서 컴파일 / 작업 한 솔루션은 다음과 같습니다.

#if defined(__LP64__) || defined(_LP64)
# define BUILD_64   1
#endif

#include <stdio.h>
#include <stdint.h>  /* for uint32_t */

/* CHAR_BIT  (or include limits.h) */
#ifndef CHAR_BIT
#define CHAR_BIT  8
#endif  /* CHAR_BIT */

/* 
 * Find the log base 2 of an integer with the MSB N set in O(N)
 * operations. (on 64bit & 32bit architectures)
 */
int
getmsb (uint32_t word)
{
    int r = 0;
    if (word < 1)
        return 0;
#ifdef BUILD_64
    union { uint32_t u[2]; double d; } t;  // temp
    t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] = 0x43300000;
    t.u[__FLOAT_WORD_ORDER!=LITTLE_ENDIAN] = word;
    t.d -= 4503599627370496.0;
    r = (t.u[__FLOAT_WORD_ORDER==LITTLE_ENDIAN] >> 20) - 0x3FF;
#else
    while (word >>= 1)
    {
        r++;
    }
#endif  /* BUILD_64 */
    return r;
}

int r이 아니 었습니다. 원래 #ifdef BUILD_64플래그 위에 정의 되었습니까? 어떤 경우에는 조건부 내에서 재정의 할 필요가 없습니다.
David C. Rankin

3

연속 근사를 사용하는 C 버전 :

unsigned int getMsb(unsigned int n)
{
  unsigned int msb  = sizeof(n) * 4;
  unsigned int step = msb;
  while (step > 1)
 {
    step /=2;
    if (n>>msb)
     msb += step;
   else
     msb -= step;
 }
  if (n>>msb)
    msb++;
  return (msb - 1);
}

장점 : 루프 수가 항상 같기 때문에 제공된 수에 관계없이 실행 시간이 일정합니다. ( "unsigned int"사용시 4 개의 루프)


삼항 연산자 ( msb += (n>>msb) ? step : -step;)로 작성하면 더 많은 컴파일러가 분기없는 asm을 만들어 모든 단계에서 분기 오류 예측을 방지 할 수 있습니다 ( stackoverflow.com/questions/11227809/… ).
Peter Cordes 2018 년

3

이 질문이 매우 오래 되었다는 것을 알고 있지만 msb () 함수를 직접 구현 했기 때문에 여기와 다른 웹 사이트에 제시된 대부분의 솔루션이 반드시 가장 효율적인 것은 아니라는 것을 알았습니다. 적어도 개인적으로 효율성을 정의하려면 아래 업데이트를 참조하십시오. ). 그 이유는 다음과 같습니다.

대부분의 솔루션 (특히 이진 검색 체계 또는 오른쪽에서 왼쪽으로 선형 스캔을 수행하는 순진한 접근 방식을 사용하는 솔루션)은 임의의 이진수에 대해 매우 긴 시퀀스로 시작하는 것이 많지 않다는 사실을 무시하는 것 같습니다. 0. 실제로 모든 비트 너비에 대해 모든 정수의 절반은 1로 시작 하고 1/4은 01로 시작 합니다. 내가 어디로 가는지 알아? 내 주장은 최상위 비트 위치에서 최하위 (왼쪽에서 오른쪽)까지의 선형 스캔 이 언뜻보기에 그렇게 "선형"이 아니라는 것입니다.

모든 비트 폭에 대해 테스트해야하는 평균 비트 수가 최대 2임을 1 로 표시 할 수 있습니다 . 이는 비트 수 (!)에 대해 O (1)상각 된 시간 복잡도 를 의미합니다. .

물론 최악의 경우는 여전히 O (n)입니다 .바이너리 검색과 같은 접근 방식으로 얻는 O (log (n))나쁘지만 최악의 경우가 거의 없기 때문에 대부분의 응용 프로그램에서 무시할 수 있습니다 ( 업데이트 : 정답이 아닙니다 : 거의 없을 수 있지만 높은 확률로 발생할 수 있습니다. 아래 업데이트 참조).

여기 내가 생각 해낸 "순진한"접근 방식이 있는데, 적어도 내 컴퓨터에서 대부분의 다른 접근 방식을 능가합니다 (32 비트 정수에 대한 이진 검색 체계는 항상 log 2 (32) = 5 단계가 필요하지만이 어리석은 알고리즘은 더 적은 평균 2 개 이상)-이것이 C ++이고 순수한 C가 아닌 것에 대해 죄송합니다.

template <typename T>
auto msb(T n) -> int
{
    static_assert(std::is_integral<T>::value && !std::is_signed<T>::value,
        "msb<T>(): T must be an unsigned integral type.");

    for (T i = std::numeric_limits<T>::digits - 1, mask = 1 << i; i >= 0; --i, mask >>= 1)
    {
        if ((n & mask) != 0)
            return i;
    }

    return 0;
}

업데이트 : 내가 여기에 쓴 내용은모든 비트 조합이 똑같이 가능한 임의의 정수에대해 완벽하게 사실이지만(내 속도 테스트는 단순히 모든 32 비트 정수에대한 MSB를 결정하는 데 걸린 시간을 측정했습니다), 실제 정수, 이러한 함수가 호출되는 것은 일반적으로 다른 패턴을 따릅니다. 예를 들어이 함수는 객체 크기 가 2의 거듭 제곱인지 여부를 확인하거나 다음 2의 거듭 제곱을 찾는데 사용됩니다. 개체 크기 . 내 생각에 MSB를 사용하는 대부분의 응용 프로그램에는 정수가 나타낼 수있는 최대 수보다 훨씬 작은 숫자가 포함되어 있습니다 (객체 크기는 size_t의). 이 경우 내 솔루션은 실제로 이진 검색 접근 방식보다 성능이 떨어질 것입니다. 따라서 내 솔루션이 모든 정수를 더 빠르게 반복하더라도 후자가 선호 될 것 입니다.
요약 : 실제 정수는 아마도이 간단한 알고리즘의 최악의 경우에 편향을 가질 것입니다. 이것은 진정한 임의의 정수에 대해 O (1)상각 한다는 사실에도 불구하고 결국 성능을 저하시킬 것입니다 .

1 인수는 다음과 같습니다 (대략 초안) : n 을 비트 수 (비트 너비)라고합니다. 한 곳 (2 개) N 으로 표현 될 수있다 느릅 정수 n 개의 비트. 거기 2 , n은 1 - 로 시작하는 정수 1 (제 1 나머지 고정되고, 1 - N 무엇이든 될 수 비트). 이러한 정수는 MSB를 결정하기 위해 루프의 상호 작용이 하나만 필요합니다. 또한 01로 시작하는 2 n-2 정수 , 2 반복, 001로 시작하는 2 n-3 정수 , 3 반복 등이 있습니다.

우리는 모든 가능한 정수에 필요한 모든 반복을 요약하고별로 나누면 2 N 하고 총 정수 개수 인 n 비트 정수에 대한 MSB를 결정하는 데 필요한 평균 반복 횟수를 얻습니다 .

(1 * 2n-1 + 2 * 2n-2 + 3 * 2n-3 + ... + n) / 2n

이 일련의 평균 반복은 실제로 수렴하며 무한대를 향한 n에 대해 2로 제한됩니다.

따라서 순진한 왼쪽에서 오른쪽 알고리즘은 실제로 임의의 비트 수에 대해 O (1)상각 된 상수 시간 복잡도를 갖습니다 .


2
msb 함수에 대한 입력이 균등하게 분산되는 경향이 있다는 것이 반드시 공정한 가정이라고 생각하지 않습니다. 실제로 이러한 입력은 인터럽트 레지스터 나 비트 보드 또는 값이 고르지 않게 분산 된 다른 데이터 구조 인 경향이 있습니다. 공정한 벤치 마크를 위해 출력 (입력이 아닌)이 균등하게 분배 될 것이라고 가정하는 것이 더 안전하다고 생각합니다.
johnwbyrd

3

우리에게주었습니다 log2. 이렇게하면 log2이 페이지에서 볼 수있는 모든 특수 소스 구현이 필요하지 않습니다 . 다음 log2과 같이 표준 구현을 사용할 수 있습니다 .

const auto n = 13UL;
const auto Index = (unsigned long)log2(n);

printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

n는 다음 0UL과 같은 이유로 보호해야합니다.

-∞가 반환되고 FE_DIVBYZERO가 발생합니다.

나는 임의 세트가 있다는 것을 확인과 예를 쓴 Index위해 ULONG_MAX여기서는 https://ideone.com/u26vsi


그만큼 ephemient의 gcc 유일한 대답에 대한 추론 은 다음과 같습니다.

const auto n = 13UL;
unsigned long Index;

_BitScanReverse(&Index, n);
printf("MSB is: %u\n", Index); // Prints 3 (zero offset)

_BitScanReverse상태에 대한 문서Index :

발견 된 첫 번째 세트 비트 (1)의 비트 위치로로드 됨

실제로 나는 경우 것으로 나타났습니다 n이다 0UL그가 Index설정되어0UL 그것이이 될 것과 마찬가지로, n1UL. 그러나의 경우에는 문서에 보장 된 유일한 n의는 0UL반환이 있다는 것입니다 :

설정된 비트가없는 경우 0

따라서 log2위 의 바람직한 구현 과 유사 Index하게이 경우 플래그가 지정된 값으로 설정 을 확인해야합니다 . ULONG_MAX여기에이 플래그 값 을 사용하는 예제를 다시 작성했습니다 . http://rextester.com/GCU61409


아니요, 입력이 인 경우 에만_BitScanReverse 0을 반환합니다 . 이것은 출력이 아닌 입력만을 기반으로 ZF를 설정하는 x86의 명령어 와 같습니다 . MS가 문서를 비트를 찾을 수 없을 때 설정되지 않은 채로 남겨둔다는 점이 흥미 롭습니다 . 의 x86 asm 동작과도 일치합니다 . (AMD는 대상 레지스터를 src = 0에서 수정되지 않은 상태로 두는 것으로 문서화하지만 Intel은 CPU가 수정되지 않은 상태로 두는 동작을 구현하더라도 정의되지 않은 출력을 말합니다.) 이것은 찾을 수 없음 을 제공하는 x86과는 다릅니다 . 0BSRindex1bsrlzcnt32
Peter Cordes

@PeterCordes _BitScanReverse는 0부터 시작하는 인덱싱을 사용하므로 n1이면 설정된 비트의 인덱스는 실제로 0입니다. 불행히도 n0이면 출력도 0입니다 :( 이것은 반환 값을 사용할 방법이 없음을 의미합니다. 구분 n1 또는 내가 노력했다의 의사 소통 것을 0이 말할 수있는 더 좋은 방법이 있다고 생각하십니까.?
조나단 미는

나는 그것이 어떻게 설정되는지에 대해 이야기하고 있다고 생각합니다 Index. 그것은 반환 값 이 아닙니다 . 입력이 0이면 false 인 부울을 반환합니다 (이것이 인덱스가 정상적으로 반환되는 대신 참조로 전달되는 이유입니다). godbolt.org/g/gQKJdE . 그리고 확인했습니다. MS 문서의 문구에도 불구하고 _BitScanReverseIndex를 설정하지 않은 상태로 두지 n==0마십시오. 사용하는 레지스터에있는 값만 얻습니다. (귀하의 경우에는 Index나중에 사용했던 것과 동일한 레지스터 였으므로 0)이 표시됩니다.
Peter Cordes

이 질문에는 C ++ 태그가 없습니다.
technosaurus

@technosaurus 감사합니다, 나 자신을 잊었습니다. 질문이 C라는 점을 감안할 때 실제로 log2C99 이후로 가졌습니다 .
Jonathan Mee

2

비트 연산자를 생각해보십시오.

나는 처음으로 질문을 이해하지 못했습니다. 가장 왼쪽 비트 세트 (다른 ​​것은 0)로 int를 생성해야합니다. cmp가 해당 값으로 설정되어 있다고 가정합니다.

position = sizeof(int)*8
while(!(n & cmp)){ 
   n <<=1;
   position--;
}

문자열로 변환한다는 것은 무엇을 의미합니까? ffs의 정의는 int를 취하고 int를 반환합니다. 전환은 어디에 있습니까? 그리고 우리가 단어의 일부를 찾고 있다면 변환은 어떤 목적으로 사용 될까요?
dreamlax

나는 그 기능을 몰랐다.
Vasil

8있어야한다 CHAR_BIT. 동일한 입력에 반복적으로 사용되지 않는 한 루프를 종료 할 때 분기 예측 오류가 발생하기 때문에 이것이 가장 빠른 방법은 아닐 것입니다. 또한 작은 입력 (많은 0)의 경우 많이 반복해야합니다. 이는 최적화 된 버전과 비교하기 위해 단위 테스트에서 확인하기 쉬운 버전으로 사용하는 대체 방법과 같습니다.
Peter Cordes 2018 년

2

Josh의 벤치 마크를 확장하면 다음과 같이 clz를 개선 할 수 있습니다.

/***************** clz2 ********************/

#define NUM_OF_HIGHESTBITclz2(a) ((a)                              \
                  ? (((1U) << (sizeof(unsigned)*8-1)) >> __builtin_clz(a)) \
                  : 0)

asm과 관련하여 : bsr 및 bsrl ( "long"버전)이 있습니다. 정상적인 것이 조금 더 빠를 수 있습니다.


1

당신이하려는 것은 정수의 정수 log2를 계산하는 것입니다.

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

unsigned int
Log2(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1; int k=0;
    for( step = 1; step < bits; ) {
        n |= (n >> step);
        step *= 2; ++k;
    }
    //printf("%ld %ld\n",x, (x - (n >> 1)) );
    return(x - (n >> 1));
}

한 번에 1 비트 이상 검색을 시도 할 수 있습니다.

unsigned int
Log2_a(unsigned long x)
{
    unsigned long n = x;
    int bits = sizeof(x)*8;
    int step = 1;
    int step2 = 0;
    //observe that you can move 8 bits at a time, and there is a pattern...
    //if( x>1<<step2+8 ) { step2+=8;
        //if( x>1<<step2+8 ) { step2+=8;
            //if( x>1<<step2+8 ) { step2+=8;
            //}
        //}
    //}
    for( step2=0; x>1L<<step2+8; ) {
        step2+=8;
    }
    //printf("step2 %d\n",step2);
    for( step = 0; x>1L<<(step+step2); ) {
        step+=1;
        //printf("step %d\n",step+step2);
    }
    printf("log2(%ld) %d\n",x,step+step2);
    return(step+step2);
}

이 접근 방식은 이진 검색을 사용합니다.

unsigned int
Log2_b(unsigned long x)
{
    unsigned long n = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int hbit = bits-1;
    unsigned int lbit = 0;
    unsigned long guess = bits/2;
    int found = 0;

    while ( hbit-lbit>1 ) {
        //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        //when value between guess..lbit
        if( (x<=(1L<<guess)) ) {
           //printf("%ld < 1<<%d %ld\n",x,guess,1L<<guess);
            hbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
        //when value between hbit..guess
        //else
        if( (x>(1L<<guess)) ) {
            //printf("%ld > 1<<%d %ld\n",x,guess,1L<<guess);
            lbit=guess;
            guess=(hbit+lbit)/2;
            //printf("log2(%ld) %d<%d<%d\n",x,lbit,guess,hbit);
        }
    }
    if( (x>(1L<<guess)) ) ++guess;
    printf("log2(x%ld)=r%d\n",x,guess);
    return(guess);
}

더 읽기 쉬운 또 다른 이진 검색 방법은

unsigned int
Log2_c(unsigned long x)
{
    unsigned long v = x;
    unsigned int bits = sizeof(x)*8;
    unsigned int step = bits;
    unsigned int res = 0;
    for( step = bits/2; step>0; )
    {
        //printf("log2(%ld) v %d >> step %d = %ld\n",x,v,step,v>>step);
        while ( v>>step ) {
            v>>=step;
            res+=step;
            //printf("log2(%ld) step %d res %d v>>step %ld\n",x,step,res,v);
        }
        step /= 2;
    }
    if( (x>(1L<<res)) ) ++res;
    printf("log2(x%ld)=r%ld\n",x,res);
    return(res);
}

그리고 이것을 테스트하고 싶기 때문에

int main()
{
    unsigned long int x = 3;
    for( x=2; x<1000000000; x*=2 ) {
        //printf("x %ld, x+1 %ld, log2(x+1) %d\n",x,x+1,Log2(x+1));
        printf("x %ld, x+1 %ld, log2_a(x+1) %d\n",x,x+1,Log2_a(x+1));
        printf("x %ld, x+1 %ld, log2_b(x+1) %d\n",x,x+1,Log2_b(x+1));
        printf("x %ld, x+1 %ld, log2_c(x+1) %d\n",x,x+1,Log2_c(x+1));
    }
    return(0);
}

1

'아직 다른'접근 방식이기 때문에 이것을 넣는 것은 이미 주어진 다른 것과 다른 것 같습니다.

-1if 반환 x==0, 그렇지 않으면 floor( log2(x)) (최대 결과 31)

32에서 4 비트 문제로 줄인 다음 테이블을 사용하십시오. 우아하지 않지만 실용적입니다.

이것은 __builtin_clz이식성 문제로 인해 사용하고 싶지 않을 때 사용하는 것 입니다.

더 간결하게 만들기 위해 대신 루프를 사용하여 줄이면서 매번 최대 7 회 반복하여 4를 r에 추가 할 수 있습니다. 또는 (64 비트 용)과 같은 일부 하이브리드 : 루프를 8로 줄이고 테스트하여 4로 줄입니다.

int log2floor( unsigned x ){
   static const signed char wtab[16] = {-1,0,1,1, 2,2,2,2, 3,3,3,3,3,3,3,3};
   int r = 0;
   unsigned xk = x >> 16;
   if( xk != 0 ){
       r = 16;
       x = xk;
   }
   // x is 0 .. 0xFFFF
   xk = x >> 8;
   if( xk != 0){
       r += 8;
       x = xk;
   }
   // x is 0 .. 0xFF
   xk = x >> 4;
   if( xk != 0){
       r += 4;
       x = xk;
   }
   // now x is 0..15; x=0 only if originally zero.
   return r + wtab[x];
}

1

와우, 많은 답변이었습니다. 오래된 질문에 답 해주셔서 죄송합니다.

int result = 0;//could be a char or int8_t instead
if(value){//this assumes the value is 64bit
    if(0xFFFFFFFF00000000&value){  value>>=(1<<5); result|=(1<<5);  }//if it is 32bit then remove this line
    if(0x00000000FFFF0000&value){  value>>=(1<<4); result|=(1<<4);  }//and remove the 32msb
    if(0x000000000000FF00&value){  value>>=(1<<3); result|=(1<<3);  }
    if(0x00000000000000F0&value){  value>>=(1<<2); result|=(1<<2);  }
    if(0x000000000000000C&value){  value>>=(1<<1); result|=(1<<1);  }
    if(0x0000000000000002&value){  result|=(1<<0);  }
}else{
  result=-1;
}

이 답변은 다른 답변과 매우 유사합니다.


시프트 금액을 그대로 쓰는 1<<k것이 좋습니다. 마스크는 어떻습니까? (1 << (1<<k-1)-1<< (1<<k-1)? ( most optimal? 당신은 최상급 비교?)
수염이 희끗 희끗 한

@greybeard이 질문의 편집을 보면 "최적"부분을 추가했을 때 알 수 있습니다. 내 대답을 변경하면서 제거하는 것을 잊었습니다. 또한 왜 당신이 마스크 에 대해 말하는지 잘 모르겠 습니까? (? 무엇 마스크 난 당신을 다음 아니에요)
해리 스벤손

( (비트) 마스크&및 에서 사용 / 선택적으로 비트를 선택 / 삭제하는 데 사용되는 값 &~입니다.) 16 진수 상수를 ((type)1<<(1<<k))-1<<(1<<k).
greybeard

아 맞다, 마스크를 쓰고 있는데 완전히 잊었다. 몇 달 전에 대답했습니다 ...-음, 컴파일 시간 동안 평가 되었기 때문에 16 진수 값 과 동일 하다고 말합니다 . 그러나 하나는 암호이고 다른 하나는 16 진수입니다.
Harry Svensson

0

코드:

    // x>=1;
    unsigned func(unsigned x) {
    double d = x ;
    int p= (*reinterpret_cast<long long*>(&d) >> 52) - 1023;
    printf( "The left-most non zero bit of %d is bit %d\n", x, p);
    }

또는 Y = 1로 설정하여 FPU 명령 FYL2X (Y * Log2 X)의 정수 부분을 가져옵니다.


어쩐지. 뭐? 이 기능은 어떻게 작동합니까? 어떤 식 으로든 휴대 가능합니까?
underscore_d

창의 코드는 이식 가능합니다. FYL2X () 함수는 fpu 명령어이지만 이식 될 수 있으며 일부 FPU / 수학 라이브러리에서 찾을 수 있습니다.
jemin

@underscore_d 부동 소수점 숫자가 정규화 되었기 때문에 작동합니다. ... 가수 비트를 이중 시프트로 변환하여 선행 0을 제거하고이 코드는 지수를 추출하고이를 조정하여 시프트 된 비트 수를 결정합니다. 확실히 아키텍처와 무관 한 것은 아니지만 아마 당신이 만나는 모든 컴퓨터에서 작동 할 것입니다.
Jim Balter 2016

이것은 이 답변 의 대체 버전입니다 . 성능 및 이식성에 대한 의견은 여기를 참조하십시오. (특히 type-punning을위한 포인터 캐스팅의 비 이식성.) 주소 연산을 사용하여.의 상위 32 비트 만 다시로드합니다 double. 이는 다른 방법으로 type-pun 대신 실제로 저장 / 다시로드하는 경우 유용 할 것입니다. 로모그래퍼 movq명령 당신은 86 여기 얻을 수 있습니다처럼.
피터 코 데스

또한 내 [답변에 대한 의견]을 참고하십시오. 이 방법이 (적어도) 범위의 값에 대해 잘못된 대답을 제공한다는 경고[7FFFFFFFFFFFFE00 - 7FFFFFFFFFFFFFFF] .
Glenn Slayden

0

또 다른 포스터 는 바이트 전체 조회를 사용 하는 조회 테이블 을 제공했습니다 . 256 개의 조회 항목이 아닌 32K의 메모리 비용으로 성능을 조금 더 높이려면 여기 에 .NETC # 7 에서 15 비트 조회 테이블을 사용하는 솔루션이 있습니다.

흥미로운 부분은 테이블을 초기화하는 것입니다. 프로세스의 수명 동안 우리가 원하는 비교적 작은 블록이기 때문에 Marshal.AllocHGlobal. 보시다시피 최대 성능을 위해 전체 예제가 네이티브로 작성되었습니다.

readonly static byte[] msb_tab_15;

// Initialize a table of 32768 bytes with the bit position (counting from LSB=0)
// of the highest 'set' (non-zero) bit of its corresponding 16-bit index value.
// The table is compressed by half, so use (value >> 1) for indexing.
static MyStaticInit()
{
    var p = new byte[0x8000];

    for (byte n = 0; n < 16; n++)
        for (int c = (1 << n) >> 1, i = 0; i < c; i++)
            p[c + i] = n;

    msb_tab_15 = p;
}

이 테이블은 위 코드를 통한 일회성 초기화가 필요합니다. 읽기 전용이므로 동시 액세스를 위해 단일 전역 복사본을 공유 할 수 있습니다. 이 테이블을 사용하면 모든 다양한 정수 너비 (8, 16, 32 및 64 비트)에 대해 여기서 찾고 있는 정수 log 2를 빠르게 찾을 수 있습니다 .

0'가장 높은 설정 비트'라는 개념이 정의되지 않은 유일한 정수인에 대한 테이블 항목 에는 값이 지정 -1됩니다. 이 구분은 아래 코드에서 값이 0 인 대문자를 올바르게 처리하는 데 필요합니다. 더 이상 고민하지 않고 다양한 정수 프리미티브 각각에 대한 코드는 다음과 같습니다.

ulong (64 비트) 버전

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(this ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 0x40) - 1;      // handles cases v==0 and MSB==63

    int j = /**/ (int)((0xFFFFFFFFU - v /****/) >> 58) & 0x20;
    j |= /*****/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

uint (32 비트) 버전

/// <summary> Index of the highest set bit in 'v', or -1 for value '0' </summary>
public static int HighestOne(uint v)
{
    if ((int)v <= 0)
        return (int)((v >> 26) & 0x20) - 1;     // handles cases v==0 and MSB==31

    int j = (int)((0x0000FFFFU - v) >> 27) & 0x10;
    return j + msb_tab_15[v >> (j + 1)];
}

위의 다양한 과부하

public static int HighestOne(long v) => HighestOne((ulong)v);
public static int HighestOne(int v) => HighestOne((uint)v);
public static int HighestOne(ushort v) => msb_tab_15[v >> 1];
public static int HighestOne(short v) => msb_tab_15[(ushort)v >> 1];
public static int HighestOne(char ch) => msb_tab_15[ch >> 1];
public static int HighestOne(sbyte v) => msb_tab_15[(byte)v >> 1];
public static int HighestOne(byte v) => msb_tab_15[v >> 1];

이것은 전문 성능 테스트 도구와 비교 한 수많은 대안에 대해 .NET 4.7.2에서 최고의 성능을 나타내는 완전하고 작동하는 솔루션입니다. 이들 중 일부는 아래에 언급되어 있습니다. 테스트 매개 변수는 모든 65 비트 위치의 균일 한 밀도, 즉 0 ... 31/63 더하기 값 0(결과 -1 생성)이었습니다. 대상 인덱스 위치 아래 의 비트 는 무작위로 채워졌습니다. 테스트는 JIT 최적화가 활성화 된 x64 전용 릴리스 모드였습니다.




이것이 제 공식적인 대답의 끝입니다. 다음은 위 코드의 성능과 정확성을 검증하기 위해 실행 한 테스트와 관련된 대체 테스트 후보에 대한 간단한 메모와 소스 코드 링크입니다.


위에 제공된 Tab16A로 코딩 된 버전은 많은 실행에서 일관된 승자였습니다. 활성 작업 / 스크래치 형태의 다양한 후보는 여기 , 여기 에서 찾을 수 있습니다.여기 .

 후보 1 명 .HighestOne_Tab16A 622,496
 2 명의 후보. HighestOne_Tab16C 628,234
 3 명의 후보자 HighestOne_Tab8A 649,146
 4 명의 후보자. HighestOne_Tab8B 656,847
 5 명의 후보. HighestOne_Tab16B 657,147
 6 명의 후보. HighestOne_Tab16D 659,650
 7 _highest_one_bit_UNMANAGED.HighestOne_U 702,900
 8 de_Bruijn.IndexOfMSB 709,672
 9 _old_2.HighestOne_Old2 715,810
10 _test_A.HighestOne8 757,188
11 _old_1.HighestOne_Old1 757,925
12 _test_A.HighestOne5 (안전하지 않음) 760,387
13 _test_B.HighestOne8 (안전하지 않음) 763,904
14 _test_A.HighestOne3 (안전하지 않음) 766,433
15 _test_A.HighestOne1 (안전하지 않음) 767,321
16 _test_A.HighestOne4 (안전하지 않음) 771,702
17 _test_B.HighestOne2 (안전하지 않음) 772,136
18 _test_B.HighestOne1 (안전하지 않음) 772,527
19 _test_B.HighestOne3 (안전하지 않음) 774,140
20 _test_A.HighestOne7 (안전하지 않음) 774,581
21 _test_B.HighestOne7 (안전하지 않음) 775,463
22 _test_A.HighestOne2 (안전하지 않음) 776,865
23 명의 후보자 HighestOne_NoTab 777,698
24 _test_B.HighestOne6 (안전하지 않음) 779,481
25 _test_A.HighestOne6 (안전하지 않음) 781,553
26 _test_B.HighestOne4 (안전하지 않음) 785,504
27 _test_B.HighestOne5 (안전하지 않음) 789,797
28 _test_A.HighestOne0 (안전하지 않음) 809,566
29 _test_B.HighestOne0 (안전하지 않음) 814,990
30 _highest_one_bit.HighestOne 824,345
30 _bitarray_ext.RtlFindMostSignificantBit 894,069
31 명의 후보자. HighestOne_Naive 898,865

주목할만한 것은 ntdll.dll!RtlFindMostSignificantBitvia P / Invoke 의 끔찍한 성능입니다 .

[DllImport("ntdll.dll"), SuppressUnmanagedCodeSecurity, SecuritySafeCritical]
public static extern int RtlFindMostSignificantBit(ulong ul);

여기에 전체 실제 기능이 있기 때문에 정말 너무 나쁩니다.

    RtlFindMostSignificantBit:
        bsr rdx, rcx  
        mov eax,0FFFFFFFFh  
        movzx ecx, dl  
        cmovne      eax,ecx  
        ret

이 다섯 줄에서 비롯된 성능 저하를 상상할 수 없으므로 관리 / 네이티브 전환 페널티가 책임 져야합니다. 또한 테스트 short가 128 바이트 (및 256 바이트) byte(8 비트) 조회 테이블 보다 32KB (및 64KB) (16 비트) 직접 조회 테이블을 선호한다는 사실에 놀랐습니다 . 나는 다음이 16 비트 조회에 대해 더 경쟁력이있을 것이라고 생각했지만 후자는 지속적으로 이보다 더 나은 성능을 보였다.

public static int HighestOne_Tab8A(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    int j;
    j =  /**/ (int)((0xFFFFFFFFU - v) >> 58) & 32;
    j += /**/ (int)((0x0000FFFFU - (v >> j)) >> 59) & 16;
    j += /**/ (int)((0x000000FFU - (v >> j)) >> 60) & 8;
    return j + msb_tab_8[v >> j];
}

마지막으로 지적 할 것은 deBruijn 방법이 더 나아지지 않았다는 사실에 상당히 충격을 받았다는 것입니다. 이것은 내가 이전에 널리 사용했던 방법입니다.

const ulong N_bsf64 = 0x07EDD5E59A4E28C2,
            N_bsr64 = 0x03F79D71B4CB0A89;

readonly public static sbyte[]
bsf64 =
{
    63,  0, 58,  1, 59, 47, 53,  2, 60, 39, 48, 27, 54, 33, 42,  3,
    61, 51, 37, 40, 49, 18, 28, 20, 55, 30, 34, 11, 43, 14, 22,  4,
    62, 57, 46, 52, 38, 26, 32, 41, 50, 36, 17, 19, 29, 10, 13, 21,
    56, 45, 25, 31, 35, 16,  9, 12, 44, 24, 15,  8, 23,  7,  6,  5,
},
bsr64 =
{
     0, 47,  1, 56, 48, 27,  2, 60, 57, 49, 41, 37, 28, 16,  3, 61,
    54, 58, 35, 52, 50, 42, 21, 44, 38, 32, 29, 23, 17, 11,  4, 62,
    46, 55, 26, 59, 40, 36, 15, 53, 34, 51, 20, 43, 31, 22, 10, 45,
    25, 39, 14, 33, 19, 30,  9, 24, 13, 18,  8, 12,  7,  6,  5, 63,
};

public static int IndexOfLSB(ulong v) =>
    v != 0 ? bsf64[((v & (ulong)-(long)v) * N_bsf64) >> 58] : -1;

public static int IndexOfMSB(ulong v)
{
    if ((long)v <= 0)
        return (int)((v >> 57) & 64) - 1;

    v |= v >> 1; v |= v >> 2;  v |= v >> 4;   // does anybody know a better
    v |= v >> 8; v |= v >> 16; v |= v >> 32;  // way than these 12 ops?
    return bsr64[(v * N_bsr64) >> 58];
}

이 SO 질문에서 deBruijn 방법 얼마나 우수하고 위대한 지에 대한 많은 토론 있으며 나는 동의하는 경향이 있습니다. 내 추측은 deBruijn과 direct lookup table method (내가 가장 빠르다는 것을 알았다)는 둘 다 테이블 룩업을해야하고 둘 다 아주 최소한의 분기를 가지고 있지만 deBruijn만이 64 비트 곱셈 연산을한다는 것입니다. IndexOfMSB여기서는 deBruijn이 아닌 기능 만 테스트했지만 IndexOfLSB작업이 너무 적기 때문에 (위 참조) 후자가 훨씬 더 나은 기회를 제공 할 것으로 예상하고 LSB에 계속 사용할 것입니다.


1
최신 x86 CPU의 L1D 캐시는 32kiB에 불과합니다. 동일한 값을 반복적으로 사용하지 않는 한 큰 LUT는 작은 LUT보다 나쁠 수 있습니다. 그렇지 않으면 캐시 누락이 자주 발생합니다.
Peter Cordes

0

내 겸손한 방법은 매우 간단합니다.

MSB (x) = INT [Log (x) / Log (2)]

번역 : x의 MSB는 (기본 x의 로그를 기본 2의 로그로 나눈 값)의 정수 값입니다.

이것은 모든 프로그래밍 언어에 쉽고 빠르게 적용 할 수 있습니다. 계산기에서 작동하는지 직접 확인하십시오.


관심있는 모든 것이 개발자 효율성 인 경우 작동합니다. 런타임 효율성을 원한다면 대체 알고리즘이 필요합니다.
Mikko Rantalainen

반올림 오류로 인해 실패 할 수 있습니다. 예를 들어, CPython 2 및 3에서 int(math.log((1 << 48) - 1) / math.log(2))48입니다.
benrg

0

다음은 GCCClang 에서 작동 하는 C 용 빠른 솔루션입니다 . 복사하여 붙여 넣을 준비가되었습니다.

#include <limits.h>

unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

unsigned long flsl(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

unsigned long long flsll(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

그리고 C ++ 용으로 약간 개선 된 버전입니다 .

#include <climits>

constexpr unsigned int fls(const unsigned int value)
{
    return (unsigned int)1 << ((sizeof(unsigned int) * CHAR_BIT) - __builtin_clz(value) - 1);
}

constexpr unsigned long fls(const unsigned long value)
{
    return (unsigned long)1 << ((sizeof(unsigned long) * CHAR_BIT) - __builtin_clzl(value) - 1);
}

constexpr unsigned long long fls(const unsigned long long value)
{
    return (unsigned long long)1 << ((sizeof(unsigned long long) * CHAR_BIT) - __builtin_clzll(value) - 1);
}

코드는 그 가정 value하지 않습니다 0. 0을 허용하려면 수정해야합니다.


0

귀하의 질문은 부호없는 정수가 아닌 정수 (아래 v라고 함)에 대한 것이라고 가정합니다.

int v = 612635685; // whatever value you wish

unsigned int get_msb(int v)
{
    int r = 31;                         // maximum number of iteration until integer has been totally left shifted out, considering that first bit is index 0. Also we could use (sizeof(int)) << 3 - 1 instead of 31 to make it work on any platform.

    while (!(v & 0x80000000) && r--) {   // mask of the highest bit
        v <<= 1;                        // multiply integer by 2.
    }
    return r;                           // will even return -1 if no bit was set, allowing error catch
}

기호를 고려하지 않고 작동하게하려면 추가 'v << = 1;'을 추가 할 수 있습니다. 루프 전에 (그리고 r 값을 그에 따라 30으로 변경하십시오). 잊은 것이 있으면 알려주세요. 나는 그것을 테스트하지 않았지만 잘 작동합니다.


v <<= 1정의되지 않은 동작 (UB) v < 0입니다.
chux-Monica 복원

0x8000000, 아마도 당신은 거기에 여분의 0을 의미합니다.
MM
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.