Tôi muốn viết một hàm trả về lũy thừa tiếp theo là 2 số. Ví dụ: nếu đầu vào của tôi là 789, thì đầu ra phải là 1024. Có cách nào để đạt được điều này mà không cần sử dụng bất kỳ vòng lặp nào mà chỉ sử dụng một số toán tử bitwise không?
Tôi muốn viết một hàm trả về lũy thừa tiếp theo là 2 số. Ví dụ: nếu đầu vào của tôi là 789, thì đầu ra phải là 1024. Có cách nào để đạt được điều này mà không cần sử dụng bất kỳ vòng lặp nào mà chỉ sử dụng một số toán tử bitwise không?
Câu trả lời:
Kiểm tra các hack Twiddling Bit . Bạn cần lấy logarit cơ sở 2, sau đó thêm 1 vào đó. Ví dụ cho giá trị 32 bit:
Làm tròn đến sức mạnh cao nhất tiếp theo là 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++;
Việc mở rộng cho các chiều rộng khác nên rõ ràng.
uint64_t next_pow2(uint64_t x) { return x == 1 ? 1 : 1<<(64-__builtin_clzl(x-1)); }
Và trong 32 bit: uint32_t next_pow2(uint32_t x) { return x == 1 ? 1 : 1<<(32-__builtin_clz(x-1)); }
Đó là nếu bạn sử dụng GCC (và Clang tôi nghĩ sao?), Nhưng sẽ là khôn ngoan khi dành thời gian để tìm cuộc gọi đến CLZ thay vì dán tất cả các tùy chọn xung quanh.
x > UINT32_MAX
và không phân nhánh. Ngoài ra, GCC và Clang sử dụng -mtune=generic
theo mặc định (như hầu hết các bản phân phối), vì vậy mã của bạn sẽ KHÔNG mở rộng thành lzcnt
hướng dẫn trên x86_64 - nó thực sự sẽ mở rộng sang thứ gì đó chậm hơn (một thói quen libgcc) trừ khi bạn sử dụng một cái gì đó như -march=native
. Vì vậy, đề xuất thay thế của bạn là không di động, lỗi và (thường) chậm hơn.
next = pow(2, ceil(log(x)/log(2)));
Điều này hoạt động bằng cách tìm số bạn đã tăng 2 bằng cách lấy x (lấy nhật ký của số và chia cho nhật ký của cơ sở mong muốn, xem wikipedia để biết thêm ). Sau đó làm tròn nó lên với trần để có được toàn bộ số điện gần nhất.
Đây là một phương pháp có mục đích chung hơn (tức là chậm hơn!) So với các phương thức bitwise được liên kết ở nơi khác, nhưng tốt để biết các phép toán, eh?
log(pow(2,29))/log(2)
= 29.000000000000004, vì vậy kết quả là 2 30 thay vì trả về 2 29. Tôi nghĩ đây là lý do tại sao các hàm log2 tồn tại?
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
.
Tôi nghĩ rằng điều này cũng hoạt động:
int power = 1;
while(power < x)
power*=2;
Và câu trả lời là power
.
power <<= 1
x
quá lớn (tức là không đủ bit để thể hiện sức mạnh tiếp theo của 2).
Nếu bạn đang sử dụng GCC, bạn có thể muốn xem Tối ưu hóa chức năng next_pow2 () của Lockless Inc .. Trang này mô tả cách sử dụng chức năng tích hợp builtin_clz()
(đếm số 0) và sau đó sử dụng trực tiếp x86 (ia32) hướng dẫn trình biên dịch chương trình bsr
(quét ngược bit), giống như được mô tả trong liên kết của câu trả lời khác với trang web gamedev . Mã này có thể nhanh hơn mã được mô tả trong câu trả lời trước .
Nhân tiện, nếu bạn không sử dụng hướng dẫn trình biên dịch và kiểu dữ liệu 64 bit, bạn có thể sử dụng
/**
* 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
trên Visual C ++
__builtin_ctz()
__builtin_ctz()
sẽ không hữu ích để làm tròn bất kỳ số không có 2 số nào cho tới số tiếp theo của hai số
constexpr uint64_t nextPowerOfTwo64 (uint64_t x) { return 1ULL<<(sizeof(uint64_t) * 8 - __builtin_clzll(x)); }
Một lần nữa, mặc dù tôi sử dụng chu trình, nhưng thi nhanh hơn nhiều so với toán hạng toán học
sức mạnh của hai tùy chọn "sàn":
int power = 1;
while (x >>= 1) power <<= 1;
sức mạnh của hai tùy chọn "trần":
int power = 2;
x--; // <<-- UPDATED
while (x >>= 1) power <<= 1;
CẬP NHẬT
Như đã đề cập trong các bình luận, đã có sai lầm trong ceil
đó kết quả của nó là sai.
Dưới đây là các chức năng đầy đủ:
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
là công suất của 2. Một micro để kiểm tra nếu đầu vào là công suất 2 là cần thiết. #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
không đúng Ví dụ: khi x = 2
kết quả nên 2
thay vì4
Đối với bất kỳ loại không dấu nào, hãy xây dựng trên 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;
}
Thực sự không có một vòng lặp ở đó vì trình biên dịch biết tại thời điểm biên dịch số lần lặp.
std::is_unsigned<UnsignedType>::value
khẳng định.
Đối với phao nổi của IEEE, bạn có thể làm một cái gì đó như thế này.
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));
}
Nếu bạn cần một giải pháp số nguyên và bạn có thể sử dụng lắp ráp nội tuyến, BSR sẽ cung cấp cho bạn log2 của một số nguyên trên x86. Nó đếm có bao nhiêu bit phải được đặt, chính xác bằng log2 của số đó. Các bộ xử lý khác có các hướng dẫn tương tự (thường), chẳng hạn như CLZ và tùy thuộc vào trình biên dịch của bạn, có thể có sẵn một bản chất để thực hiện công việc cho bạn.
Mặc dù câu hỏi được gắn thẻ như c
ở đây năm xu của tôi. May mắn cho chúng tôi, C ++ 20 sẽ bao gồm std::ceil2
và std::floor2
(xem tại đây ). Đây là các consexpr
hàm mẫu, triển khai GCC hiện tại sử dụng bẻ khóa và hoạt động với bất kỳ loại không dấu tích phân nào.
bit_ceil
open-std.org/JTC1/SC22/WG21/docs/ con / 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
}
Nếu bạn không muốn mạo hiểm vào vương quốc của hành vi không xác định, giá trị đầu vào phải nằm trong khoảng từ 1 đến 2 ^ 63. Macro cũng hữu ích để đặt hằng số tại thời gian biên dịch.
Để hoàn thiện ở đây là một triển khai dấu phẩy động trong tiêu chuẩn 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
nhanh hơn khoảng 25 lần.
Một giải pháp cụ thể hiệu quả của Microsoft (ví dụ: Visual Studio 2017) trong C / C ++ cho đầu vào số nguyên. Xử lý trường hợp đầu vào khớp chính xác với công suất hai giá trị bằng cách giảm trước khi kiểm tra vị trí của 1 bit quan trọng nhất.
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
Điều này tạo ra 5 hoặc nhiều hướng dẫn nội tuyến cho bộ xử lý Intel tương tự như sau:
dec eax
bsr rcx, rax
inc ecx
mov eax, 1
shl rax, cl
Rõ ràng trình biên dịch Visual Studio C ++ không được mã hóa để tối ưu hóa điều này cho các giá trị thời gian biên dịch, nhưng nó không giống như có rất nhiều hướng dẫn ở đó.
Biên tập:
Nếu bạn muốn giá trị đầu vào là 1 để mang lại 1 (2 cho công suất zeroth), một sửa đổi nhỏ cho mã trên vẫn tạo ra thông qua các hướng dẫn không có chi nhánh.
inline unsigned int ExpandToPowerOf2(unsigned int Value)
{
unsigned long Index;
_BitScanReverse(&Index, --Value);
if (Value == 0)
Index = (unsigned long) -1;
return (1U << (Index + 1));
}
Tạo ra chỉ một vài hướng dẫn. Thủ thuật là Index có thể được thay thế bằng một bài kiểm tra theo hướng dẫn cmove.
Trong x86, bạn có thể sử dụng các hướng dẫn thao tác bit sse4 để làm cho nó nhanh.
//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
Trong c bạn có thể sử dụng nội tại phù hợp.
Đây là giải pháp của tôi trong C. Hy vọng điều này sẽ giúp!
int next_power_of_two(int n) {
int i = 0;
for (--n; n > 0; n >>= 1) {
i++;
}
return 1 << i;
}
Nhiều kiến trúc bộ xử lý hỗ trợ log base 2
hoặc hoạt động rất giống nhau - count leading zeros
. Nhiều trình biên dịch có nội tại cho nó. Xem https://en.wikipedia.org/wiki/Find_first_set
Giả sử bạn có một trình biên dịch tốt và nó có thể thực hiện thao tác xoay vòng một chút trước khi đưa nó lên trên tôi vào thời điểm này, nhưng dù sao thì điều này vẫn hoạt động !!!
// 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)))
Mã kiểm tra dưới đây:
#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);
}
Đầu ra:
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
Tôi đang cố gắng để có được sức mạnh thấp hơn gần nhất là 2 và thực hiện chức năng này. Có thể nó giúp bạn. Chỉ cần nhân số thấp nhất gần nhất lần 2 để có được sức mạnh trên gần nhất là 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;
}
Câu trả lời của Paul Dixon cho Excel, điều này hoạt động hoàn hảo.
=POWER(2,CEILING.MATH(LOG(A1)/LOG(2)))
Một biến thể của câu trả lời @YannDroneaud hợp lệ cho x==1
, chỉ dành cho dạng tấm x86, trình biên dịch, gcc hoặc 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);
}
Đây là những gì tôi đang sử dụng để có một biểu thức không đổi, nếu đầu vào là một biểu thức không đổi.
#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 */
Vì vậy, ví dụ, một biểu thức như:
uptopow2(sizeof (struct foo))
sẽ giảm độc đáo đến một hằng số.
Chuyển đổi nó thành một float và sau đó sử dụng .hex () để hiển thị biểu diễn IEEE được chuẩn hóa.
>>> float(789).hex()
'0x1.8a80000000000p+9'
Sau đó, chỉ cần giải nén số mũ và thêm 1.
>>> int(float(789).hex().split('p+')[1]) + 1
10
Và nâng 2 lên sức mạnh này.
>>> 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
Nếu bạn cần nó cho những thứ liên quan đến 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;
}
}
Nếu bạn muốn một mẫu một dòng. Nó đây rồi
int nxt_po2(int n) { return 1 + (n|=(n|=(n|=(n|=(n|=(n-=1)>>1)>>2)>>4)>>8)>>16); }
hoặc là
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
nhiều lần mà không có điểm thứ tự là không hợp lệ. Bạn đã viết nó như thể n-=1
sẽ xảy ra đầu tiên nhưng đảm bảo duy nhất ở đây là n
có chứa giá trị mới của nó sau ;
và dấu ngoặc đơn không thay đổi điều đó.