IT

2의 다음 거듭 제곱으로 반올림

lottoking 2020. 5. 28. 08:17
반응형

2의 다음 거듭 제곱으로 반올림


가장 가까운 다음 2의 거듭 제곱을 반환하는 함수를 작성하고 싶습니다. 예를 들어 내 입력이 789 인 경우 출력은 1024 여야합니다. 루프를 사용하지 않고 비트 연산자 만 사용하여이를 달성 할 수있는 방법이 있습니까?


Bit Twiddling Hacks를 확인하십시오 . 밑이 2 인 로그를 구한 다음 1을 더해야합니다. 32 비트 값의 예 :

2의 다음 최대 거듭 제곱으로 올림

unsigned int v; // compute the next highest power of 2 of 32-bit v

v--;
v |= v >> 1;
v |= v >> 2;
v |= v >> 4;
v |= v >> 8;
v |= v >> 16;
v++;

다른 너비로의 확장은 분명해야합니다.


next = pow(2, ceil(log(x)/log(2)));

이것은 x를 얻기 위해 2를 올린 숫자를 찾아서 작동합니다 (숫자의 로그를 가져 와서 원하는 기본의 로그로 나눕니다 . 자세한 내용은 wikipedia 참조 ). 그런 다음 ceil로 반올림하여 가장 가까운 정수 전력을 얻습니다.

이것은 다른 곳에서 연결된 비트 방식보다 일반적인 목적 (즉, 느리게!)이지만 수학을 아는 것이 좋습니다.


unsigned long upper_power_of_two(unsigned long v)
{
    v--;
    v |= v >> 1;
    v |= v >> 2;
    v |= v >> 4;
    v |= v >> 8;
    v |= v >> 16;
    v++;
    return v;

}

나는 이것이 작동한다고 생각한다.

int power = 1;
while(power < x)
    power*=2;

대답은 power입니다.


GCC를 사용하는 경우 Lockless Inc. 의 next_pow2 () 함수 최적화를 참조하십시오.이 페이지에서는 내장 함수 builtin_clz()(제로 선행 0)를 사용하고 나중에 직접 x86 (ia32) 을 사용하는 방법에 대해 설명합니다. gamedev 사이트에 대한 다른 답변링크에bsr 설명 된 것처럼 어셈블러 명령어 (비트 스캔 역) . 이 코드는 이전 답변 에서 설명한 것보다 빠를 수 있습니다 .

그런데 어셈블러 명령어와 64 비트 데이터 형식을 사용하지 않을 경우에는

/**
 * return the smallest power of two value
 * greater than x
 *
 * Input range:  [2..2147483648]
 * Output range: [2..2147483648]
 *
 */
__attribute__ ((const))
static inline uint32_t p2(uint32_t x)
{
#if 0
    assert(x > 1);
    assert(x <= ((UINT32_MAX/2) + 1));
#endif

    return 1 << (32 - __builtin_clz (x - 1));
}

하나 더, 사이클을 사용하지만 수학 피연산자보다 훨씬 빠릅니다.

두 개의 "바닥"옵션의 힘 :

int power = 1;
while (x >>= 1) power <<= 1;

두 개의 "천장"옵션의 힘 :

int power = 2;
x--;    // <<-- UPDATED
while (x >>= 1) power <<= 1;

최신 정보

의견에서 언급했듯이 ceil결과가 잘못 곳에 실수가있었습니다 .

전체 기능은 다음과 같습니다.

unsigned power_floor(unsigned x) {
    int power = 1;
    while (x >>= 1) power <<= 1;
    return power;
}

unsigned power_ceil(unsigned x) {
    if (x <= 1) return 1;
    int power = 2;
    x--;
    while (x >>= 1) power <<= 1;
    return power;
}

비트 트위들 링 해킹을 기반으로하는 서명되지 않은 유형의 경우 :

#include <climits>
#include <type_traits>

template <typename UnsignedType>
UnsignedType round_up_to_power_of_2(UnsignedType v) {
  static_assert(std::is_unsigned<UnsignedType>::value, "Only works for unsigned types");
  v--;
  for (size_t i = 1; i < sizeof(v) * CHAR_BIT; i *= 2) //Prefer size_t "Warning comparison between signed and unsigned integer"
  {
    v |= v >> i;
  }
  return ++v;
}

컴파일러가 컴파일 타임에 반복 횟수를 알고 있기 때문에 실제로 루프가 없습니다.


IEEE float의 경우 다음과 같은 작업을 수행 할 수 있습니다.

int next_power_of_two(float a_F){
    int f = *(int*)&a_F;
    int b = f << 9 != 0; // If we're a power of two this is 0, otherwise this is 1

    f >>= 23; // remove factional part of floating point number
    f -= 127; // subtract 127 (the bias) from the exponent

    // adds one to the exponent if were not a power of two, 
    // then raises our new exponent to the power of two again.
    return (1 << (f + b)); 
}

정수 솔루션이 필요하고 인라인 어셈블리를 사용할 수있는 경우 BSR은 x86에서 정수의 log2를 제공합니다. 얼마나 많은 오른쪽 비트가 설정되어 있는지 계산합니다. 이는 해당 숫자의 log2와 정확히 같습니다. 다른 프로세서에는 CLZ와 같은 유사한 명령어가 있으며 (종종) 컴파일러에 따라 작업을 수행 할 수있는 내장 함수가있을 수 있습니다.


/*
** http://graphics.stanford.edu/~seander/bithacks.html#IntegerLog
*/
#define __LOG2A(s) ((s &0xffffffff00000000) ? (32 +__LOG2B(s >>32)): (__LOG2B(s)))
#define __LOG2B(s) ((s &0xffff0000)         ? (16 +__LOG2C(s >>16)): (__LOG2C(s)))
#define __LOG2C(s) ((s &0xff00)             ? (8  +__LOG2D(s >>8)) : (__LOG2D(s)))
#define __LOG2D(s) ((s &0xf0)               ? (4  +__LOG2E(s >>4)) : (__LOG2E(s)))
#define __LOG2E(s) ((s &0xc)                ? (2  +__LOG2F(s >>2)) : (__LOG2F(s)))
#define __LOG2F(s) ((s &0x2)                ? (1)                  : (0))

#define LOG2_UINT64 __LOG2A
#define LOG2_UINT32 __LOG2B
#define LOG2_UINT16 __LOG2C
#define LOG2_UINT8  __LOG2D

static inline uint64_t
next_power_of_2(uint64_t i)
{
#if defined(__GNUC__)
    return 1UL <<(1 +(63 -__builtin_clzl(i -1)));
#else
    i =i -1;
    i =LOG2_UINT64(i);
    return 1UL <<(1 +i);
#endif
}

정의되지 않은 동작 영역으로 들어 가지 않으려면 입력 값이 1과 2 ^ 63 사이 여야합니다. 이 매크로는 컴파일 타임에 상수를 설정하는데도 유용합니다.


완전성을 위해 여기에는 bog 표준 C의 부동 소수점 구현이 있습니다.

double next_power_of_two(double value) {
    int exp;
    if(frexp(value, &exp) == 0.5) {
        // Omit this case to round precise powers of two up to the *next* power
        return value;
    }
    return ldexp(1.0, exp);
}

정수 입력을위한 C / C ++의 효율적인 Microsoft (예 : Visual Studio 2017) 특정 솔루션입니다. 가장 중요한 1 비트의 위치를 ​​확인하기 전에 감소시켜 두 값의 거듭 제곱과 정확히 일치하는 입력의 경우를 처리합니다.

inline unsigned int ExpandToPowerOf2(unsigned int Value)
{
    unsigned long Index;
    _BitScanReverse(&Index, Value - 1);
    return (1U << (Index + 1));
}

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

#if defined(WIN64) // The _BitScanReverse64 intrinsic is only available for 64 bit builds because it depends on x64

inline unsigned long long ExpandToPowerOf2(unsigned long long Value)
{
    unsigned long Index;
    _BitScanReverse64(&Index, Value - 1);
    return (1ULL << (Index + 1));
}

#endif

그러면 다음과 유사한 인텔 프로세서에 대해 5 개 정도의 인라인 명령어가 생성됩니다.

dec eax
bsr rcx, rax
inc ecx
mov eax, 1
shl rax, cl

분명히 Visual Studio C ++ 컴파일러는 컴파일 타임 값을 위해 이것을 최적화하도록 코딩되지 않았지만 거기에 많은 명령이있는 것은 아닙니다.

편집하다:

1의 입력 값이 1 (2에서 0의 제곱 거듭 제곱)을 생성하도록하려면 위 코드를 약간만 수정해도 분기가없는 명령을 통해 직접 생성됩니다.

inline unsigned int ExpandToPowerOf2(unsigned int Value)
{
    unsigned long Index;
    _BitScanReverse(&Index, --Value);
    if (Value == 0)
        Index = (unsigned long) -1;
    return (1U << (Index + 1));
}

몇 가지 명령 만 더 생성합니다. 요령은 인덱스를 테스트와 cmove 명령으로 대체 할 수 있다는 것입니다.


x86에서는 sse4 비트 조작 명령을 사용하여 빠르게 만들 수 있습니다.

//assume input is in eax
popcnt edx,eax
lzcnt ecx,eax
cmp edx,1
jle @done       //popcnt says its a power of 2, return input unchanged
mov eax,2
shl eax,cl
@done: rep ret

c에서는 일치하는 내장 함수를 사용할 수 있습니다.


여기 C의 솔루션이 있습니다. 도움이 되길 바랍니다.

int next_power_of_two(int n) {
    int i = 0;
    for (--n; n > 0; n >>= 1) {
        i++;
    }
    return 1 << i;
}

질문에도 불구하고 c여기에 내 5 센트 가 붙어 있습니다. 운 좋게도 C ++ 20에는 std::ceil2이 포함됩니다 std::floor2( 여기 참조 ). 그것은이다 consexpr템플릿 기능, 현재 GCC 구현 어떤 정수 부호없는 형식에 사용 bitshifting 및 작품.


많은 프로세서 아키텍처가 log base 2매우 유사한 작업을 지원 count leading zeros합니다. 많은 컴파일러가 내장 함수를 가지고 있습니다. 참조 https://en.wikipedia.org/wiki/Find_first_set를


좋은 컴파일러가 있다고 가정하면이 시점에서 나보다 앞서서 약간 손을 do 수 있지만 어쨌든 이것이 작동합니다!

    // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
    #define SH1(v)  ((v-1) | ((v-1) >> 1))            // accidently came up w/ this...
    #define SH2(v)  ((v) | ((v) >> 2))
    #define SH4(v)  ((v) | ((v) >> 4))
    #define SH8(v)  ((v) | ((v) >> 8))
    #define SH16(v) ((v) | ((v) >> 16))
    #define OP(v) (SH16(SH8(SH4(SH2(SH1(v))))))         

    #define CB0(v)   ((v) - (((v) >> 1) & 0x55555555))
    #define CB1(v)   (((v) & 0x33333333) + (((v) >> 2) & 0x33333333))
    #define CB2(v)   ((((v) + ((v) >> 4) & 0xF0F0F0F) * 0x1010101) >> 24)
    #define CBSET(v) (CB2(CB1(CB0((v)))))
    #define FLOG2(v) (CBSET(OP(v)))

아래 테스트 코드 :

#include <iostream>

using namespace std;

// http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogObvious
#define SH1(v)  ((v-1) | ((v-1) >> 1))  // accidently guess this...
#define SH2(v)  ((v) | ((v) >> 2))
#define SH4(v)  ((v) | ((v) >> 4))
#define SH8(v)  ((v) | ((v) >> 8))
#define SH16(v) ((v) | ((v) >> 16))
#define OP(v) (SH16(SH8(SH4(SH2(SH1(v))))))         

#define CB0(v)   ((v) - (((v) >> 1) & 0x55555555))
#define CB1(v)   (((v) & 0x33333333) + (((v) >> 2) & 0x33333333))
#define CB2(v)   ((((v) + ((v) >> 4) & 0xF0F0F0F) * 0x1010101) >> 24)
#define CBSET(v) (CB2(CB1(CB0((v)))))
#define FLOG2(v) (CBSET(OP(v))) 

#define SZ4         FLOG2(4)
#define SZ6         FLOG2(6)
#define SZ7         FLOG2(7)
#define SZ8         FLOG2(8) 
#define SZ9         FLOG2(9)
#define SZ16        FLOG2(16)
#define SZ17        FLOG2(17)
#define SZ127       FLOG2(127)
#define SZ1023      FLOG2(1023)
#define SZ1024      FLOG2(1024)
#define SZ2_17      FLOG2((1ul << 17))  // 
#define SZ_LOG2     FLOG2(SZ)

#define DBG_PRINT(x) do { std::printf("Line:%-4d" "  %10s = %-10d\n", __LINE__, #x, x); } while(0);

uint32_t arrTble[FLOG2(63)];

int main(){
    int8_t n;

    DBG_PRINT(SZ4);    
    DBG_PRINT(SZ6);    
    DBG_PRINT(SZ7);    
    DBG_PRINT(SZ8);    
    DBG_PRINT(SZ9); 
    DBG_PRINT(SZ16);
    DBG_PRINT(SZ17);
    DBG_PRINT(SZ127);
    DBG_PRINT(SZ1023);
    DBG_PRINT(SZ1024);
    DBG_PRINT(SZ2_17);

    return(0);
}

출력 :

Line:39           SZ4 = 2
Line:40           SZ6 = 3
Line:41           SZ7 = 3
Line:42           SZ8 = 3
Line:43           SZ9 = 4
Line:44          SZ16 = 4
Line:45          SZ17 = 5
Line:46         SZ127 = 7
Line:47        SZ1023 = 10
Line:48        SZ1024 = 10
Line:49        SZ2_16 = 17

가장 가까운 2의 낮은 전력을 얻으려고 노력 하고이 기능을 만들었습니다. 가장 가까운 더 낮은 수에 2를 곱하여 가장 가까운 2의 거듭 제곱을 구하십시오.

int nearest_upper_power(int number){
    int temp=number;
    while((number&(number-1))!=0){
        temp<<=1;
        number&=temp;
    }
    //Here number is closest lower power 
    number*=2;
    return number;
}

Excel에 대한 Paul Dixon의 답변에 따라 완벽하게 작동합니다.

 =POWER(2,CEILING.MATH(LOG(A1)/LOG(2)))

@YannDroneaud 응답의 변형은 x==1x86 플레이트, 컴파일러, gcc 또는 clang에만 유효합니다 .

__attribute__ ((const))
static inline uint32_t p2(uint32_t x)
{
#if 0
    assert(x > 0);
    assert(x <= ((UINT32_MAX/2) + 1));
#endif
  int clz;
  uint32_t xm1 = x-1;
  asm(
    "lzcnt %1,%0"
    :"=r" (clz)
    :"rm" (xm1)
    :"cc"
    );
    return 1 << (32 - clz);
}

입력이 상수 표현식 인 경우 상수 표현식이되도록 사용하고 있습니다.

#define uptopow2_0(v) ((v) - 1)
#define uptopow2_1(v) (uptopow2_0(v) | uptopow2_0(v) >> 1)
#define uptopow2_2(v) (uptopow2_1(v) | uptopow2_1(v) >> 2)
#define uptopow2_3(v) (uptopow2_2(v) | uptopow2_2(v) >> 4)
#define uptopow2_4(v) (uptopow2_3(v) | uptopow2_3(v) >> 8)
#define uptopow2_5(v) (uptopow2_4(v) | uptopow2_4(v) >> 16)

#define uptopow2(v) (uptopow2_5(v) + 1)  /* this is the one programmer uses */

예를 들어 다음과 같은 표현식은

uptopow2(sizeof (struct foo))

일정하게 줄어 듭니다.


OpenGL 관련 항목에 필요한 경우 :

/* Compute the nearest power of 2 number that is 
 * less than or equal to the value passed in. 
 */
static GLuint 
nearestPower( GLuint value )
{
    int i = 1;

    if (value == 0) return -1;      /* Error! */
    for (;;) {
         if (value == 1) return i;
         else if (value == 3) return i*4;
         value >>= 1; i *= 2;
    }
}

참고 URL : https://stackoverflow.com/questions/466204/rounding-up-to-next-power-of-2

반응형