`stdc_leading_zeros` — 최상위 비트부터 연속된 0을 세는 함수

stdc_leading_zeros — 최상위 비트부터 연속된 0을 세는 함수 (C23)

"이 정수를 이진수로 봤을 때, 맨 앞에 0이 몇 개나 붙어 있지?" 같은 질문을 할 때가 있어요. 예를 들어 최상위 1 비트의 위치를 알아야 할 때가 대표적이죠. C23의 stdc_leading_zeros가 이걸 타입 제네릭으로 계산해 줍니다.

출처: cppreference — stdc_leading_zeros

본문

stdc_leading_zerosvalue 안에서 최상위 비트(MSB)부터 시작해 연속된 0 비트의 개수를 돌려줘요.

문법

<stdbit.h>(C23)에는 실제로는 타입별 함수 다섯 개와, 그걸 자동으로 골라 주는 타입 제네릭 매크로가 정의돼 있어요.

#include <stdbit.h>

unsigned int stdc_leading_zeros_uc ( unsigned char value )        [[unsequenced]];
unsigned int stdc_leading_zeros_us ( unsigned short value )       [[unsequenced]];
unsigned int stdc_leading_zeros_ui ( unsigned int value )         [[unsequenced]];
unsigned int stdc_leading_zeros_ul ( unsigned long int value )    [[unsequenced]];
unsigned int stdc_leading_zeros_ull( unsigned long long int value )[[unsequenced]];

#define stdc_leading_zeros(value)   /* type-generic macro */

설명

타입 제네릭 매크로(6)는 입력 value의 타입이 다음 중 하나라면 그에 맞는 결과를 돌려줘요.

  • bool을 제외한 표준 부호 없는 정수 타입
  • 확장 부호 없는 정수 타입
  • 표준 또는 확장 정수 타입과 폭이 일치하는 비트 정밀(bit-precise) 부호 없는 정수 타입(bool 제외)

반환 타입은 계산 결과를 표현할 수 있을 만큼 충분히 큰 적절한 부호 없는 정수 타입이에요.

매개변수

매개변수 설명
value 부호 없는 정수 타입의 값

반환값

value 안에서 최상위 비트부터 시작한, 연속된 0 비트의 개수.

예시

uint8_tuint16_t에 대해 값을 오른쪽으로 쉬프트하며 leading zeros가 어떻게 변하는지 보여 주는 예시예요.

#include <limits.h>
#include <stdbit.h>
#include <stdint.h>
#include <stdio.h>

#define bits_num(value) (sizeof(value) * CHAR_BIT)

#define bin_impl(T, suffix) \
const char* bin_##suffix(T x) \
{ \
    static char buf[bits_num(x) * CHAR_BIT + 1]; \
    for (T i = 0, mask = ((T)1 << (bits_num(x) - 1)); mask; mask >>= 1) \
        buf[i++] = x & mask ? '1' : '0'; \
    buf[bits_num(x)] = '\0'; \
    return buf; \
}

bin_impl(uint8_t,  u8)
bin_impl(uint16_t, u16)
bin_impl(uint32_t, u32)
bin_impl(uint64_t, u64)

#define bin(x) _Generic((x), \
    uint8_t: bin_u8, uint16_t: bin_u16, uint32_t: bin_u32, default: bin_u64)(x)

int main()
{
    puts("uint8_t:");
    for (uint8_t x = 0b11000000; ; x >>= 1)
    {
        printf("x = [%s], leading zeros: %d\n", bin(x), stdc_leading_zeros(x));
        if (!x)
            break;
    }

    puts("uint16_t:");
    for (uint16_t x = 0b11000000; ; x >>= 1)
    {
        printf("x = [%s], leading zeros: %d\n", bin(x), stdc_leading_zeros(x));
        if (!x)
            break;
    }
}

출력:

uint8_t:
x = [11000000], leading zeros: 0
x = [01100000], leading zeros: 1
x = [00110000], leading zeros: 2
x = [00011000], leading zeros: 3
x = [00001100], leading zeros: 4
x = [00000110], leading zeros: 5
x = [00000011], leading zeros: 6
x = [00000001], leading zeros: 7
x = [00000000], leading zeros: 8
uint16_t:
x = [0000000011000000], leading zeros: 8
x = [0000000001100000], leading zeros: 9
x = [0000000000110000], leading zeros: 10
x = [0000000000011000], leading zeros: 11
x = [0000000000001100], leading zeros: 12
x = [0000000000000110], leading zeros: 13
x = [0000000000000011], leading zeros: 14
x = [0000000000000001], leading zeros: 15
x = [0000000000000000], leading zeros: 16

0b11000000(8비트)에서는 leading zeros가 0이지만, 같은 값을 uint16_t로 보면 앞에 8비트의 0이 더 붙어 있어서 8부터 시작하는 걸 볼 수 있어요. 타입의 전체 폭을 기준으로 센다는 뜻이죠.

더 알아보기

  • stdc_first_leading_zero — 최상위 비트부터 첫 0 비트의 위치를 찾아요 (C23).
  • stdc_count_zeros — 부호 없는 정수 안의 0 비트 개수를 세요 (C23).
  • stdc_leading_ones — 최상위 비트부터 연속된 1 비트를 세요 (C23).
  • cppreference의 stdc_leading_zeros 원문에서 표준 항목별 세부 규칙을 더 확인할 수 있어요.