Dalam C, apakah operator shift ( <<
, >>
) aritmatika atau logis?
c
binary
bit-manipulation
bit-shift
littlebyte
sumber
sumber
Jawaban:
Menurut K&R edisi ke-2 , hasilnya tergantung pada implementasi untuk pergeseran yang tepat dari nilai yang ditandatangani.
Wikipedia mengatakan bahwa C / C ++ 'biasanya' mengimplementasikan perubahan aritmatika pada nilai yang ditandatangani.
Pada dasarnya Anda perlu menguji kompiler Anda atau tidak bergantung padanya. VS2008 bantuan saya untuk kompiler MS C ++ saat ini mengatakan bahwa kompiler mereka melakukan perubahan aritmatika.
sumber
Saat bergeser ke kiri, tidak ada perbedaan antara aritmatika dan pergeseran logis. Saat bergeser ke kanan, tipe shift tergantung pada tipe nilai yang digeser.
(Sebagai latar belakang bagi pembaca yang tidak terbiasa dengan perbedaan, pergeseran kanan "logis" dengan 1 bit menggeser semua bit ke kanan dan mengisi bit paling kiri dengan angka 0. Pergeseran "aritmatika" meninggalkan nilai asli di bit paling kiri Perbedaannya menjadi penting ketika berhadapan dengan angka negatif.)
Saat menggeser nilai yang tidak ditandatangani, >> operator di C adalah perubahan logis. Saat menggeser nilai yang ditandatangani, operator >> adalah perubahan aritmatika.
Misalnya, dengan asumsi mesin 32 bit:
sumber
TL; DR
Pertimbangkan
i
dann
menjadi operan kiri dan kanan masing-masing dari operator shift; jenisi
, setelah promosi bilangan bulat, menjadiT
. Dengan asumsin
berada di[0, sizeof(i) * CHAR_BIT)
- tidak ditentukan sebaliknya - kami memiliki kasus ini:† kebanyakan kompiler mengimplementasikan ini sebagai pergeseran aritmatika
‡ tidak terdefinisi jika nilai melebihi tipe T hasil; tipe i yang dipromosikan
Bergeser
Pertama adalah perbedaan antara pergeseran logis dan aritmatika dari sudut pandang matematika, tanpa khawatir tentang ukuran tipe data. Pergeseran logis selalu mengisi bit yang dibuang dengan nol sedangkan pergeseran aritmatika mengisinya dengan nol hanya untuk shift kiri, tetapi untuk shift kanan menyalin MSB sehingga mempertahankan tanda operan (dengan asumsi komplemen dua pengkodean untuk nilai negatif).
Dengan kata lain, shift logis melihat operan yang digeser hanya sebagai aliran bit dan memindahkannya, tanpa peduli tentang tanda dari nilai yang dihasilkan. Pergeseran aritmatika melihatnya sebagai nomor (bertanda) dan mempertahankan tanda itu ketika pergantian dilakukan.
Pergeseran aritmatika kiri dari angka X oleh n sama dengan mengalikan X dengan 2 n dan dengan demikian setara dengan pergeseran kiri logis; perubahan logis juga akan memberikan hasil yang sama karena MSB akhirnya jatuh dan tidak ada yang bisa dipertahankan.
Pergeseran aritmatika kanan dari angka X oleh n sama dengan pembagian bilangan bulat X dengan 2 n HANYA jika X adalah non-negatif! Divisi integer tidak lain adalah divisi matematika dan bulat menuju 0 ( trunc ).
Untuk bilangan negatif, diwakili oleh pengkodean komplemen dua, menggeser ke kanan oleh n bit memiliki efek membaginya secara matematis dengan 2 n dan pembulatan ke arah −∞ ( lantai ); dengan demikian pergeseran kanan berbeda untuk nilai-nilai non-negatif dan negatif.
dimana
÷
adalah pembagian matematika,/
adalah pembagian bilangan bulat. Mari kita lihat sebuah contoh:Seperti yang ditunjukkan Guy Steele , perbedaan ini menyebabkan bug di lebih dari satu kompiler . Di sini nilai non-negatif (matematika) dapat dipetakan ke nilai non-negatif yang ditandatangani dan ditandatangani (C); keduanya diperlakukan sama dan menggeser-kanannya dilakukan oleh divisi integer.
Jadi logis dan aritmatika adalah setara dalam pergeseran kiri dan untuk nilai non-negatif dalam pergeseran kanan; ada pergeseran nilai-nilai negatif yang benar.
Operan dan Jenis Hasil
Standar C99 §6.5.7 :
Dalam cuplikan di atas, kedua operan menjadi
int
(karena promosi bilangan bulat); jikaE2
negatif atauE2 ≥ sizeof(int) * CHAR_BIT
operasi tidak terdefinisi. Ini karena menggeser lebih dari bit yang tersedia pasti akan meluap. TelahR
dinyatakan sebagaishort
,int
hasil dari operasi shift akan secara implisit dikonversi menjadishort
; konversi penyempitan, yang dapat mengarah pada perilaku yang ditentukan implementasi jika nilainya tidak dapat diwakili dalam tipe tujuan.Shift Kiri
Karena shift kiri sama untuk keduanya, bit yang dikosongkan hanya diisi dengan nol. Itu kemudian menyatakan bahwa untuk kedua jenis bertanda tangan dan ditandatangani itu adalah perubahan aritmatika. Saya menafsirkannya sebagai perubahan aritmatika karena pergeseran logis tidak peduli tentang nilai yang diwakili oleh bit, itu hanya melihatnya sebagai aliran bit; tetapi standar tidak berbicara dalam hal bit, tetapi dengan mendefinisikannya dalam hal nilai yang diperoleh oleh produk E1 dengan 2 E2 .
Peringatan di sini adalah bahwa untuk jenis yang ditandatangani nilai harus non-negatif dan nilai yang dihasilkan harus dapat diwakili dalam jenis hasil. Kalau tidak, operasi tidak ditentukan. Jenis hasil akan menjadi tipe E1 setelah menerapkan promosi integral dan bukan tipe tujuan (variabel yang akan menahan hasil). Nilai yang dihasilkan secara implisit dikonversi ke tipe tujuan; jika tidak dapat diwakili dalam tipe itu, maka konversi ditentukan oleh implementasi (C99 §6.3.1.3 / 3).
Jika E1 adalah tipe bertanda tangan dengan nilai negatif maka perilaku perpindahan kiri tidak ditentukan. Ini adalah rute yang mudah menuju perilaku tidak terdefinisi yang mungkin dengan mudah diabaikan.
Shift Kanan
Pergeseran ke kanan untuk nilai non-negatif yang ditandatangani dan ditandatangani cukup mudah; bit kosong diisi dengan nol. Untuk nilai negatif yang ditandatangani, hasil dari penggeseran kanan ditentukan oleh implementasi. Yang mengatakan, sebagian besar implementasi seperti GCC dan Visual C ++ menerapkan pergeseran kanan sebagai pergeseran aritmatika dengan mempertahankan bit tanda.
Kesimpulan
Tidak seperti Java, yang memiliki operator khusus
>>>
untuk pemindahan logis selain dari biasanya>>
dan<<
, C dan C ++ hanya memiliki pemindahan aritmatika dengan beberapa area dibiarkan tidak terdefinisi dan implementasi-didefinisikan. Alasan saya menganggap mereka sebagai aritmatika adalah karena standar kata operasi secara matematis daripada memperlakukan operan bergeser sebagai aliran bit; ini mungkin alasan mengapa hal itu membuat area-area itu tidak terdefinisi / implementasi-alih-alih hanya mendefinisikan semua kasus sebagai pergeseran logis.sumber
-Inf
ke angka negatif dan positif. Pembulatan ke 0 dari angka positif adalah kasus pembulatan ke arah pribadi-Inf
. Saat memotong, Anda selalu menurunkan nilai tertimbang positif, karenanya Anda mengurangi hasil yang sebaliknya.Dalam hal jenis shift yang Anda dapatkan, yang penting adalah tipe nilai yang Anda geser. Sumber klasik bug adalah ketika Anda menggeser literal ke, katakanlah, tutup bit. Misalnya, jika Anda ingin menjatuhkan bit paling kiri dari integer yang tidak ditandatangani, maka Anda dapat mencoba ini sebagai mask Anda:
Sayangnya, ini akan membuat Anda kesulitan karena topeng akan memiliki semua bit yang ditetapkan karena nilai yang digeser (~ 0) ditandatangani, sehingga pergeseran aritmatika dilakukan. Alih-alih, Anda ingin memaksakan perubahan logis dengan secara eksplisit menyatakan nilai sebagai tidak ditandatangani, yaitu dengan melakukan sesuatu seperti ini:
sumber
Berikut adalah fungsi untuk menjamin pergeseran kanan logis dan pergeseran kanan aritmatika int di C:
sumber
Ketika Anda melakukannya - shift kiri dengan 1 Anda kalikan dengan 2 - shift kanan dengan 1 Anda bagi dengan 2
sumber
Yah, saya mencarinya di wikipedia , dan mereka mengatakan ini:
Jadi sepertinya tergantung pada kompiler Anda. Juga dalam artikel itu, perhatikan bahwa shift kiri sama untuk aritmatika dan logis. Saya akan merekomendasikan melakukan tes sederhana dengan beberapa angka yang ditandatangani dan tidak ditandatangani pada kasus perbatasan (set bit tinggi tentu saja) dan melihat apa hasilnya pada kompiler Anda. Saya juga merekomendasikan untuk menghindari tergantung pada itu menjadi satu atau yang lain karena tampaknya C tidak memiliki standar, setidaknya jika itu masuk akal dan mungkin untuk menghindari ketergantungan seperti itu.
sumber
Pergeseran ke kiri
<<
Ini entah bagaimana mudah dan kapan pun Anda menggunakan operator shift, itu selalu merupakan operasi yang sedikit bijaksana, jadi kami tidak dapat menggunakannya dengan operasi ganda dan float. Setiap kali kami meninggalkan shift satu nol, selalu ditambahkan ke bit paling tidak signifikan (
LSB
).Tetapi dalam shift kanan
>>
kita harus mengikuti satu aturan tambahan dan aturan itu disebut "sign bit copy". Arti "tanda bit copy" adalah jika bit yang paling signifikan (MSB
) diatur kemudian setelah bergeser ke kanan lagiMSB
akan ditetapkan jika itu direset maka kembali diatur ulang, artinya jika nilai sebelumnya nol lalu setelah bergeser lagi, bit adalah nol jika bit sebelumnya adalah satu maka setelah shift itu adalah satu lagi. Aturan ini tidak berlaku untuk shift kiri.Contoh paling penting pada shift kanan jika Anda menggeser angka negatif ke shift kanan, kemudian setelah beberapa pergeseran nilainya akhirnya mencapai nol dan kemudian setelah ini jika menggeser -1 ini berapa kali nilainya akan tetap sama. Silakan periksa.
sumber
gccbiasanya akan menggunakan shift logis pada variabel yang tidak ditandatangani dan untuk shift kiri pada variabel yang ditandatangani. Pergeseran kanan aritmatika adalah yang benar-benar penting karena akan memperpanjang variabel.
gcc akan akan menggunakan ini ketika berlaku, karena kompiler lain mungkin melakukannya.
sumber
GCC melakukannya
untuk -ve -> Pergeseran Aritmatika
Untuk + ve -> Shift Logis
sumber
Menurut banyak orang c kompiler:
<<
adalah shift kiri aritmatika atau shift kiri bitwise.>>
adalah pergeseran kanan aritmatika atau bitwise pergeseran kanan.sumber
>>
aritmatika atau bitwise (logis)?" Anda menjawab ">>
adalah aritmatika atau bitwise." Itu tidak menjawab pertanyaan.<<
dan>>
operator logis, bukan aritmatika