Saya ingin menulis fungsi yang mengembalikan kekuatan terdekat 2 angka. Sebagai contoh jika input saya 789, output harus 1024. Apakah ada cara untuk mencapai ini tanpa menggunakan loop tetapi hanya menggunakan beberapa operator bitwise?
Saya ingin menulis fungsi yang mengembalikan kekuatan terdekat 2 angka. Sebagai contoh jika input saya 789, output harus 1024. Apakah ada cara untuk mencapai ini tanpa menggunakan loop tetapi hanya menggunakan beberapa operator bitwise?
Jawaban:
Periksa Bit Twiddling Hacks . Anda perlu mendapatkan logaritma basis 2, lalu tambahkan 1 untuk itu. Contoh untuk nilai 32-bit:
Membulatkan ke kekuatan tertinggi berikutnya 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++;
Perpanjangan ke lebar lain harus jelas.
uint64_t next_pow2(uint64_t x) { return x == 1 ? 1 : 1<<(64-__builtin_clzl(x-1)); }
Dan untuk 32 bit: uint32_t next_pow2(uint32_t x) { return x == 1 ? 1 : 1<<(32-__builtin_clz(x-1)); }
Itu jika Anda menggunakan GCC (dan menurut saya, Dentang?), Tetapi akan lebih bijaksana jika meluangkan waktu untuk temukan panggilan ke CLZ alih-alih menempelkan semua opsi di sekitar.
x > UINT32_MAX
dan tidak bercabang. Juga, GCC dan Dentang digunakan -mtune=generic
secara default (seperti kebanyakan distro), jadi kode Anda TIDAK akan meluas ke lzcnt
instruksi pada x86_64 - itu sebenarnya akan meluas ke sesuatu yang JAUH lebih lambat (rutin libgcc) kecuali Anda menggunakan sesuatu seperti -march=native
. Jadi pengganti yang Anda ajukan adalah non-portable, buggy dan (biasanya) lebih lambat.
next = pow(2, ceil(log(x)/log(2)));
Ini berfungsi dengan menemukan angka yang harus Anda naikkan 2 untuk mendapatkan x (ambil log nomor tersebut, dan bagi dengan log basis yang diinginkan, lihat wikipedia untuk informasi lebih lanjut ). Kemudian kumpulkan dengan langit-langit untuk mendapatkan kekuatan bilangan bulat terdekat.
Ini adalah metode yang lebih umum (yaitu lebih lambat!) Daripada metode bitwise yang ditautkan di tempat lain, tetapi bagus untuk mengetahui matematika, eh?
log(pow(2,29))/log(2)
= 29.000000000000004, jadi hasilnya adalah 2 30 bukannya mengembalikan 2 29. Saya pikir ini adalah mengapa fungsi log2 ada?
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
.
Saya pikir ini juga berfungsi:
int power = 1;
while(power < x)
power*=2;
Dan jawabannya adalah power
.
power <<= 1
x
terlalu besar (mis. Tidak cukup bit untuk mewakili kekuatan 2 berikutnya).
Jika Anda menggunakan GCC, Anda mungkin ingin melihat Mengoptimalkan fungsi next_pow2 () oleh Lockless Inc. .. Halaman ini menjelaskan cara untuk menggunakan fungsi bawaan builtin_clz()
(menghitung memimpin nol) dan kemudian menggunakan langsung x86 (ia32) assembler instruksi bsr
(bit pemindaian terbalik), seperti itu dijelaskan dalam jawaban lain 's link ke situs gamedev . Kode ini mungkin lebih cepat daripada yang dijelaskan dalam jawaban sebelumnya .
Omong-omong, jika Anda tidak akan menggunakan instruksi assembler dan tipe data 64bit, Anda bisa menggunakan ini
/**
* 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()
tidak akan berguna untuk membulatkan non power dari 2 angka hingga kekuatan dua berikutnya
constexpr uint64_t nextPowerOfTwo64 (uint64_t x) { return 1ULL<<(sizeof(uint64_t) * 8 - __builtin_clzll(x)); }
Satu lagi, meskipun saya menggunakan siklus, tetapi ini jauh lebih cepat daripada operan matematika
kekuatan dua opsi "lantai":
int power = 1;
while (x >>= 1) power <<= 1;
kekuatan dua opsi "ceil":
int power = 2;
x--; // <<-- UPDATED
while (x >>= 1) power <<= 1;
MEMPERBARUI
Seperti disebutkan dalam komentar ada kesalahan di ceil
mana hasilnya salah.
Berikut adalah fungsi lengkapnya:
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
kekuatan 2. Mikro untuk menguji apakah input adalah kekuatan 2 diperlukan. #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
itu tidak benar. Misalnya, ketika x = 2
hasilnya seharusnya 2
bukan4
Untuk jenis yang tidak ditandatangani, membangun 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;
}
Sebenarnya tidak ada loop di sana sebagai kompiler tahu pada waktu kompilasi jumlah iterasi.
std::is_unsigned<UnsignedType>::value
pernyataan tersebut.
Untuk pelampung IEEE Anda bisa melakukan hal seperti ini.
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));
}
Jika Anda membutuhkan solusi integer dan Anda dapat menggunakan perakitan inline, BSR akan memberi Anda log2 integer pada x86. Itu menghitung berapa banyak bit kanan diatur, yang persis sama dengan log2 dari angka itu. Prosesor lain memiliki instruksi yang serupa (sering), seperti CLZ dan tergantung pada kompiler Anda, mungkin ada intrinsik yang tersedia untuk melakukan pekerjaan untuk Anda.
Meskipun pertanyaannya ditandai karena di c
sini lima sen saya. Beruntung kami, C ++ 20 akan mencakup std::ceil2
dan std::floor2
(lihat di sini ). Ini adalah consexpr
fungsi template, implementasi GCC saat ini menggunakan bitshifting dan bekerja dengan semua tipe unsigned integral.
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
}
Jika Anda tidak ingin menjelajah ke ranah perilaku tidak terdefinisi, nilai input harus antara 1 dan 2 ^ 63. Makro juga berguna untuk menetapkan konstanta pada waktu kompilasi.
Untuk kelengkapan di sini adalah implementasi floating-point dalam standar rawa 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
sekitar 25x lebih cepat.
Solusi spesifik Microsoft (mis. Visual Studio 2017) yang efisien dalam C / C ++ untuk input integer. Menangani case dari input yang sama persis dengan kekuatan dua nilai dengan mengurangi sebelum memeriksa lokasi 1 bit paling signifikan.
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
Ini menghasilkan 5 atau lebih instruksi bergaris untuk prosesor Intel yang serupa dengan yang berikut:
dec eax
bsr rcx, rax
inc ecx
mov eax, 1
shl rax, cl
Tampaknya kompiler Visual Studio C ++ tidak dikodekan untuk mengoptimalkan ini untuk nilai waktu kompilasi, tetapi tidak seperti ada banyak instruksi di sana.
Edit:
Jika Anda ingin nilai input 1 menghasilkan 1 (2 pangkat zeroth), sedikit modifikasi pada kode di atas masih menghasilkan instruksi langsung tanpa cabang.
inline unsigned int ExpandToPowerOf2(unsigned int Value)
{
unsigned long Index;
_BitScanReverse(&Index, --Value);
if (Value == 0)
Index = (unsigned long) -1;
return (1U << (Index + 1));
}
Hasilkan hanya beberapa instruksi lagi. Kuncinya adalah bahwa Indeks dapat diganti dengan tes diikuti oleh instruksi cmove.
Di x86 Anda dapat menggunakan instruksi manipulasi sse4 bit untuk membuatnya cepat.
//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
Dalam c Anda dapat menggunakan intrinsik yang cocok.
Inilah solusi saya di C. Semoga ini bisa membantu!
int next_power_of_two(int n) {
int i = 0;
for (--n; n > 0; n >>= 1) {
i++;
}
return 1 << i;
}
Banyak arsitektur prosesor mendukung log base 2
atau operasi yang sangat mirip - count leading zeros
. Banyak kompiler memiliki intrinsik untuk itu. Lihat https://en.wikipedia.org/wiki/Find_first_set
Dengan asumsi Anda memiliki kompiler yang baik & dapat melakukan sedikit twiddling sebelum tangan itu di atas saya pada saat ini, tetapi tetap ini bekerja !!!
// 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)))
Kode tes di bawah ini:
#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);
}
Output:
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
Saya mencoba untuk mendapatkan daya terdekat 2 yang lebih rendah dan membuat fungsi ini. Semoga itu membantu Anda. Baru saja dikalikan angka terendah terdekat kali 2 untuk mendapatkan kekuatan tertinggi terdekat 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;
}
Diadaptasi jawaban Paul Dixon untuk Excel, ini berfungsi dengan baik.
=POWER(2,CEILING.MATH(LOG(A1)/LOG(2)))
Varian jawaban @YannDroneaud hanya berlaku untuk x==1
, hanya untuk pelat x86, kompiler, gcc, atau dentang:
__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);
}
Inilah yang saya gunakan untuk membuat ini menjadi ekspresi konstan, jika inputnya adalah ekspresi konstan.
#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 */
Jadi misalnya, ekspresi seperti:
uptopow2(sizeof (struct foo))
akan dengan baik mengurangi ke konstan.
Konversikan menjadi float lalu gunakan .hex () yang menunjukkan representasi IEEE yang dinormalisasi.
>>> float(789).hex()
'0x1.8a80000000000p+9'
Kemudian cukup ekstrak eksponen dan tambahkan 1.
>>> int(float(789).hex().split('p+')[1]) + 1
10
Dan angkatlah 2 menjadi kekuatan ini.
>>> 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
Jika Anda membutuhkannya untuk hal-hal terkait 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;
}
}
Jika Anda menginginkan templat satu garis. Ini dia
int nxt_po2(int n) { return 1 + (n|=(n|=(n|=(n|=(n|=(n-=1)>>1)>>2)>>4)>>8)>>16); }
atau
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
beberapa kali tanpa titik urutan tidak valid. Anda menulisnya seolah-olah n-=1
harus terjadi terlebih dahulu tetapi satu-satunya jaminan di sini adalah yang n
berisi nilai baru setelah ;
tanda kurung dan tidak mengubah itu.