次の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
は、ブランチレスでない場合、未定義の動作をします。また、GCCとClangは-mtune=generic
デフォルトで(ほとんどのディストリビューションと同様に)使用するため、コードはlzcnt
x86_64 の命令に展開されません-march=native
。そのため、提案された交換は、移植不可能でバグが多く、(通常)遅くなります。
next = pow(2, ceil(log(x)/log(2)));
これは、xを取得するために2で発生させた数値を見つけることによって機能します(数値の対数を取り、目的の基数の対数で除算します。詳細については、ウィキペディアを参照してください)。次に、それを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()
(カウントリーディングゼロ)を使用し、後で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)); }
もう1つ、サイクルを使用していますが、算術演算よりもはるかに高速です。
2つの累乗の「フロア」オプション:
int power = 1;
while (x >>= 1) power <<= 1;
2つの累乗「ceil」オプション:
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
署名されていないタイプの場合、Bit Twiddling Hacksに基づいて構築します。
#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
and が含まれますstd::floor2
(ここを参照)。これはconsexpr
テンプレート関数であり、現在のGCC実装はビットシフトを使用しており、任意の整数の符号なし型で機能します。
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の間でなければなりません。このマクロは、コンパイル時に定数を設定する場合にも役立ちます。
完全を期すために、ここでは沼地標準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ビットの場所を確認する前にデクリメントすることにより、入力が2の累乗の値と正確に一致するケースを処理します。
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
これにより、次のようなIntelプロセッサ用に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))
定数にうまく還元されます。
それを浮動小数点数に変換してから、正規化されたIEEE表現を示す.hex()を使用します。
>>> float(789).hex()
'0x1.8a80000000000p+9'
次に、指数を抽出して1を追加します。
>>> int(float(789).hex().split('p+')[1]) + 1
10
そして2をこの累乗します。
>>> 2 ** (int(float(789).hex().split('p+')[1]) + 1)
1024
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;
}
}
1行テンプレートが必要な場合。ここにあります
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
後に新しい値が含まれ;
、括弧がそれを変更しないことです。