Mengapa (a% 256) berbeda dari (a & 0xFF)?


145

Saya selalu berasumsi bahwa ketika melakukan (a % 256)pengoptimal akan secara alami menggunakan operasi bitwise yang efisien, seolah-olah saya menulis (a & 0xFF).

Saat menguji pada compiler explorer gcc-6.2 (-O3):

// Type your code here, or load an example.
int mod(int num) {
    return num % 256;
}

mod(int):
    mov     edx, edi
    sar     edx, 31
    shr     edx, 24
    lea     eax, [rdi+rdx]
    movzx   eax, al
    sub     eax, edx
    ret

Dan ketika mencoba kode lainnya:

// Type your code here, or load an example.
int mod(int num) {
    return num & 0xFF;
}

mod(int):
    movzx   eax, dil
    ret

Sepertinya saya benar-benar kehilangan sesuatu. Ada ide?


64
0xFF 255 bukan 256.
Rishikesh Raje

186
@RishikeshRaje: Jadi? %juga tidak &.
usr2564301

27
@RishikeshRaje: Saya yakin OP sangat sadar akan hal itu. Mereka digunakan dengan operasi yang berbeda.
Ceria dan hth. - Alf

28
Dari bunga, Anda mendapatkan hasil yang lebih baik jika numadalah unsigned?
Batsyeba

20
@RishikeshRaje Bitwise dan 0xFF setara dengan modulo 2 ^ 8 untuk bilangan bulat yang tidak ditandatangani.
2501

Jawaban:


230

Ini tidak sama. Coba num = -79, dan Anda akan mendapatkan hasil yang berbeda dari kedua operasi. (-79) % 256 = -79, sedangkan (-79) & 0xffbeberapa angka positif.

Dengan menggunakan unsigned int, operasinya sama, dan kode kemungkinan akan sama.

PS- Seseorang berkomentar

Mereka tidak boleh sama, a % bdidefinisikan sebagai a - b * floor (a / b).

Itu bukan bagaimana itu didefinisikan dalam C, C ++, Objective-C (yaitu semua bahasa di mana kode dalam pertanyaan akan dikompilasi).


Komentar bukan untuk diskusi panjang; percakapan ini telah dipindahkan ke obrolan .
Martijn Pieters

52

Jawaban singkat

-1 % 256hasil -1dan bukan 255yang mana -1 & 0xFF. Karena itu, optimasi akan salah.

Jawaban panjang

C ++ memiliki konvensi itu (a/b)*b + a%b == a, yang tampaknya cukup alami. a/bselalu mengembalikan hasil aritmatika tanpa bagian fraksional (memotong menuju 0). Akibatnya, a%bmemiliki tanda yang sama dengan aatau adalah 0.

Divisi -1/256menghasilkan 0dan karenanya -1%256harus -1memenuhi persyaratan di atas ( (-1%256)*256 + -1%256 == -1). Ini jelas berbeda dari -1&0xFFyang mana 0xFF. Oleh karena itu, kompiler tidak dapat mengoptimalkan seperti yang Anda inginkan.

Bagian yang relevan dalam standar C ++ [expr.mul §4] pada N4606 menyatakan:

Untuk operan integral /operator menghasilkan hasil bagi aljabar dengan setiap bagian pecahan dibuang; jika hasil bagi a/bdiwakili dalam jenis hasil, (a/b)*b + a%bsama dengan a[...].

Mengaktifkan pengoptimalan

Namun, menggunakan unsignedtipe, optimisasi akan sepenuhnya benar , memenuhi konvensi di atas:

unsigned(-1)%256 == 0xFF

Lihat juga ini .

Bahasa lainnya

Ini ditangani sangat berbeda di berbagai bahasa pemrograman karena Anda dapat melihat di Wikipedia .


50

Sejak C ++ 11, num % 256harus non-positif jika numnegatif.

Jadi pola bit akan tergantung pada implementasi tipe yang ditandatangani pada sistem Anda: untuk argumen pertama yang negatif, hasilnya bukan ekstraksi dari 8 bit yang paling tidak signifikan.

Ini akan menjadi masalah yang berbeda jika numdalam kasus Anda adalah unsigned: hari ini saya hampir berharap kompiler membuat optimasi yang Anda kutip.


6
Hampir tetapi tidak cukup. Jika numnegatif, maka num % 256nol atau negatif (alias tidak positif).
Nayuki

5
Yang IMO, adalah kesalahan dalam standar: operasi modulo matematis harus mengambil tanda pembagi, 256 dalam hal ini. Untuk memahami mengapa mempertimbangkan itu (-250+256)%256==6, tetapi (-250%256)+(256%256)harus, menurut standar, "non-positif", dan karenanya tidak 6. Melanggar asosiatif seperti itu memiliki efek samping kehidupan nyata: misalnya saat menghitung "perkecil" yang menghasilkan koordinat bilangan bulat, seseorang harus menggeser gambar agar semua koordinat non-negatif.
Michael

2
@Michael Modulus tidak pernah distributif atas penambahan ("asosiatif" adalah nama yang salah untuk properti ini!), Bahkan jika Anda mengikuti definisi matematika pada surat itu. Sebagai contoh, (128+128)%256==0tetapi (128%256)+(128%256)==256. Mungkin ada keberatan yang baik terhadap perilaku yang ditentukan, tetapi tidak jelas bagi saya bahwa itu yang Anda katakan.
Daniel Wagner

1
@DanielWagner, Anda benar, tentu saja, saya salah bicara dengan "asosiatif". Namun, jika seseorang menyimpan tanda pembagi dan menghitung semuanya dalam aritmatika modular, properti distributif tetap berlaku; dalam contoh Anda, Anda akan memiliki 256==0. Kuncinya adalah memiliki Nnilai yang mungkin tepat dalam modulo Naritmatika, yang hanya mungkin jika semua hasilnya berada dalam kisaran 0,...,(N-1), bukan -(N-1),...,(N-1).
Michael

6
@Michael: Kecuali% bukan operator modulo, ini adalah operator sisanya .
Joren

11

Saya tidak memiliki wawasan telepati ke dalam alasan kompiler, tetapi dalam kasus %ada perlunya berurusan dengan nilai-nilai negatif (dan putaran pembagian ke nol), sementara dengan &hasilnya selalu 8 bit yang lebih rendah.

The sarinstruksi suara untuk saya seperti "bergeser ke kanan aritmatika", mengisi bit dikosongkan dengan nilai tanda bit.


0

Secara matematis, modulo didefinisikan sebagai berikut:

a% b = a - b * lantai (a / b)

Ini di sini harus menjelaskannya untuk Anda. Kita dapat menghilangkan lantai untuk bilangan bulat karena pembagian bilangan bulat setara dengan lantai (a / b). Namun, jika kompiler menggunakan trik umum seperti yang Anda sarankan, itu perlu bekerja untuk semua dan semua b. Sayangnya, ini bukan masalahnya. Secara matematis, trik Anda 100% benar untuk bilangan bulat yang tidak ditandatangani (Saya melihat jawaban menyatakan bilangan bulat yang ditandatangani rusak, tetapi saya dapat mengonfirmasi atau menolak ini karena -a% b harus positif). Namun, dapatkah Anda melakukan trik ini untuk semua b? Mungkin tidak. Itu sebabnya kompiler tidak melakukannya. Lagi pula, jika modulo mudah ditulis sebagai satu operasi bitwise, maka kita hanya akan menambahkan rangkaian modulo seperti untuk penambahan dan operasi lainnya.


4
Saya pikir Anda membingungkan "lantai" dengan "terpotong". Komputer awal menggunakan divisi pemotongan karena seringkali lebih mudah untuk menghitung daripada pembagian lantai bahkan dalam kasus di mana hal-hal terjadi secara merata. Saya telah melihat sangat sedikit kasus di mana pembagian terpotong lebih bermanfaat daripada pembagian lantai seharusnya, tetapi banyak bahasa mengikuti jejak FORTRAN dalam menggunakan pembagian terpotong.
supercat

@supercat matematis berbicara, lantai adalah truncate. Keduanya memiliki efek yang sama. Mereka mungkin tidak diimplementasikan yang sama di komputer, tetapi mereka melakukan hal yang sama.
user64742

5
@TheGreatDuck: Mereka tidak sama untuk angka negatif. Lantai -2.3adalah -3, sedangkan jika Anda memotong -2.3ke bilangan bulat yang Anda dapatkan -2. Lihat en.wikipedia.org/wiki/Truncation . + msgstr "untuk pemotongan bilangan negatif tidak membulat ke arah yang sama dengan fungsi lantai". Dan perilaku %untuk bilangan negatif justru merupakan alasan OP melihat perilaku yang dijelaskan.
Mark Dickinson

@ MarkDickinson Saya cukup yakin bahwa modulo di c ++ memberikan nilai positif untuk pembagi positif, tapi saya tidak akan berdebat.
user64742

1
@TheGreatDuck - lihat contoh: cpp.sh/3g7h (Perhatikan bahwa C ++ 98 tidak menentukan yang mana dari dua varian yang mungkin digunakan, tetapi standar yang lebih baru berlaku, jadi ada kemungkinan Anda telah menggunakan implementasi C ++ di masa lalu yang melakukannya secara berbeda ...)
Periata Breatta
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.