(A% 256) neden (a & 0xFF) seçeneğinden farklıdır?


145

Her zaman (a % 256)optimize ediciyi yaparken doğal olarak verimli bir bitsel işlem kullanacağını varsaymıştım, sanki yazmışım gibi (a & 0xFF).

Derleyici gezgini gcc-6.2 (-O3) üzerinde test ederken:

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

Ve diğer kodu denerken:

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

mod(int):
    movzx   eax, dil
    ret

Görünüşe göre bir şeyleri tamamen kaçırıyorum. Herhangi bir fikir?


64
0xFF 256 değil 255'tir.
Rishikesh Raje

186
@RishikeshRaje: Yani? ikisi %de değil &.
Jongware

27
@RishikeshRaje: OP'nin bunun çok farkında olduğuna eminim. Farklı işlemlerle kullanılırlar.
Şerefe ve hth. - Alf

28
Eğer bir çıkar gözetmeksizin, daha iyi sonuçlar alabilirim numolduğunu unsigned?
Bathsheba

20
@RishikeshRaje Bitwise ve 0xFF, işaretsiz tamsayılar için modulo 2 ^ 8'e eşdeğerdir.
2501

Yanıtlar:


230

Aynı şey değil. Deneyin num = -79ve her iki işlemden de farklı sonuçlar alacaksınız. (-79) % 256 = -79İken (-79) & 0xffbazı olumlu sayıdır.

Kullanım unsigned int, işlemler aynıdır ve kod muhtemelen aynı olacaktır.

PS- Birisi yorum yaptı

Aynı olmamaları gerekir, a % bolarak tanımlanır a - b * floor (a / b).

Bu, C, C ++, Objective-C'de (yani sorudaki kodun derleneceği tüm diller) böyle tanımlanmaz.


Yorumlar uzun tartışmalar için değildir; bu konuşma sohbete taşındı .
Martijn Pieters

52

Kısa cevap

-1 % 256verim -1, 255hangisi değil -1 & 0xFF. Bu nedenle optimizasyon yanlış olur.

Uzun cevap

C ++, (a/b)*b + a%b == aoldukça doğal görünen bir kurala sahiptir. a/bher zaman kesirli kısım olmadan aritmetik sonucu verir (0'a doğru kırpılır). Sonuç a%bolarak a, 0 ile aynı işarete sahiptir .

Bölünme -1/256getirileri 0ve dolayısıyla yukarıdaki koşulu ( ) karşılamak için -1%256olmalıdır . Bu açıkça hangisinden farklıdır . Bu nedenle, derleyici istediğiniz şekilde optimize edemez.-1(-1%256)*256 + -1%256 == -1-1&0xFF0xFF

C ++ standardının [ifade §4] N4606 itibariyle ilgili bölümü şunları belirtir:

İntegral işlenenler için /operatör, herhangi bir kesirli kısım atılmış olarak cebirsel bölümü verir; bölüm a/bsonuç türünde gösterilebilirse, [...] 'ye (a/b)*b + a%beşittir a.

Optimizasyonu etkinleştirmek

Bununla birlikte, türleri kullanarak unsigned, optimizasyon tamamen doğru olur ve yukarıdaki kuralı yerine getirir:

unsigned(-1)%256 == 0xFF

Ayrıca buna bakın .

Diğer diller

Wikipedia'da bakabileceğiniz gibi bu, farklı programlama dillerinde çok farklı şekilde ele alınır .


50

C ++ 11 num % 256olduğundan num, negatifse pozitif olmamalıdır .

Dolayısıyla, bit örüntüsü, sisteminizdeki işaretli türlerin uygulanmasına bağlı olacaktır: negatif bir ilk argüman için, sonuç en az önemli olan 8 bitin çıkarılması değildir.

numSizin durumunuzda olsaydı farklı bir konu olurdu unsigned: Bu günlerde neredeyse bir derleyicinin alıntı yaptığınız optimizasyonu yapmasını beklerdim .


6
Neredeyse ama tam olarak değil. Eğer numnegatiftir, o num % 256sıfır ya da negatif (aka pozitif olmayan) 'dir.
Nayuki

5
Hangi IMO, standartta bir hatadır: matematiksel olarak modulo işlemi, bu durumda 256 bölen işaretini almalıdır. Nedenini anlamak için bunu düşünün (-250+256)%256==6, ancak (-250%256)+(256%256)standarda göre "pozitif olmayan" olmalı ve bu nedenle değil 6. Bunun gibi ilişkiselliği kırmanın gerçek hayatta yan etkileri vardır: örneğin, tamsayı koordinatlarında "uzaklaştırma" işlemini hesaplarken, tüm koordinatların negatif olmaması için görüntüyü kaydırmak gerekir.
Michael

2
@Michael Modülü, harfin matematiksel tanımını takip etseniz bile, hiçbir zaman toplamaya dağıtılmamıştır ("ilişkisel" bu özellik için yanlış isimdir!). Örneğin, (128+128)%256==0ama (128%256)+(128%256)==256. Belki de belirtilen davranışa iyi bir itiraz vardır, ancak bunun senin söylediğin şey olduğu bana açık değil.
Daniel Wagner

1
@DanielWagner, haklısın elbette, "çağrışımlı" ile yanlış konuştum. Bununla birlikte, eğer bölenin işaretini tutarsanız ve her şeyi modüler aritmetikte hesaplarsa, dağıtım özelliği geçerli olur; örneğinizde sahip olurdunuz 256==0. Anahtar, Nmodulo Naritmetiğinde tam olarak olası değerlere sahip olmaktır ; bu, yalnızca tüm sonuçlar aralık içindeyse mümkündür 0,...,(N-1), değil -(N-1),...,(N-1).
Michael

6
@Michael:% dışında bir modulo operatörü değil, bir kalan operatörü.
Joren

11

Derleyicinin mantığına dair telepatik bir anlayışa sahip değilim, ancak bu durumda %negatif değerlerle (ve sıfıra doğru bölme yuvarlamalarını) ele alma zorunluluğu varken &, sonuç her zaman daha düşük 8 bittir.

sarİşaret bit değeri ile boşalan bit dolduruyor, "aritmetik hakkı shift" gibi bana talimat sesler.


0

Matematiksel olarak, modulo şu şekilde tanımlanır:

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

Bu tam burada sizin için açıklığa kavuşturmalı. Tamsayılar için tabanı ortadan kaldırabiliriz çünkü tamsayı bölmesi floor (a / b) ile eşdeğerdir. Ancak, derleyici önerdiğiniz gibi genel bir numara kullanacaksa, tüm a ve tümü b için çalışması gerekir. Maalesef durum bu değil. Matematiksel olarak konuşursak, numaranız işaretsiz tamsayılar için% 100 doğrudur (bir cevap durumunun işaretli tamsayılar kırıldığını görüyorum, ancak bunu onaylayabilir veya reddedebilirim - a% b pozitif olmalıdır). Ancak, bu numarayı tüm b için yapabilir misiniz? Muhtemelen değil. Bu yüzden derleyici bunu yapmaz. Sonuçta, eğer modulo kolayca bitsel bir işlem olarak yazılsaydı, o zaman ekleme ve diğer işlemler için olduğu gibi bir modulo devresi eklerdik.


4
Sanırım "yer" ile "kes" i karıştırıyorsun. İlk bilgisayarlar kesme bölme kullanıyordu çünkü hesaplamak, işlerin eşit şekilde bölündüğü durumlarda bile tabana bölünmüş bölmeden daha kolaydır. Kesilmiş bölmenin tabana bölünmüş bölmeden daha yararlı olduğu çok az durum gördüm, ancak birçok dil, FORTRAN'ın kesilmiş bölme kullanma öncülüğünü izliyor.
supercat

Matematiksel olarak konuşursak @supercat, zemin olduğu kesiğinin. İkisi de aynı etkiye sahip. Bir bilgisayarda aynı şekilde uygulanmayabilirler, ancak aynı şeyi yaparlar.
user64742

5
@TheGreatDuck: Negatif sayılar için aynı değiller. Zemini -2.3ise -3sen kesmek eğer ederken, -2.3Alacağınız tam sayıya -2. Bkz. En.wikipedia.org/wiki/Truncation . "negatif sayılar için kesme, kat işlevi ile aynı yönde yuvarlanmaz". Ve %negatif sayılar için davranışı , tam olarak OP'nin tanımlanan davranışı görmesinin sebebidir.
Mark Dickinson

@MarkDickinson c ++ 'daki modulo'nun pozitif bölenler için pozitif değerler verdiğinden oldukça eminim, ancak tartışmayacağım.
user64742

1
@TheGreatDuck - örneğe bakın: cpp.sh/3g7h (Not C ++ 98 kullanılır, ama öyle ki daha yeni standartlar, do edildi iki olası varyantların hangi tanımlamak vermedi mümkün Eğer C bir uygulama kullandım ++ geçmişte bunu farklı yaptı ...)
Periata Breatta
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.