次の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を上げる数を見つけることによって機能します(数のログを取得し、目的のベースのログで除算します 詳細についてはウィキペディアを参照 )。次に、それを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()
(先頭のゼロをカウント)の使用方法について説明します。 another answer の gamedevサイトへのリンク で説明されているように、後でx86(ia32)アセンブラー命令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));
}
もう1つ、私はサイクルを使用していますが、thiは数学オペランドよりもはるかに高速です
2のべき乗の「フロア」オプション:
int power = 1;
while (x >>= 1) power <<= 1;
2のべき乗「ceil」オプション:
int power = 2;
x--; // <<-- UPDATED
while (x >>= 1) power <<= 1;
UPDATE
コメントで述べたように、結果が間違っていた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;
}
署名のないタイプの場合、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;
}
コンパイラはコンパイル時に反復回数を知っているため、実際にはループはありません。
IEEEフロートの場合、このようなことができます。
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での浮動小数点実装です。
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);
}
/*
** 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/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));
}
さらにいくつかの命令を生成します。秘Theは、Indexをテストとそれに続くcmove命令で置き換えることができるということです。
これがCでの私の解決策です。これが役に立てば幸いです!
int next_power_of_two(int n) {
int i = 0;
for (--n; n > 0; n >>= 1) {
i++;
}
return 1 << i;
}
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では、一致する組み込み関数を使用できます。
Paul DixonのExcelへの回答を適合させた、これは完全に機能します。
=POWER(2,CEILING.MATH(LOG(A1)/LOG(2)))
入力が定数式である場合、これを定数式にするために使用しているものを次に示します。
#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))
定数にうまく減少します。
あなたが良いコンパイラを持っていると仮定すると、それはこの時点で私の上にある手前で少しいじることができますが、とにかくこれは動作します!!!
// 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
多くのプロセッサアーキテクチャは、log base 2
または非常に類似した操作– count leading zeros
をサポートしています。多くのコンパイラには、組み込み関数があります。 https://en.wikipedia.org/wiki/Find_first_set を参照してください
x==1
に対して有効な@YannDroneaudの回答のバリアントは、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);
}
私は最も近い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;
}