Operacja przesunięcia cyklicznego (rotary/circular shift) to przesunięcie bitów liczby o określoną liczbę pozycji z przeniesieniem "wypadających" bitów na przeciwny koniec. W języku C nie ma wbudowanego operatora do tej operacji; to rozwiązanie ma historyczne powiązania z przenośnością standardowej biblioteki i koniecznością wyraźnego określenia zachowania dla różnych platform.
Problem polega na tym, że standardowe operatory przesunięcia (<<, >>) nie wykonują przesunięcia cyklicznego: po prostu przesuwają bity, a "wypadające" zastępują zerami. Aby wykonać przesunięcie cykliczne, trzeba wyraźnie połączyć wyniki dwóch przesunięć i zamaskować wynik dla określonej liczby bitów.
Rozwiązanie — ręcznie zaimplementować przesunięcie cykliczne. Dla 32-bitowej liczby unsigned wygląda to tak:
uint32_t rotate_left(uint32_t value, unsigned int shift) { return (value << shift) | (value >> (32 - shift)); } uint32_t rotate_right(uint32_t value, unsigned int shift) { return (value >> shift) | (value << (32 - shift)); }
Kluczowe cechy:
Czy operator << lub >> może zaimplementować przesunięcie cykliczne bez dodatkowych operacji?
Nie. Zwykłe przesunięcia zastępują przekroczone bity zerami, a nie przenoszą ich na drugą stronę.
Co się stanie, jeśli wykona się przesunięcie na rozmiar typu (shift równy liczbie bitów w liczbie)?
Zachowanie jest niezdefiniowane (Undefined Behavior według standardu C), trzeba koniecznie wykonać przesunięcie modulo rozmiaru typu.
Czy dla typów signed jest bezpiecznie wykonywać rotary shift przy użyciu tych funkcji?
Nie, zawsze używaj typów unsigned, ponieważ przesunięcia bitowe dla typów signed działają różnie w zależności od kompilatora i architektury.
Użycie zwykłego przesunięcia do przesunięcia cyklicznego:
uint32_t x = 0xFA3C0F00; uint32_t y = x << 5; // Nie ma charakteru cyklicznego
Zalety:
Wady:
Użycie ręcznie napisanej funkcji rotary shift z uwzględnieniem rozmiaru typu i zabezpieczeniem przed UB:
uint32_t x = 0xFA3C0F00; uint32_t y = rotate_left(x, 5);
Zalety:
Wady: