Saya sedang menjelajahi beberapa kode C ++, dan menemukan sesuatu seperti ini:
(a + (b & 255)) & 255
Ganda DAN mengganggu saya, jadi saya memikirkan:
(a + b) & 255
( a
dan b
merupakan bilangan bulat 32-bit unsigned)
Saya dengan cepat menulis skrip pengujian (JS) untuk mengkonfirmasi teori saya:
for (var i = 0; i < 100; i++) {
var a = Math.ceil(Math.random() * 0xFFFF),
b = Math.ceil(Math.random() * 0xFFFF);
var expr1 = (a + (b & 255)) & 255,
expr2 = (a + b) & 255;
if (expr1 != expr2) {
console.log("Numbers " + a + " and " + b + " mismatch!");
break;
}
}
Sementara skrip mengkonfirmasi hipotesis saya (kedua operasi sama), saya masih tidak mempercayainya, karena 1) acak dan 2) Saya bukan ahli matematika, saya tidak tahu apa yang saya lakukan .
Juga, maaf untuk judul Lisp-y. Jangan ragu untuk mengeditnya.
Math.random()
mengembalikan integer atau double pada [0,1)? Saya tidak berpikir naskah Anda (sebaik yang saya tahu) mencerminkan masalah yang Anda ajukan sama sekali.&
dan+
pada unsigned integers di C dan C ++.Jawaban:
Mereka sama. Ini buktinya:
Catat dulu identitasnya
(A + B) mod C = (A mod C + B mod C) mod C
Mari kita ulangi masalah dengan menganggap
a & 255
sebagai berdiri untuka % 256
. Ini benar karenaa
tidak ditandatangani.Begitu
(a + (b & 255)) & 255
juga(a + (b % 256)) % 256
Ini sama dengan
(a % 256 + b % 256 % 256) % 256
(Saya telah menerapkan identitas yang disebutkan di atas: perhatikan itumod
dan%
setara untuk tipe yang tidak bertanda tangan.)Ini menyederhanakan
(a % 256 + b % 256) % 256
menjadi(a + b) % 256
(menerapkan kembali identitas). Anda kemudian dapat mengembalikan operator bitwise untuk memberi(a + b) & 255
melengkapi buktinya.
sumber
A=0xFFFFFFFF, B=1, C=3
. Identitas pertama tidak berlaku. (Overflow tidak akan menjadi masalah untuk aritmatika unsigned, tetapi ini adalah hal yang sedikit berbeda.)(a + (b & 255)) & 255
sama saja dengan(a + (b % 256)) % N % 256
, dimanaN
satu lebih besar dari nilai unsigned maksimum. (rumus terakhir dimaksudkan untuk ditafsirkan sebagai aritmatika bilangan bulat matematika)Dalam penambahan posisi, pengurangan dan perkalian bilangan unsigned untuk menghasilkan hasil unsigned, digit masukan yang lebih signifikan tidak mempengaruhi digit yang kurang signifikan dari hasil. Ini berlaku untuk aritmatika biner seperti halnya pada aritmatika desimal. Ini juga berlaku untuk aritmatika bertanda "dua komplemen", tetapi tidak untuk aritmatika bertanda besar-tanda.
Namun kita harus berhati-hati saat mengambil aturan dari aritmatika biner dan menerapkannya ke C (saya percaya C ++ memiliki aturan yang sama dengan C pada hal ini tetapi saya tidak yakin 100%) karena aritmatika C memiliki beberapa aturan misterius yang dapat membuat kita tersandung naik. Aritmatika tak bertanda tangan di C mengikuti aturan pembungkus biner sederhana tetapi luapan aritmatika bertanda tangan adalah perilaku tak terdefinisi. Lebih buruk dalam beberapa keadaan C akan secara otomatis "mempromosikan" tipe unsigned ke (signed) int.
Perilaku tidak terdefinisi di C bisa sangat berbahaya. Kompilator yang bodoh (atau kompilator dengan tingkat pengoptimalan rendah) kemungkinan besar akan melakukan apa yang Anda harapkan berdasarkan pemahaman Anda tentang aritmatika biner, sementara kompilator pengoptimalan dapat merusak kode Anda dengan cara yang aneh.
Jadi kembali ke rumus dalam pertanyaan kesetaraan tergantung pada jenis operan.
Jika mereka adalah unsigned integers yang ukurannya lebih besar dari atau sama dengan ukuran
int
maka perilaku overflow dari operator penjumlahan didefinisikan dengan baik sebagai sampul biner sederhana. Apakah kita menutupi atau tidak 24 bit tinggi dari satu operan sebelum operasi penambahan tidak berdampak pada bit rendah hasil.Jika mereka adalah unsigned integers yang ukurannya kurang dari
int
maka mereka akan dipromosikan menjadi (ditandatangani)int
. Limpahan bilangan bulat yang ditandatangani adalah perilaku yang tidak ditentukan tetapi setidaknya pada setiap platform saya telah menemukan perbedaan ukuran antara jenis bilangan bulat yang berbeda cukup besar sehingga satu penambahan dua nilai yang dipromosikan tidak akan menyebabkan luapan. Jadi sekali lagi kita dapat kembali ke argumen aritmatika biner sederhana untuk menganggap pernyataan tersebut setara.Jika mereka adalah bilangan bulat bertanda yang ukurannya kurang dari int maka luapan lagi tidak dapat terjadi dan pada implementasi pelengkap dua kita dapat mengandalkan argumen aritmatika biner standar untuk mengatakan bahwa mereka ekuivalen. Pada besarnya tanda atau yang melengkapi implementasi mereka tidak akan sama.
OTOH jika
a
danb
ditandatangani bilangan bulat yang ukurannya lebih besar dari atau sama dengan ukuran int maka bahkan pada dua implementasi pelengkap ada kasus di mana satu pernyataan akan terdefinisi dengan baik sementara yang lain akan menjadi perilaku tidak terdefinisi.sumber
Lemma:
a & 255 == a % 256
untuk unsigneda
.Unsigned
a
dapat ditulis kembali sebagaim * 0x100 + b
beberapa unsignedm
,b
,0 <= b < 0xff
,0 <= m <= 0xffffff
. Ini mengikuti dari kedua definisi itua & 255 == b == a % 256
.Selain itu, kami membutuhkan:
(a + b) mod n = [(a mod n) + (b mod n)] mod n
(a + b) ==> (a + b) % (2 ^ 32)
Jadi:
Jadi ya, memang benar. Untuk integer 32-bit unsigned.
Bagaimana dengan tipe integer lainnya?
2^64
untuk2^32
.int
. Iniint
pasti tidak akan meluap atau negatif dalam salah satu operasi ini, jadi semuanya tetap valid.a+b
ataua+(b&255)
meluap, ini adalah perilaku yang tidak ditentukan. Jadi kesetaraan tidak bisa berlaku - ada kasus di mana(a+b)&255
perilaku tidak terdefinisi tetapi(a+(b&255))&255
tidak.sumber
Ya,
(a + b) & 255
baiklah.Ingat penambahan di sekolah? Anda menambahkan angka digit demi digit, dan menambahkan nilai carry ke kolom digit berikutnya. Kolom digit selanjutnya (yang lebih signifikan) tidak dapat memengaruhi kolom yang sudah diproses. Karena itu, tidak ada bedanya jika Anda mengosongkan digit hanya di hasil, atau juga pertama dalam argumen.
Hal di atas tidak selalu benar, standar C ++ memungkinkan implementasi yang akan merusaknya.
Seperti Deathstation 9000 : - ) harus menggunakan 33-bit
int
, jika OP berartiunsigned short
"32-bit unsigned integers". Jikaunsigned int
dimaksudkan, DS9K harus menggunakan 32-bitint
, dan 32-bitunsigned int
dengan bit padding. (Bilangan bulat unsigned harus memiliki ukuran yang sama dengan bilangan yang ditandatangani sesuai §3.9.1 / 3, dan bit padding diperbolehkan dalam §3.9.1 / 1.) Kombinasi lain dari ukuran dan bit padding juga akan berfungsi.Sejauh yang saya tahu, inilah satu-satunya cara untuk memecahkannya, karena:
int
dapat mewakili semua nilai dari tipe sumber (§4.5 / 1), jadiint
harus memiliki setidaknya 32 bit yang berkontribusi pada nilai, ditambah bit tanda.int
tidak dapat memiliki nilai lebih bit (tidak termasuk bit tanda) dari 32, karena yang lain tambahan tidak bisa meluap.sumber
2^N-1
. (N bahkan tidak diperlukan untuk menjadi kelipatan CHAR_BIT, saya lupa, tapi saya cukup yakin standar mengharuskan sampul terjadi modulo beberapa kekuatan 2.) Saya pikir satu-satunya cara Anda bisa mendapatkan keanehan adalah melalui promosi ke tipe bertanda tangan yang cukup lebar untuk menampunga
ataub
tetapi tidak cukup lebar untuk dipeganga+b
dalam semua kasus.Anda sudah memiliki jawaban cerdas: aritmatika unsigned adalah aritmatika modulo dan oleh karena itu hasilnya akan bertahan, Anda dapat membuktikannya secara matematis ...
Namun, satu hal keren tentang komputer adalah komputer itu cepat. Memang, mereka sangat cepat sehingga menghitung semua kombinasi valid dari 32 bit dimungkinkan dalam jumlah waktu yang wajar (jangan coba dengan 64 bit).
Jadi, dalam kasus Anda, saya pribadi suka membuangnya ke komputer; saya membutuhkan lebih sedikit waktu untuk meyakinkan diri sendiri bahwa program itu benar daripada yang diperlukan untuk meyakinkan diri sendiri daripada bukti matematis yang benar dan bahwa saya tidak mengawasi detail dalam spesifikasi 1 :
Ini menghitung melalui semua kemungkinan nilai
a
danb
dalam ruang 32-bit dan memeriksa apakah kesetaraan berlaku, atau tidak. Jika tidak, ia mencetak kasus yang tidak berfungsi, yang dapat Anda gunakan sebagai pemeriksaan kewarasan.Dan, menurut Clang : Kesetaraan berlaku .
Selanjutnya, mengingat bahwa aturan aritmatika adalah bit-width agnostic (di atas
int
bit-width), persamaan ini akan berlaku untuk semua tipe integer 32 bit atau lebih yang tidak bertanda tangan, termasuk 64 bit dan 128 bit.Catatan: Bagaimana kompilator menyebutkan semua pola 64-bit dalam kerangka waktu yang wajar? Itu tidak bisa. Loop dioptimalkan. Kalau tidak, kita semua akan mati sebelum eksekusi dihentikan.
Saya awalnya hanya membuktikannya untuk 16-bit unsigned integers; sayangnya C ++ adalah bahasa yang tidak masuk akal di mana bilangan bulat kecil (bitwidth lebih kecil dari
int
) pertama kali dikonversiint
.Dan sekali lagi, menurut Clang : Kesetaraan berlaku .
Nah, ini dia :)
1 Tentu saja, jika suatu program secara tidak sengaja memicu Perilaku Tidak Terdefinisi, itu tidak akan membuktikan banyak.
sumber
int
: bilangan bulat kecil pertama-tama diubah menjadiint
(aturan aneh). Jadi saya sebenarnya harus melakukan demonstrasi dengan 32-bit (dan setelah itu meluas ke 64 bit, 128 bit, ...).int
yang dapat mewakili setiap kemungkinanuint16_t
, tetapi di manaa+b
dapat meluap. Itu hanya masalah untuk tipe unsigned yang lebih sempit dariint
; C mensyaratkan bahwaunsigned
tipe adalah bilangan bulat biner, sehingga terjadi sampul modulo dengan pangkat 2Jawaban singkatnya adalah: kedua ekspresi itu setara
a
danb
merupakan bilangan bulat unsigned 32-bit, hasilnya sama bahkan jika terjadi luapan. aritmatika unsigned menjamin ini: hasil yang tidak dapat diwakili oleh jenis bilangan bulat unsigned yang dihasilkan dikurangi modulo bilangan yang satu lebih besar dari nilai terbesar yang dapat diwakili oleh jenis yang dihasilkan.Jawaban panjangnya adalah: tidak ada platform yang diketahui di mana ekspresi ini akan berbeda, tetapi Standar tidak menjaminnya, karena aturan promosi yang tidak terpisahkan.
Jika jenis
a
danb
(unsigned 32 bit integers) memiliki peringkat lebih tinggi dariint
, komputasi dilakukan sebagai unsigned, modulo 2 32 , dan menghasilkan hasil yang sama untuk kedua ekspresi untuk semua nilaia
danb
.Sebaliknya, Jika tipe
a
danb
lebih kecil dariint
, keduanya dipromosikan keint
dan penghitungan dilakukan menggunakan aritmatika bertanda, di mana luapan memunculkan perilaku yang tidak ditentukan.Jika
int
memiliki setidaknya 33 bit nilai, tidak satu pun dari ekspresi di atas dapat meluap, sehingga hasilnya ditentukan dengan sempurna dan memiliki nilai yang sama untuk kedua ekspresi.Jika
int
memiliki tepat 32 bit nilai, komputasi dapat meluap untuk kedua ekspresi, sebagai contoh nilaia=0xFFFFFFFF
danb=1
akan menyebabkan luapan di kedua ekspresi. Untuk menghindari hal ini, Anda perlu menulis((a & 255) + (b & 255)) & 255
.Kabar baiknya adalah tidak ada platform seperti itu 1 .
1 Lebih tepatnya, tidak ada platform nyata seperti itu, tetapi DS9K dapat dikonfigurasi untuk menunjukkan perilaku seperti itu dan masih sesuai dengan Standar C.
sumber
a
lebih kecil dariint
(2)int
memiliki 32 bit nilai (3)a=0xFFFFFFFF
. Itu tidak mungkin semua benar.int
, di mana terdapat 32 bit nilai dan bit satu tanda.Identik dengan asumsi tidak melimpah . Tidak ada versi yang benar-benar kebal terhadap luapan tetapi versi ganda dan versi lebih tahan terhadapnya. Saya tidak mengetahui sistem di mana luapan dalam kasus ini merupakan masalah, tetapi saya dapat melihat penulis melakukan ini jika ada.
sumber
int
lebarnya 33 bit, hasilnya sama bahkan jika terjadi overflow. aritmatika unsigned menjamin ini: hasil yang tidak dapat diwakili oleh jenis bilangan bulat unsigned yang dihasilkan dikurangi modulo bilangan yang satu lebih besar dari nilai terbesar yang dapat diwakili oleh jenis yang dihasilkan.Ya, Anda dapat membuktikannya dengan aritmatika, tetapi ada jawaban yang lebih intuitif.
Saat menambahkan, setiap bit hanya memengaruhi yang lebih signifikan daripada dirinya sendiri; tidak pernah kurang signifikan.
Oleh karena itu, apa pun yang Anda lakukan pada bit yang lebih tinggi sebelum penambahan tidak akan mengubah hasilnya, selama Anda hanya menyimpan bit yang kurang signifikan daripada bit terendah yang dimodifikasi.
sumber
Buktinya sepele dan dibiarkan sebagai latihan bagi pembaca
Tetapi untuk benar-benar melegitimasi ini sebagai jawaban, baris kode pertama Anda mengatakan ambil 8 bit terakhir dari
b
** (semua bit lebih tinggi darib
set ke nol) dan tambahkan inia
dan kemudian ambil hanya 8 bit terakhir dari pengaturan hasil semua lebih tinggi bit ke nol.Baris kedua mengatakan tambahkan
a
danb
dan ambil 8 bit terakhir dengan semua bit yang lebih tinggi nol.Hanya 8 bit terakhir yang signifikan dalam hasil. Oleh karena itu hanya 8 bit terakhir yang signifikan pada masukan.
** 8 bit terakhir = 8 LSB
Juga menarik untuk dicatat bahwa outputnya akan sama dengan
Seperti di atas, hanya 8 LSB yang signifikan, tetapi hasilnya adalah an
unsigned int
dengan semua bit lainnya nol. Thea + b
akan meluap, menghasilkan hasil yang diharapkan.sumber