비트 값이 1 인 위치 (32 비트 정수의 경우 0에서 31까지)가 연속 영역을 형성하는지 테스트해야합니다. 예를 들면 :
00111111000000000000000000000000 is contiguous
00111111000000000000000011000000 is not contiguous
이 테스트, 즉 일부 기능 has_contiguous_one_bits(int)
이 이식 가능하기를 바랍니다.
한 가지 분명한 방법은 위치를 반복하여 첫 번째 설정 비트를 찾은 다음 첫 번째 설정되지 않은 비트를 찾고 더 많은 설정 비트를 확인하는 것입니다.
더 빠른 방법이 있는지 궁금합니다. 최고 및 최저 세트 비트를 찾는 빠른 방법이있는 경우 (하지만 이 질문 에서 휴대용 비트 가없는 것으로 보입니다) 가능한 구현은 다음과 같습니다.
bool has_contiguous_one_bits(int val)
{
auto h = highest_set_bit(val);
auto l = lowest_set_bit(val);
return val == (((1 << (h-l+1))-1)<<l);
}
재미를 위해 연속 비트가있는 처음 100 개의 정수가 있습니다.
0 1 2 3 4 6 7 8 12 14 15 16 24 28 30 31 32 48 56 60 62 63 64 96 112 120 124 126 127 128 192 224 240 248 252 254 255 256 384 448 480 496 504 508 510 511 512 768 896 960 992 1008 1016 1020 1022 1023 1024 1536 1792 1920 1984 2016 2032 2040 2044 2046 2047 2048 3072 3584 3840 3968 4032 4064 4080 4088 4092 4094 4095 4096 6144 7168 7680 7936 8064 8128 8160 8176 8184 8188 8190 8191 8192 12288 14336 15360 15872 16128 16256 16320
그들은 (물론) (1<<m)*(1<<n-1)
음수가 아닌 형식 m
이며 n
.
답변
static _Bool IsCompact(unsigned x)
{
return (x & x + (x & -x)) == 0;
}
간단히:
x & -x
가장 낮은 비트 세트를 제공합니다 x
(또는 0 인 경우 x
0).
x + (x & -x)
연속 된 1의 가장 낮은 문자열을 단일 1로 변환합니다 (또는 0으로 래핑).
x & x + (x & -x)
그 1 비트를 지 웁니다.
(x & x + (x & -x)) == 0
다른 1 비트가 남아 있는지 테스트합니다.
더 길게:
-x
~x+1
우리가 가정하는 2의 보수를 사용하여 같음 . 비트가 뒤집힌 후 ~x
1을 더하면 하위 1 비트 ~x
와 첫 번째 0 비트가 뒤집힌 다음 중지됩니다. 따라서 -x
처음 1 개까지 의 하위 비트는의 하위 비트 와 동일 x
하지만 상위 비트는 모두 뒤집 힙니다. (예 : ~10011100
제공 01100011
하고 1을 더하면는 제공 01100100
되므로 낮은 100
값은 동일하지만 높은 값 10011
은으로 반전됩니다 01100
.) 그런 다음 x & -x
둘 다에서 1 인 유일한 비트, 즉 가장 낮은 1 비트 ( 00000100
)를 제공합니다. ( x
0이면 x & -x
0입니다.)
이 값을 추가 x
하면 연속 된 1을 모두 통과하여 0으로 변경합니다. 다음으로 높은 0 비트에 1이 남게됩니다 (또는 하이 엔드를 통과하여 래핑 된 총 0이 남음) (10100000
.)
이것이와 AND 처리되면 x
1이 0으로 변경된 위치 (및 캐리가 0에서 1로 변경된 위치)에 0이 있습니다. 따라서 결과는 1 비트가 더 높은 경우에만 0이 아닙니다.
답변
실제로 내장 함수를 사용할 필요가 없습니다.
먼저 첫 번째 1 앞에있는 모든 0을 뒤집습니다. 그런 다음 새 값이 메르 센 숫자인지 테스트합니다. 이 알고리즘에서 0은 true로 매핑됩니다.
bool has_compact_bits( unsigned const x )
{
// fill up the low order zeroes
unsigned const y = x | ( x - 1 );
// test if the 1's is one solid block
return not ( y & ( y + 1 ) );
}
물론 내장 함수를 사용하려는 경우 다음은 popcount 메서드입니다.
bool has_compact_bits( unsigned const x )
{
size_t const num_bits = CHAR_BIT * sizeof(unsigned);
size_t const sum = __builtin_ctz(x) + __builtin_popcount(x) + __builtin_clz(z);
return sum == num_bits;
}
답변
실제로 선행 0을 계산할 필요가 없습니다. 주석에서 pmg가 제안한대로 찾고있는 숫자가 시퀀스 OEIS A023758 의 숫자라는 사실을 이용하여 즉, i> = j 인 2 ^ i-2 ^ j 형식의 숫자를 이용하면 후행 0을 계산할 수 있습니다 ( 즉, j-1 ), 원래 값에서 해당 비트를 토글 ( 2 ^ j-1 을 더하는 것과 동일 ) 한 다음 해당 값이 2 ^ i-1 형식인지 확인합니다 . GCC / clang 내장 함수를 사용하면
bool has_compact_bits(int val) {
if (val == 0) return true; // __builtin_ctz undefined if argument is zero
int j = __builtin_ctz(val) + 1;
val |= (1 << j) - 1; // add 2^j - 1
val &= (val + 1); // val set to zero if of the form (2^i - 1)
return val == 0;
}
이 버전 은 KamilCuk이 제안한 버전과 팝 카운트 만있는 Yuri Feldman이 제안한 버전 보다 약간 빠릅니다 .
당신이 C ++ (20)를 사용하는 경우, 당신은 대체하여 휴대용 기능을 얻을 수 있습니다 __builtin_ctz
으로 std::countr_zero
:
#include <bit>
bool has_compact_bits(int val) {
int j = std::countr_zero(static_cast<unsigned>(val)) + 1; // ugly cast
val |= (1 << j) - 1; // add 2^j - 1
val &= (val + 1); // val set to zero if of the form (2^i - 1)
return val == 0;
}
캐스트는 추악하지만 비트를 조작 할 때 부호없는 유형으로 작업하는 것이 더 낫다는 경고입니다. Pre-C ++ 20 대안은 다음과 같습니다.boost::multiprecision::lsb
.
편집하다:
취소 선 링크에 대한 벤치 마크는 Yuri Feldman 버전에 대해 popcount 명령이 생성되지 않았기 때문에 제한되었습니다. 를 사용하여 내 PC에서 컴파일하려고 시도 -march=westmere
하면서 동일한 시퀀스로 10 억 번의 반복에 대해 다음 시간을 측정했습니다 std::mt19937
.
- 현재 버전 : 5.7 초
- KamilCuk의 두 번째 버전 : 4.7 초
- 내 버전 : 4.7 초
- Eric Postpischil의 첫 번째 버전 : 4.3 초
- Yuri Feldman의 버전 (명시 적으로 사용
__builtin_popcount
) : 4.1 초
따라서 적어도 내 아키텍처에서 가장 빠른 것은 popcount가있는 것 같습니다.
편집 2 :
새로운 Eric Postpischil 버전으로 벤치 마크를 업데이트했습니다. 의견에서 요청했듯이 내 테스트 코드는 여기 에서 찾을 수 있습니다 . PRNG에 필요한 시간을 추정하기 위해 no-op 루프를 추가했습니다. KevinZ의 두 가지 버전도 추가했습니다. 코드가 clang에서 컴파일 -O3 -msse4 -mbmi
되어 가져 오기 popcnt
및 blsi
지시가 가능합니다 (Peter Cordes 덕분에).
결과 : 적어도 내 아키텍처에서 Eric Postpischil의 버전은 Yuri Feldman의 버전만큼 빠르며 지금까지 제안 된 다른 버전보다 두 배 이상 빠릅니다.
답변
빠름에 대해서는 잘 모르겠지만 확인하여 한 줄로 할 수 있습니다. val^(val>>1)
최대 2 비트가 켜져 .
이것은 부호없는 유형에서만 작동합니다 0
. 부호 비트의 복사본에서 이동하는 산술 오른쪽 이동이 아니라 맨 위에서 이동 (논리 이동)이 필요합니다.
#include <bitset>
bool has_compact_bits(unsigned val)
{
return std::bitset<8*sizeof(val)>((val ^ (val>>1))).count() <= 2;
}
거부하려면 0
(즉, 정확히 1 개의 연속 비트 그룹이있는 입력 만 허용), 논리적 AND는 val
0이 아닙니다. 이 질문에 대한 다른 답변 0
은 간결한 것으로 받아들 입니다.
bool has_compact_bits(unsigned val)
{
return std::bitset<8*sizeof(val)>((val ^ (val>>1))).count() <= 2 and val;
}
C ++ 이식 노출하는 popcount를 통해 std::bitset::count()
, 또는 를 통해 20 C ++로std::popcount
. C는 여전히 사용 가능한 대상에서 popcnt 또는 유사한 명령어로 안정적으로 컴파일하는 이식 가능한 방법이 없습니다.
답변
CPU에는 매우 빠른 전용 명령이 있습니다. PC에서는 BSR / BSF (1985 년 80386에 도입 됨)이고 ARM에서는 CLZ / CTZ입니다.
하나를 사용하여 최하위 세트 비트의 인덱스를 찾고 그 양만큼 정수를 오른쪽으로 이동하십시오. 다른 하나를 사용하여 최상위 세트 비트의 인덱스를 찾고 정수를 (1u << (bsr + 1))-1과 비교하십시오.
불행히도 35 년은 하드웨어에 맞도록 C ++ 언어를 업데이트하기에 충분하지 않았습니다. C ++에서 이러한 명령어를 사용하려면 내장 함수가 필요하며 이식 가능하지 않으며 약간 다른 형식으로 결과를 반환합니다. 전 처리기 #ifdef
등을 사용하여 컴파일러를 감지 한 다음 적절한 내장 함수를 사용합니다. 그들이 MSVC에서 _BitScanForward
, _BitScanForward64
, _BitScanReverse
, _BitScanReverse64
. GCC 및 clang에서는 __builtin_clz
및 __builtin_ctz
.
답변
1 대신 0과 비교하면 일부 작업이 절약됩니다.
bool has_compact_bits2(int val) {
if (val == 0) return true;
int h = __builtin_clz(val);
// Clear bits to the left
val = (unsigned)val << h;
int l = __builtin_ctz(val);
// Invert
// >>l - Clear bits to the right
return (~(unsigned)val)>>l == 0;
}
다음 gcc10 -O3
은 x86_64 에서 위의 것보다 적은 하나의 명령어를 생성하고 on sign 확장을 사용합니다.
bool has_compact_bits3(int val) {
if (val == 0) return true;
int h = __builtin_clz(val);
val <<= h;
int l = __builtin_ctz(val);
return ~(val>>l) == 0;
}
godbolt에서 테스트되었습니다 .
답변
요구 사항을 바꿀 수 있습니다.
- N을 이전 비트 수와 다른 비트 수로 설정 (비트를 반복하여)
- N = 2이고 첫 번째 또는 마지막 비트가 0이면 대답은 예입니다.
- N = 1이면 대답은 예입니다 (모든 1이 한쪽에 있기 때문에)
- N = 0이고 어떤 비트가 0이면 1이없는 것입니다. 답을 예 또는 아니오라고 생각하면 귀하에게 달렸습니다.
- 기타 : 대답은 ‘아니오’입니다.
모든 비트를 살펴보면 다음과 같이 보일 수 있습니다.
unsigned int count_bit_changes (uint32_t value) {
unsigned int bit;
unsigned int changes = 0;
uint32_t last_bit = value & 1;
for (bit = 1; bit < 32; bit++) {
value = value >> 1;
if (value & 1 != last_bit {
changes++;
last_bit = value & 1;
}
}
return changes;
}
그러나 이것은 확실히 최적화 될 수 있습니다 (예를 들어 , 값이 1 인 더 이상 중요한 비트가 존재하지 않음을 의미하는 도달 하면 for
루프 를 중단함으로써 ).value
0