Kaydırma operatörleri (<<, >>) C'de aritmetik mi yoksa mantıksal mı?


Yanıtlar:


97

Göre K & R 2 baskısında sonuçları uygulama bağımlı imzalı değerlerin doğru vardiya içindir.

Wikipedia , C / C ++ 'nın' genellikle 'işaretli değerler üzerinde aritmetik bir kayma uyguladığını söylüyor.

Temel olarak derleyicinizi test etmeniz veya ona güvenmemeniz gerekir. Mevcut MS C ++ derleyicisi için VS2008 yardımım, derleyicisinin aritmetik bir değişiklik yaptığını söylüyor.


141

Sola kaydırırken, aritmetik ve mantıksal kaydırma arasında bir fark yoktur. Sağa kaydırırken, kaydırma türü, kaydırılan değerin türüne bağlıdır.

(Farkı bilmeyen okuyucular için arka plan olarak, 1 bitlik "mantıksal" sağa kaydırma tüm bitleri sağa kaydırır ve en soldaki biti 0 ile doldurur. "Aritmetik" bir kaydırma, orijinal değeri en soldaki bitte bırakır Negatif sayılarla uğraşırken fark önemli hale gelir.)

İşaretsiz bir değeri kaydırırken, C'deki >> operatörü mantıksal bir kaydırmadır. İşaretli bir değeri kaydırırken, >> operatörü aritmetik bir kaydırmadır.

Örneğin, 32 bitlik bir makine varsayarsak:

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);

57
Çok yaklaş, Greg. Açıklamanız neredeyse mükemmel, ancak işaretli tip ve negatif değerdeki bir ifadeyi değiştirmek, uygulama tanımlıdır. ISO / IEC 9899: 1999 Bölüm 6.5.7'ye bakın.
Robᵩ

12
@Rob: Aslında, sola kaydırma ve işaretli negatif sayı için davranış tanımsızdır.
JeremyP

5
Gerçekte, eğer sonuçta ortaya çıkan matematiksel değer (bit boyutunda sınırlı değildir), o işaretli tipte pozitif bir değer olarak temsil edilemiyorsa , sola kayma aynı zamanda pozitif işaretli değerler için tanımsız davranışla sonuçlanır. Sonuç olarak, işaretli bir değeri doğru kaydırırken dikkatli olmanız gerekir.
Michael Burr

3
@supercat: Gerçekten bilmiyorum. Bununla birlikte, tanımlanmamış davranışa sahip kodun bir derleyicinin sezgisel olmayan şeyler yapmasına neden olduğu belgelenmiş durumlar olduğunu biliyorum (genellikle agresif optimizasyon nedeniyle - örneğin eski Linux TUN / TAP sürücüsü boş işaretçi hatasına bakın: lwn.net / Makaleler / 342330 ). Sağ kaydırmada işaret doldurmaya ihtiyacım olmadıkça (bunun uygulama tanımlı davranış olduğunu fark ettim), oraya ulaşmak için yayın kullanmak anlamına gelse bile, genellikle işaretsiz değerleri kullanarak bit kaydırmalarımı gerçekleştirmeye çalışırım.
Michael Burr

2
@MichaelBurr: Hipermodern derleyicilerin, C standardı tarafından tanımlanmayan davranışı ( uygulamaların % 99'unda tanımlanmış olmasına rağmen ) davranışı tümünde tam olarak tanımlanmış olan programları açmak için bir gerekçe olarak kullandığını biliyorum. yararlı bir davranışı olmayan değersiz makine talimatlarının içine çalıştırılmalarının beklenebileceği platformlar. Yine de itiraf edeceğim (alay konusu) Derleyici yazarlarının neden en büyük optimizasyon olasılığını kaçırdığına şaşırdım: eğer ulaşılırsa işlevlerin iç içe
geçmesine

51

TL; DR

Düşünün ive nbir kaydırma operatörünün sırasıyla sol ve sağ işlenen olması; itamsayı yükseltmeden sonra türü , be T. İçinde nolduğunu varsayarsak [0, sizeof(i) * CHAR_BIT)- aksi takdirde tanımsız - şu durumlara sahibiz:

| 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                |

† çoğu derleyici bunu aritmetik kaydırma olarak uygular
‡ tanımsız, eğer değer T sonuç türünün dışına taşarsa; terfi tipi i


değişken

Birincisi, veri türü boyutu hakkında endişelenmeden matematiksel bir bakış açısından mantıksal ve aritmetik kaymalar arasındaki farktır. Mantıksal kaymalar her zaman atılan bitleri sıfırlarla doldururken, aritmetik kaydırma onu yalnızca sola kaydırma için sıfırlarla doldurur, ancak sağa kaydırma için MSB'yi kopyalar ve böylece işlenenin işaretini korur ( ikinin tamamlayıcısı olduğu varsayılarak) negatif değerler için kodlamasını ).

Başka bir deyişle, mantıksal kayma, kaydırılmış işlenene sadece bir bit akışı olarak bakar ve ortaya çıkan değerin işareti hakkında endişelenmeden onları hareket ettirir. Aritmetik kaydırma, ona (işaretli) bir sayı olarak bakar ve vardiya yapılırken işareti korur.

Bir X sayısının n ile sola aritmetik kayması, X'in 2 n ile çarpılmasına eşdeğerdir ve bu nedenle mantıksal sola kaydırmaya eşdeğerdir; Mantıksal bir değişim de aynı sonucu verecektir çünkü MSB yine de sona ermektedir ve korunacak hiçbir şey yoktur.

X sayısının n'ye doğru aritmetik kayması, YALNIZCA X negatif değilse , X'in tamsayı 2 n'ye bölünmesine eşdeğerdir ! Tamsayı bölme, matematiksel bölümden başka bir şey değildir ve 0'a yuvarlanır ( kesik ).

İkinin tümleyen kodlamasıyla temsil edilen negatif sayılar için, sağa n bit ile kaydırma, onu matematiksel olarak 2 n'ye bölerek ve −∞'a ( taban ) yuvarlama etkisine sahiptir ; bu nedenle sağa kaydırma, negatif olmayan ve negatif değerler için farklıdır.

için X ≥ 0, X >> n = X / 2 n = trunc (X ÷ 2 n )

X <0 için, X >> n = kat (X ÷ 2 n )

÷matematiksel bölme nerede , /tamsayı bölmedir. Bir örneğe bakalım:

37) 10 = 100101) 2

37 ÷ 2 = 18.5

37/2 = 18 (18,5'i 0'a yuvarlayarak) = 10010) 2 [aritmetik sağa kaydırmanın sonucu]

-37) 10 = 11011011) 2 (bir ikinin tümleyeni, 8 bitlik gösterimi dikkate alındığında)

-37 ÷ 2 = -18,5

-37 / 2 = -18 (18,5'i 0'a yuvarlayarak) = 11101110) 2 [Aritmetik sağa kaydırmanın sonucu DEĞİL]

-37 >> 1 = -19 (18,5'i −∞'a yuvarlayarak) = 11101101) 2 [aritmetik sağa kaydırmanın sonucu]

Guy Steele'in belirttiği gibi , bu tutarsızlık birden fazla derleyicide hatalara neden oldu . Burada negatif olmayan (matematik) işaretsiz ve işaretli negatif olmayan değerlere (C) eşlenebilir; her ikisi de aynı şekilde ele alınır ve sağa kaydırma tamsayı bölme ile yapılır.

Yani mantıksal ve aritmetik sola kaydırmada eşdeğerdir ve sağa kaydırmada negatif olmayan değerler için; farklı oldukları negatif değerlerin doğru kaymasıdır.

Operand ve Sonuç Türleri

Standart C99 §6.5.7 :

İşlenenlerin her birinin tam sayı türleri olacaktır.

Tamsayı yükseltmeleri, işlenenlerin her birinde gerçekleştirilir. Sonucun türü, yükseltilmiş sol işlenenin türüdür. Sağ işlenenin değeri negatifse veya yükseltilen sol işlenenin genişliğinden büyük veya ona eşitse, davranış tanımsızdır.

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

Yukarıdaki kod parçacığında, her iki işlenen int(tamsayı yükseltmesinden dolayı) olur; eğer E2negatif oldu ya da E2 ≥ sizeof(int) * CHAR_BITsonra işlem tanımlanmamış. Bunun nedeni, mevcut bitlerden daha fazla kaydırmanın kesinlikle taşmasıdır. Had Rolarak ilan edilmiştir short, intkaydırma işleminin sonucu örtülü olarak dönüştürülür olacaktırshort ; hedef türünde değer gösterilemezse, uygulama tanımlı davranışa yol açabilen daraltıcı bir dönüşüm.

Sol shift

E1 << E2'nin sonucu, E1 sola kaydırılmış E2 bit pozisyonlarıdır; boş bitler sıfırlarla doldurulur. E1 işaretsiz bir türe sahipse, sonucun değeri E1 × 2 E2 , indirgenmiş modülo, sonuç türünde gösterilebilen maksimum değerden bir fazla. E1 işaretli bir türe ve negatif olmayan bir değere sahipse ve E1 × 2 E2 sonuç türünde gösterilebilirse, bu sonuçta elde edilen değerdir; aksi takdirde davranış tanımsızdır.

Sol vardiyalar her ikisi için de aynı olduğundan, boşalan bitler basitçe sıfırlarla doldurulur. Daha sonra hem işaretsiz hem de işaretli türler için bunun bir aritmetik kayma olduğunu belirtir. Bunu aritmetik kayma olarak yorumluyorum çünkü mantıksal kaymalar bitlerin temsil ettiği değerle ilgilenmiyor, sadece bir bit akışı olarak bakıyor; ancak standart, bit cinsinden değil, E1'in 2 E2 ile çarpımı ile elde edilen değer cinsinden tanımlayarak konuşuyor .

Buradaki uyarı, işaretli tipler için değerin negatif olmaması ve sonuçta ortaya çıkan değerin sonuç tipinde gösterilebilir olmasıdır. Aksi takdirde işlem tanımsızdır. Sonuç türü, hedef (sonucu tutacak değişken) türü değil, integral yükseltme uygulandıktan sonra E1'in türü olacaktır. Elde edilen değer örtük olarak hedef türüne dönüştürülür; bu türde gösterilemezse, dönüşüm uygulama tanımlıdır (C99 §6.3.1.3 / 3).

E1, negatif bir değere sahip işaretli bir tür ise, sola kaydırma davranışı tanımsızdır. Bu, kolayca gözden kaçabilecek tanımlanmamış davranışlara giden kolay bir yoldur.

Sağa kaydırma

E1 >> E2'nin sonucu, E1 sağa kaydırılmış E2 bit pozisyonlarıdır. E1 işaretsiz bir türe sahipse veya E1 işaretli bir türe ve negatif olmayan bir değere sahipse, sonucun değeri E1 / 2 E2 bölümünün ayrılmaz parçasıdır . E1 işaretli bir türe ve negatif bir değere sahipse, ortaya çıkan değer uygulama tanımlıdır.

İşaretsiz ve işaretli negatif olmayan değerler için sağa kaydırma oldukça basittir; boş bitler sıfırlarla doldurulur. İşaretli negatif değerler için, sağa kaydırmanın sonucu uygulama tanımlıdır. Bununla birlikte, GCC ve Visual C ++ gibi çoğu uygulama , işaret bitini koruyarak aritmetik kaydırma olarak sağa kaydırmayı uygular.

Sonuç

>>>Normalden ayrı mantıksal kaydırma için özel bir operatöre sahip olan Java'nın aksine >>ve <<C ve C ++ yalnızca aritmetik kaydırmaya sahiptir ve bazı alanlar tanımsız ve uygulama tanımlı bırakılmıştır. Bunları aritmetik olarak görmemin nedeni, kaydırılan işleneni bir bit akışı olarak ele almaktan ziyade, işlemi matematiksel olarak ifade eden standarttır; Bu belki de, tüm durumları mantıksal kaymalar olarak tanımlamak yerine, bu alanları / uygulama tanımlamasız bırakmasının nedenidir.


1
Güzel cevap. Yuvarlama ile ilgili olarak ( Kaydırma başlıklı bölümde ) - -Infhem negatif hem de pozitif sayılar için sağa kaydırma yuvarlar . Pozitif bir sayının 0'a yuvarlanması, özel bir yuvarlama durumudur -Inf. Keserken, her zaman pozitif ağırlıklı değerleri düşürürsünüz, dolayısıyla aksi takdirde kesin olan sonuçtan çıkarırsınız.
ysap

1
@ysap Evet, iyi gözlem. Temelde, pozitif sayılar için 0'a yuvarlama, −∞'a doğru daha genel yuvarlağın özel bir durumudur; bu, hem pozitif hem de negatif sayıların −∞'a doğru yuvarlandığını kaydettiğim tabloda görülebilir.
efsaneler2k

17

Aldığınız değişim türü açısından önemli olan, değiştirdiğiniz değerin türüdür. Klasik bir hata kaynağı, bir kelimeyi örneğin bitleri maskelemek için değiştirdiğiniz zamandır. Örneğin, işaretsiz bir tam sayının en soldaki bitini bırakmak istiyorsanız, bunu maskeniz olarak deneyebilirsiniz:

~0 >> 1

Ne yazık ki, bu başınızı belaya sokacaktır çünkü maskenin tüm bitleri ayarlanmış olacaktır, çünkü kaydırılan değer (~ 0) işaretlenmiştir, dolayısıyla bir aritmetik kaydırma gerçekleştirilir. Bunun yerine, değeri açıkça işaretsiz olarak bildirerek, yani şöyle bir şey yaparak mantıksal bir kaymayı zorlamak istersiniz:

~0U >> 1;

16

C'deki bir int'in mantıksal sağa kaymasını ve aritmetik sağa kaymasını garanti eden işlevler şunlardır:

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;
}

7

Yaptığınızda - sola kaydırma 1 ile 2 ile çarpılır - sağa kaydırma 1 ile 2'ye bölersiniz

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

 x = 5
 x << 1
 x = 10 (x=5*2)

X >> a ve x << a'da koşul a> 0 ise yanıt sırasıyla x = x / 2 ^ a, x = x * 2 ^ a'dır, o zaman a <0 ise yanıt ne olur?
JAVA

@sunny: a, 0'dan küçük olmamalıdır. C'de tanımlanmamış bir davranış
Jeremy

4

Wikipedia'dan baktım ve şunu söyleyecekler:

C, ancak, yalnızca bir sağa kaydırma operatörüne sahiptir, >>. Birçok C derleyicisi, kaydırılan tamsayı türüne bağlı olarak hangi sağa kaydırmanın gerçekleştirileceğini seçer; genellikle işaretli tamsayılar aritmetik kaydırma kullanılarak kaydırılır ve işaretsiz tam sayılar mantıksal kaydırma kullanılarak kaydırılır.

Yani derleyicinize bağlı gibi görünüyor. Ayrıca bu makalede, sola kaydırmanın aritmetik ve mantıksal için aynı olduğuna dikkat edin. Sınır durumu üzerinde bazı imzalı ve işaretsiz sayılarla basit bir test yapmanızı (tabii ki yüksek bit seti) ve derleyicinizde sonucun ne olduğunu görmenizi öneririm. Ayrıca, C'nin bir standardı yok gibi göründüğü için, en azından makul ve bu tür bir bağımlılıktan kaçınmak mümkünse, bunlardan biri ya da diğeri olmasına bağlı olarak kaçınmanızı tavsiye ederim.


Çoğu C derleyicisinin işaretli değerler için aritmetik sola kayması olmasına rağmen, bu tür yararlı davranışlar kullanımdan kaldırılmış gibi görünüyor. Mevcut derleyici felsefesi, bir değişken üzerindeki sola kayma performansının, bir derleyiciye değişkenin negatif olmaması gerektiğini varsayma ve böylece değişken negatifse doğru davranış için gerekli olabilecek herhangi bir kodu başka yerde çıkarma hakkı verdiğini varsaymak gibi görünmektedir. .
2015 17:47

0

Sol shift <<

Bu bir şekilde kolaydır ve vardiya operatörünü ne zaman kullanırsanız kullanın, her zaman biraz akıllıca bir işlemdir, bu nedenle onu çift ve kayan işlemle kullanamayız. Shift'i bir sıfır bıraktığımızda, her zaman en önemsiz bit'e ( LSB) eklenir .

Ancak sağa kaydırmada >>ek bir kuralı izlemeliyiz ve bu kurala "işaret biti kopyası" adı verilir. "İşaret biti kopyası" nın anlamı, en önemli bit ( MSB) ayarlanırsa, sağa kaydırmadan sonra tekrar MSBayarlanırsa, sıfırlanırsa tekrar sıfırlanır, yani önceki değer sıfır ise ve sonra tekrar kaydırıldıktan sonra, önceki bit bir ise bit sıfırdır ve vardiyadan sonra yine birdir. Bu kural sola kayma için geçerli değildir.

Sağ kaydırmadaki en önemli örnek, herhangi bir negatif sayıyı sağa kaydırmaya kaydırırsanız, sonra bir miktar kaydırdıktan sonra değer nihayet sıfıra ulaşır ve bundan sonra bu -1 kaydırırsa değer herhangi bir sayıda aynı kalacaktır. Lütfen kontrol edin.


0

tipik olarak işaretsiz değişkenler üzerinde mantıksal kaydırmalar ve işaretli değişkenler üzerinde sola kaymalar için kullanılır. Aritmetik sağa kaydırma gerçekten önemli olanıdır çünkü değişkeni genişlettiğini belirtecektir.

diğer derleyicilerin de yapması muhtemel olduğundan, uygun olduğunda bunu kullanacaktır.


-1

GCC yapar

  1. for -ve -> Aritmetik Kaydırma

  2. + Ve -> Mantıksal Kaydırma için


-7

Çoğuna göre derleyiciler:

  1. << aritmetik bir sola kaydırma veya bitsel sola kaydırmadır.
  2. >> aritmetik bir sağa kaydırıcı bitsel sağa kaydırmadır.

3
"Aritmetik sağa kaydırma" ve "bitsel sağa kaydırma" farklıdır. Sorunun amacı bu. Soru sordu, " >>Aritmetik mi yoksa bitsel mi (mantıksal)?" " >>Aritmetik veya bitseldir" dediniz. Bu soruya cevap vermiyor.
wchargin

Hayır, <<ve >>operatörleri mantıklı değil aritmetik
shjeff
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.