가장 가까운 다음 2의 거듭 제곱을 반환하는 함수를 작성하고 싶습니다. 예를 들어 내 입력이 789 인 경우 출력은 1024 여야합니다. 루프를 사용하지 않고 비트 연산자 만 사용하여이를 달성 할 수있는 방법이 있습니까?
가장 가까운 다음 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++;
다른 폭으로의 확장은 분명해야합니다.
uint64_t next_pow2(uint64_t x) { return x == 1 ? 1 : 1<<(64-__builtin_clzl(x-1)); }
그리고 32 비트 : uint32_t next_pow2(uint32_t x) { return x == 1 ? 1 : 1<<(32-__builtin_clz(x-1)); }
GCC (그리고 Clang이라고 생각합니까?)를 사용한다면 시간이 걸리는 것이 현명합니다. 주변의 모든 옵션을 복사하여 붙여 넣는 대신 CLZ에 대한 호출을 찾으십시오.
x > UINT32_MAX
분기가없는 경우 64 비트 버전에 정의되지 않은 동작 이 있습니다. 또한 GCC와 Clang -mtune=generic
은 기본적으로 (대부분의 배포판과 마찬가지로) 코드를 사용하므로 lzcnt
x86_64 의 명령으로 코드가 확장되지 않습니다 -march=native
. 따라서 제안 된 대체품은 이식성이없고, 버그가 있으며 (보통) 느립니다.
next = pow(2, ceil(log(x)/log(2)));
이것은 x를 얻기 위해 2를 올린 숫자를 찾아서 작동합니다 (숫자의 로그를 가져 와서 원하는 기본의 로그로 나눕니다 . 자세한 내용은 wikipedia 참조 ). 그런 다음 ceil로 반올림하여 가장 가까운 정수 전력을 얻습니다.
이것은 다른 곳에서 연결된 비트 방식보다 일반적인 목적 (즉, 느리게!)이지만 수학을 아는 것이 좋습니다.
log(pow(2,29))/log(2)
= 29.000000000000004, 결과는 2 29 를 반환하는 대신 2 30 입니다. 이것이 log2 함수가 존재하는 이유라고 생각합니다.
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;
}
uint32_t
.
나는 이것이 작동한다고 생각한다.
int power = 1;
while(power < x)
power*=2;
대답은 power
입니다.
power <<= 1
x
너무 큰 경우 무한 루프에주의하십시오 (즉, 2의 다음 거듭 제곱을 나타내기에 충분한 비트가 아님).
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));
}
_BitScanForward
Visual C ++에서 사용할 수 있습니다
__builtin_ctz()
__builtin_ctz()
는 2의 비 거듭 제곱을 다음 2의 거듭 제곱으로 반올림하는 데 유용하지 않습니다.
constexpr uint64_t nextPowerOfTwo64 (uint64_t x) { return 1ULL<<(sizeof(uint64_t) * 8 - __builtin_clzll(x)); }
하나 더, 사이클을 사용하지만 수학 피연산자보다 훨씬 빠릅니다.
두 개의 "바닥"옵션의 힘 :
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;
}
x
입력이 필요하다 (2)의 전원이있는 경우에 테스트 2. 마이크로 전력이다. #define ISPOW2(x) ((x) > 0 && !((x) & (x-1)))
if (x == 0) return 1; /* Or 0 (Which is what I use) */ x--; /* Rest of program */
power of two "ceil" option
올바르지 않습니다. 예를 들어 x = 2
결과가 다음 2
과 같이 4
비트 트위들 링 해킹을 기반으로하는 서명되지 않은 유형의 경우 :
#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;
}
컴파일러가 컴파일 타임에 반복 횟수를 알고 있기 때문에 실제로 루프가 없습니다.
std::is_unsigned<UnsignedType>::value
어설 션을 무시 하고이 간단한 템플릿을 확장 할 수 있다고 확신합니다 .
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와 같은 유사한 지침이 있으며 (종종) 컴파일러에 따라 작업을 수행하는 데 본질적인 기능이있을 수 있습니다.
질문에도 불구하고 c
여기에 내 5 센트 가 붙어 있습니다. 운 좋게도 C ++ 20에는 std::ceil2
및 이 포함됩니다 std::floor2
( 여기 참조 ). 그것은이다 consexpr
템플릿 기능, 현재 GCC 구현 어떤 정수 부호없는 형식에 사용 bitshifting 및 작품.
bit_ceil
open-std.org/JTC1/SC22/WG21/docs/papers/2020/p1956r1.pdf
/*
** 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);
}
rep bsr ecx,eax; mov eax,0; cmovnz eax,2; shl eax,cl
약 25 배 빠릅니다.
정수 입력을위한 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 명령으로 대체 할 수 있다는 것입니다.
많은 프로세서 아키텍처가 log base 2
매우 유사한 작업을 지원 합니다 – – count leading zeros
. 많은 컴파일러가 내장 함수를 가지고 있습니다. 참조 https://en.wikipedia.org/wiki/Find_first_set를
좋은 컴파일러가 있다고 가정하면이 시점에서 나보다 앞서있는 비트 손질을 할 수 있지만 어쨌든 이것이 작동합니다!
// 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
@YannDroneaud 응답의 변형은 x==1
x86 플레이트, 컴파일러, 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))
일정하게 줄어 듭니다.
import sys
def is_power2(x):
return x > 0 and ((x & (x - 1)) == 0)
def find_nearest_power2(x):
if x <= 0:
raise ValueError("invalid input")
if is_power2(x):
return x
else:
bits = get_bits(x)
upper = 1 << (bits)
lower = 1 << (bits - 1)
mid = (upper + lower) // 2
if (x - mid) > 0:
return upper
else:
return lower
def get_bits(x):
"""return number of bits in binary representation"""
if x < 0:
raise ValueError("invalid input: input should be positive integer")
count = 0
while (x != 0):
try:
x = x >> 1
except TypeError as error:
print(error, "input should be of type integer")
sys.exit(1)
count += 1
return count
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;
}
}
한 줄 템플릿을 원한다면. 여기있어
int nxt_po2(int n) { return 1 + (n|=(n|=(n|=(n|=(n|=(n-=1)>>1)>>2)>>4)>>8)>>16); }
또는
int nxt_po2(int n) { return 1 + (n|=(n|=(n|=(n|=(n|=(n-=1)>>(1<<0))>>(1<<1))>>(1<<2))>>(1<<3))>>(1<<4)); }
n
시퀀스 포인트없이 여러 번 수정 하는 것은 유효하지 않습니다. 당신은 그것을 n-=1
먼저 해야하는 것처럼 썼지 만 여기서 유일한 보증 은 괄호가 그것을 바꾸지 않은 n
후에 새로운 가치 를 포함 ;
한다는 것입니다.