Czy operatory zmiany ( < < ,) są arytmetyczne czy logiczne w C?

W C, są operatorami zmiany (<<, >>) arytmetyka czy logika?

11 answers

Zgodnie z K & R 2nd edition wyniki są zależne od implementacji dla prawidłowych przesunięć podpisanych wartości.

Wikipedia mówi, że C / C++ "Zwykle" implementuje przesunięcie arytmetyczne na podpisanych wartościach.

Zasadniczo musisz albo przetestować swój kompilator, albo nie polegać na nim. Moja pomoc VS2008 dla obecnego kompilatora MS C++ mówi, że ich kompilator wykonuje przesunięcie arytmetyczne.

 80
Author: Ronnie,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-10-09 17:43:54

Gdy przesunięcie w lewo, nie ma różnicy między przesunięciem arytmetycznym i logicznym. Przy przesunięciu w prawo, rodzaj przesunięcia zależy od rodzaju przesuniętej wartości.

(jako tło dla tych czytelników, którzy nie znają tej różnicy, "logiczne" przesunięcie w prawo o 1 bit przesuwa wszystkie bity w prawo i wypełnia lewy bit z 0. Przesunięcie "arytmetyczne" pozostawia oryginalną wartość w lewym bitie. Różnica staje się istotna przy radzeniu sobie z negatywnymi liczby.)

W przypadku przesunięcia niepodpisanej wartości operator >> w C jest przesunięciem logicznym. W przypadku przesunięcia podpisanej wartości operator > > jest przesunięciem arytmetycznym.

Na przykład, zakładając maszynę 32-bitową:

signed int x1 = 5;
assert((x1 >> 1) == 2);
signed int x2 = -5;
assert((x2 >> 1) == -3);
unsigned int x3 = (unsigned int)-5;
assert((x3 >> 1) == 0x7FFFFFFD);
 115
Author: Greg Hewgill,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-11 09:16:34

TL;DR

Niech i I n będą odpowiednio operandami lewego i prawego operatora przesunięcia; typ i, Po promocji liczby całkowitej, będzie T. Zakładając, że {[3] } będzie w [0, sizeof(i) * CHAR_BIT) - niezdefiniowany inaczej-mamy takie przypadki:

| Direction  |   Type   | Value (i) | Result                   |
| ---------- | -------- | --------- | ------------------------ |
| Right (>>) | unsigned |    ≥ 0    | −∞ ← (i ÷ 2ⁿ)            |
| Right      | signed   |    ≥ 0    | −∞ ← (i ÷ 2ⁿ)            |
| Right      | signed   |    < 0    | Implementation-defined†  |
| Left  (<<) | unsigned |    ≥ 0    | (i * 2ⁿ) % (T_MAX + 1)   |
| Left       | signed   |    ≥ 0    | (i * 2ⁿ) ‡               |
| Left       | signed   |    < 0    | Undefined                |

† większość kompilatorów implementuje to jako przesunięcie arytmetyczne
‡ undefined if value overflows the result type t; promoted type of I


Przesunięcie

Pierwsza to różnica między logiczne i arytmetyczne przesunięcia z matematycznego punktu widzenia, bez martwienia się o rozmiar typu danych. Przesunięcia logiczne zawsze wypełniają odrzucone bity zerami, podczas gdy przesunięcie arytmetyczne wypełnia je zerami tylko dla lewego przesunięcia, ale dla prawego przesunięcia kopiuje MSB, zachowując w ten sposób znak operandu (zakładając kodowanie dopełniacza dwójki Dla wartości ujemnych).

Innymi słowy, logiczne przesunięcie patrzy na przesunięty operand jako tylko strumień bitów i przesuwa je, bez zawracanie głowy znakiem wynikowej wartości. Przesunięcie arytmetyczne wygląda na liczbę (podpisaną) i zachowuje znak podczas dokonywania przesunięć.

Lewe przesunięcie arytmetyczne liczby X przez n jest równoważne mnożeniu X przez 2 n i dlatego jest równoważne logicznemu przesunięciu w lewo; logiczne przesunięcie również dałoby ten sam wynik, ponieważ MSB i tak odpada od końca i nie ma nic do zachowania.

Prawe przesunięcie arytmetyczne liczby X przez n jest równoważne liczbie całkowitej dzielenie X przez 2 n tylko wtedy, gdy X jest nieujemne! Dzielenie liczb całkowitych jest niczym innym jak dzieleniem matematycznym i okrągłym w kierunku 0 (trunc).

Dla liczb ujemnych, reprezentowanych przez kodowanie dopełniacza dwójki, przesunięcie w prawo O N bitów ma efekt matematycznego dzielenia go przez 2 n i zaokrąglania w kierunku - ∞ ( floor ); zatem przesunięcie w prawo różni się dla wartości nieujemnych i ujemnych.

Dla X ≥ 0, X > > n = X / 2 n = trunc(x ÷ 2 n)

Dla X > n = floor(x ÷ 2 n )

Gdzie ÷ jest podziałem matematycznym, / jest podziałem całkowitym. Spójrzmy na przykład:

37)10 = 100101)2

37 ÷ 2 = 18.5

37 / 2 = 18 (zaokrąglenie 18,5 w kierunku 0) = 10010)2 [wynik arytmetycznego przesunięcia w prawo]

-37)10 = 11011011)2 (biorąc pod uwagę dopełniacz dwójki, reprezentacja 8-bitowa)

-37 ÷ 2 = -18.5

-37 / 2 = -18 (zaokrąglenie 18,5 w kierunku 0) = 11101110)2 [Nie wynik arytmetycznego przesunięcia w prawo]

-37 >> 1 = -19 (zaokrąglenie 18,5 w kierunku -∞) = 11101101)2 [wynik arytmetycznego przesunięcia w prawo]

Jak zauważył Guy Steele , ta rozbieżność doprowadziła do błędów w więcej niż jednym kompilatorze. Tutaj nieujemne (matematyczne) można odwzorować na niepodpisane i podpisane wartości nieujemne (C); obie są traktowane tak samo, a przesunięcie ich w prawo odbywa się przez dzielenie liczb całkowitych.

Tak więc logiczne i arytmetyczne są równoważne w przesunięciu w lewo i dla wartości nieujemnych w przesunięciu w prawo; to w przesunięciu w prawo wartości ujemnych różnią się.

Typy Operand i result

Standard C99 §6.5.7:

Każdy z operandów ma typy całkowite.

The integer promotions są wykonywane na każdym z operandów. Typ wyniku to wynik promowanego lewego operandu. Jeśli wartość prawego operandu jest ujemna lub jest większa lub równa szerokości promowanego lewego operandu, zachowanie jest niezdefiniowane.

short E1 = 1, E2 = 3;
int R = E1 << E2;

W powyższym fragmencie oba operandy stają się int (ze względu na promocję liczby całkowitej); jeśli E2 było ujemne lub E2 ≥ sizeof(int) * CHAR_BIT, operacja jest niezdefiniowana. Dzieje się tak, ponieważ przesunięcie więcej niż dostępne bity z pewnością przepełni się. Jeśli R zostanie zadeklarowana jako short, wynik int operacji przesunięcia zostanie w domyśle przekonwertowany na short; konwersja zawężająca, która może prowadzić do zachowania zdefiniowanego przez implementację, jeśli wartość nie jest reprezentowalna w typie docelowym.

Left Shift

Wynik E1 E2 , pomniejszona modulo o jeden więcej niż maksymalna wartość reprezentowana w typie wynikowym. Jeśli E1 ma znakowany typ i wartość nieujemną, a E1×2 E2 {[26] } jest reprezentowalny w typie wynikowym, to jest to wartość wynikowa; w przeciwnym razie zachowanie jest niezdefiniowane.

Ponieważ przesunięcia lewe są takie same dla obu, puste bity są po prostu wypełnione zerami. Następnie stwierdza, że zarówno dla typów niepodpisanych, jak i podpisanych jest to przesunięcie arytmetyczne. Interpretuję to jako przesunięcie arytmetyczne, ponieważ przesunięcia logiczne nie przejmują się wartość reprezentowana przez bity, po prostu patrzy na nią jako strumień bitów; ale standard mówi Nie w kategoriach bitów, ale definiując ją w kategoriach wartości otrzymanej przez iloczyn E1 z 2 E2.

Zastrzeżenie polega na tym, że dla typów podpisanych wartość powinna być nieujemna, a wartość wynikowa powinna być reprezentowalna w typie wynikowym. W przeciwnym razie operacja jest niezdefiniowana. typem wyniku będzie Typ E1 po zastosowaniu promocji Całkowej a nie Typ destination (zmienna, która będzie zawierać wynik). Wartość wynikowa jest domyślnie przekonwertowana na typ docelowy; jeśli nie jest reprezentowalna w tym typie, wtedy konwersja jest zdefiniowana w implementacji (C99 §6.3.1.3/3).

Jeśli E1 jest typem podpisanym z wartością ujemną, to zachowanie przesunięcia w lewo jest niezdefiniowane. jest to łatwa droga do nieokreślonego zachowania, które można łatwo przeoczyć.

Przesunięcie W Prawo

Wynik E1 > > E2 to E1 przesunięte w prawo pozycje bitowe E2. Jeśli E1 ma typ niepodpisany lub jeśli E1 ma typ podpisany i wartość nieujemną, wartość wyniku jest integralną częścią ilorazu E1 / 2 E2. Jeśli E1 ma typ podpisany i wartość ujemną, wartość wynikowa jest zdefiniowana w implementacji.

Przesunięcie w prawo dla niepodpisanych i podpisanych wartości nieujemnych jest dość proste; puste bity są wypełnione zerami. dla podpisanego negatywu wartości wynik WŁAŚCIWEGO przesunięcia jest zdefiniowany w implementacji.Większość implementacji, takich jak GCC i Visual C++, implementuje przesunięcie w prawo jako przesunięcie arytmetyczne, zachowując bit znaku.

Podsumowanie

W Przeciwieństwie Do Javy, która ma specjalny operator >>> do logicznego przesunięcia poza zwykłymi >> i <<, C i C++ mają tylko przesunięcie arytmetyczne z niektórymi obszarami, które pozostały nieokreślone i zdefiniowane w implementacji. Powód, dla którego uważam je za arytmetykę ze względu na standardowe sformułowanie operacja matematycznie zamiast traktować przesunięty operand jako strumień bitów; być może jest to powód, dla którego pozostawia te obszary un / implementation-zdefiniowane zamiast po prostu definiowanie wszystkich przypadków jako przesunięcia logiczne.

 40
Author: legends2k,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-09 17:35:51

W kategoriach rodzaju przesunięcia, które otrzymujesz, ważną rzeczą jest rodzaj wartości, którą zmieniasz. Klasycznym źródłem błędów jest przesunięcie literału na, powiedzmy, maskowanie bitów. Na przykład, jeśli chcesz upuścić najbardziej lewy bit niepodpisanej liczby całkowitej, możesz spróbować użyć tego jako maski:

~0 >> 1

Niestety, wpędzi cię to w kłopoty, ponieważ maska będzie miała ustawione wszystkie swoje bity, ponieważ przesunięta wartość (~0) jest podpisana, więc przesunięcie arytmetyczne jest wystąpili Zamiast tego chcesz wymusić logiczne przesunięcie, jawnie deklarując wartość jako niepodpisaną, tzn. wykonując coś takiego:

~0U >> 1;
 16
Author: Nick,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-13 17:20:06

Oto funkcje gwarantujące logiczne przesunięcie w prawo i arytmetyczne przesunięcie w prawo Int w C:

int logicalRightShift(int x, int n) {
    return (unsigned)x >> n;
}
int arithmeticRightShift(int x, int n) {
    if (x < 0 && n > 0)
        return x >> n | ~(~0U >> n);
    else
        return x >> n;
}
 12
Author: John Scipione,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-08 18:28:47

Kiedy - lewy shift przez 1 mnożysz przez 2 - przesunięcie w prawo o 1 dzielisz przez 2

 x = 5
 x >> 1
 x = 2 ( x=5/2)

 x = 5
 x << 1
 x = 10 (x=5*2)
 6
Author: Srikant Patnaik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-06 06:09:47

Cóż, sprawdziłem to na Wikipedii i mają to do powiedzenia:

C ma jednak tylko jedno prawe przesunięcie operator, > > Wiele kompilatorów C wybiera które prawe przesunięcie wykonać w zależności na jaki typ liczby całkowitej jest przesunięte; często podpisane liczby całkowite są przesunięcie za pomocą przesunięcia arytmetycznego, i niepodpisane liczby całkowite są przesuwane używając logicznego przesunięcia.

Więc wygląda na to, że to zależy od Twojego kompilatora. Również w tym artykule zwróć uwagę, że lewy shift jest to samo dla arytmetyki i logiki. Polecam zrobić prosty test z kilkoma podpisanymi i niepodpisanymi liczbami na granicy (oczywiście zestaw wysokich bitów) i zobaczyć, jaki jest wynik na Twoim kompilatorze. Zalecałbym również unikanie w zależności od tego, czy jest to jedno czy drugie, ponieważ wydaje się, że C nie ma standardu, przynajmniej jeśli jest to rozsądne i możliwe, aby uniknąć takiej zależności.

 4
Author: Mike Stone,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-08-11 09:18:52

Left shift <<

Jest to w jakiś sposób łatwe i za każdym razem, gdy używasz operatora zmiany, zawsze jest to operacja bitowa, więc nie możemy jej używać z operacją double I float. Ilekroć zostawiamy przesunięcie o jedno zero, jest ono zawsze dodawane do najmniej znaczącego bitu (LSB).

Ale w prawym przesunięciu >> musimy przestrzegać jednej dodatkowej reguły i reguła ta nazywa się"Sign bit copy". Znaczenie "Sign bit copy" polega na tym, że jeśli ustawiony jest najbardziej znaczący bit (MSB), to po ponownym przesunięciu w prawo MSB zostanie ustawione, jeśli został zresetowany, to jest ponownie zresetowany, czyli jeśli poprzednia wartość była równa zero, to po ponownym przesunięciu bit jest równy zero, jeśli poprzedni bit był jeden, to po przesunięciu jest znowu jeden. Zasada ta nie ma zastosowania do zmiany w lewo.

Najważniejszy przykład na prawej zmianie jeśli przesuniesz dowolną liczbę ujemną na prawą zmianę, to po pewnym przesunięciu wartość w końcu osiągnie zero, a następnie po tym, jeśli przesuniesz to -1 dowolną liczbę razy wartość pozostanie taka sama. Proszę sprawdzić.

 0
Author: asifaftab87,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-26 13:44:27

Gcc zazwyczaj używa przesunięć logicznych na zmiennych niepodpisanych oraz przesunięć w lewo na zmiennych podpisanych. Prawe przesunięcie arytmetyczne jest naprawdę ważne, ponieważ podpisze zmienną.

Gcc użyje tego, gdy ma to zastosowanie, podobnie jak inne Kompilatory.

 0
Author: Cristián Romo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-26 13:44:50

GCC robi

  1. For-ve - > przesunięcie arytmetyczne

  2. For + ve - > Logical Shift

 0
Author: Alok Prasad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-02-09 14:44:45

Według wielu C kompilatorów:

  1. << jest arytmetycznym przesunięciem w lewo lub bitowym przesunięciem w lewo.
  2. >> jest arytmetycznym przesunięciem w prawo bitowo w prawo.
 -5
Author: srinath,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-26 13:45:07