C'de, kaydırma operatörleri ( <<
, >>
) aritmetik mi yoksa mantıksal mı?
C'de, kaydırma operatörleri ( <<
, >>
) aritmetik mi yoksa mantıksal mı?
Yanıtlar:
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.
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);
Düşünün i
ve n
bir kaydırma operatörünün sırasıyla sol ve sağ işlenen olması; i
tamsayı yükseltmeden sonra türü , be T
. İçinde n
olduğ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
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.
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 E2
negatif oldu ya da E2 ≥ sizeof(int) * CHAR_BIT
sonra işlem tanımlanmamış. Bunun nedeni, mevcut bitlerden daha fazla kaydırmanın kesinlikle taşmasıdır. Had R
olarak ilan edilmiştir short
, int
kaydı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.
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.
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.
>>>
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.
-Inf
hem 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.
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;
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;
}
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)
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.
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 MSB
ayarlanı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.
gcctipik 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.
gcc diğer derleyicilerin de yapması muhtemel olduğundan, uygun olduğunda bunu kullanacaktır.
GCC yapar
for -ve -> Aritmetik Kaydırma
+ Ve -> Mantıksal Kaydırma için
Çoğuna göre c derleyiciler:
<<
aritmetik bir sola kaydırma veya bitsel sola kaydırmadır.>>
aritmetik bir sağa kaydırıcı bitsel sağa kaydırmadır.>>
Aritmetik mi yoksa bitsel mi (mantıksal)?" " >>
Aritmetik veya bitseldir" dediniz. Bu soruya cevap vermiyor.
<<
ve >>
operatörleri mantıklı değil aritmetik