Dlaczego (a% 256) różni się od (a & 0xFF)?


145

Zawsze zakładałem, że robiąc (a % 256)optymalizator naturalnie użyje wydajnej operacji bitowej, tak jakbym pisał (a & 0xFF).

Podczas testowania w eksploratorze kompilatora 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

A kiedy próbujesz innego kodu:

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

mod(int):
    movzx   eax, dil
    ret

Wygląda na to, że zupełnie czegoś mi brakuje. Jakieś pomysły?


64
0xFF to 255, a nie 256.
Rishikesh Raje

186
@RishikeshRaje: Więc? %też nie &jest.
usr2564301

27
@RishikeshRaje: Jestem pewien, że OP jest tego bardzo świadomy. Są używane do różnych operacji.
Pozdrawiam i hth. - Alf

28
Czy poza zainteresowaniem uzyskujesz lepsze wyniki, jeśli tak numjest unsigned?
Batszeba

20
@RishikeshRaje Bitwise i 0xFF jest odpowiednikiem modulo 2 ^ 8 dla liczb całkowitych bez znaku.
2501

Odpowiedzi:


230

To nie to samo. Spróbuj num = -79, a uzyskasz różne wyniki z obu operacji. (-79) % 256 = -79, podczas gdy (-79) & 0xffjest liczbą dodatnią.

Używając unsigned int, operacje są takie same, a kod prawdopodobnie będzie taki sam.

PS: Ktoś skomentował

Nie powinny być takie same, a % bokreśla się jako a - b * floor (a / b).

Nie tak to jest zdefiniowane w C, C ++, Objective-C (tj. We wszystkich językach, w których skompilowałby się kod w pytaniu).


Komentarze nie służą do rozszerzonej dyskusji; ta rozmowa została przeniesiona do czatu .
Martijn Pieters

52

Krótka odpowiedź

-1 % 256plony, -1a nie 255to, co jest -1 & 0xFF. Dlatego optymalizacja byłaby nieprawidłowa.

Długa odpowiedź

C ++ ma konwencję (a/b)*b + a%b == a, która wydaje się całkiem naturalna. a/bzawsze zwraca wynik arytmetyczny bez części ułamkowej (obcięcie do 0). W konsekwencji a%bma ten sam znak co alub wynosi 0.

Dzielenie -1/256daje 0i dlatego -1%256musi być -1, aby spełnić powyższy warunek ( (-1%256)*256 + -1%256 == -1). To oczywiście różni się od tego, -1&0xFFktóry jest 0xFF. Dlatego kompilator nie może zoptymalizować tak, jak chcesz.

Odpowiednia sekcja w standardzie C ++ [expr.mul §4] od N4606 mówi:

Dla argumentów całkowitych /operator daje iloraz algebraiczny z odrzuconą dowolną częścią ułamkową; jeśli iloraz a/bjest reprezentowalny w typie wyniku, (a/b)*b + a%bjest równy a[...].

Włączanie optymalizacji

Jednak przy użyciu unsignedtypów optymalizacja byłaby całkowicie poprawna , spełniając powyższą konwencję:

unsigned(-1)%256 == 0xFF

Zobacz także to .

Inne języki

Jest to obsługiwane bardzo różnie w różnych językach programowania, ponieważ można to sprawdzić w Wikipedii .


50

Od C ++ 11 num % 256musi być niedodatnia, jeśli numjest ujemna.

Tak więc wzorzec bitowy będzie zależał od implementacji typów ze znakiem w twoim systemie: dla ujemnego pierwszego argumentu wynikiem nie jest wyodrębnienie najmniej znaczących 8 bitów.

Byłoby inaczej, gdyby numw twoim przypadku było unsigned: w dzisiejszych czasach prawie oczekiwałbym, że kompilator wykona optymalizację, którą przytaczasz.


6
Prawie, ale nie do końca. Jeśli numjest ujemne, to num % 256jest równe zero lub ujemne (inaczej niż dodatnie).
Nayuki

5
Która IMO jest błędem w standardzie: matematycznie działanie modulo powinno przyjmować znak dzielnika, w tym przypadku 256. Aby zrozumieć, dlaczego rozważ to (-250+256)%256==6, ale (-250%256)+(256%256)musi być, zgodnie ze standardem, „niedodatnie”, a zatem nie 6. Takie łamanie asocjatywności ma rzeczywiste skutki uboczne: na przykład podczas obliczania renderowania „oddalającego” we współrzędnych całkowitych należy przesunąć obraz, aby wszystkie współrzędne były nieujemne.
Michael

2
@Michael Modulus nigdy nie był rozdzielczy względem dodawania („asocjacyjny” to zła nazwa tej właściwości!), Nawet jeśli postępujesz zgodnie z definicją matematyczną co do litery. Na przykład (128+128)%256==0ale (128%256)+(128%256)==256. Być może jest dobry zarzut co do określonego zachowania, ale nie jest dla mnie jasne, czy to właśnie to, o którym mówiłeś.
Daniel Wagner

1
@DanielWagner, masz rację, oczywiście, pomyliłem się z „skojarzeniem”. Jednakże, jeśli ktoś zachowuje znak dzielnika i oblicza wszystko w arytmetyce modularnej, to właściwość rozdzielcza zachowuje; w twoim przykładzie byś to zrobił 256==0. Kluczem jest uzyskanie dokładnie Nmożliwych wartości w Narytmetyce modulo , co jest możliwe tylko wtedy, gdy wszystkie wyniki mieszczą się w zakresie 0,...,(N-1), a nie -(N-1),...,(N-1).
Michael

6
@Michael: Z wyjątkiem tego, że% nie jest operatorem modulo, jest operatorem reszty .
Joren

11

Nie mam telepatycznego wglądu w rozumowanie kompilatora, ale w takim przypadku %istnieje konieczność radzenia sobie z wartościami ujemnymi (i dzielenia zaokrągla do zera), podczas &gdy wynikiem jest zawsze niższe 8 bitów.

Te sardźwięki instrukcja do mnie jak „przesunięcie arytmetyczne prawo”, wypełnienie opuszczonego bitów o wartości bit znaku.


0

Mówiąc matematycznie, modulo definiuje się następująco:

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

To tutaj powinno ci wyjaśnić. Możemy wyeliminować podłogę dla liczb całkowitych, ponieważ dzielenie liczb całkowitych jest równoważne podłodze (a / b). Jeśli jednak kompilator miałby użyć ogólnej sztuczki, takiej jak sugerujesz, musiałby działać dla wszystkich a i wszystkich b. Niestety tak nie jest. Mówiąc matematycznie, twoja sztuczka jest w 100% poprawna dla liczb całkowitych bez znaku (widzę, że odpowiedź mówi, że liczby całkowite ze znakiem są łamane, ale mogę to potwierdzić ani zaprzeczyć, ponieważ -a% b powinno być dodatnie). Czy możesz jednak zrobić tę sztuczkę dla wszystkich b? Prawdopodobnie nie. Dlatego kompilator tego nie robi. W końcu, gdyby modulo można było łatwo zapisać jako jedną operację bitową, po prostu dodalibyśmy obwód modulo, taki jak dla dodawania i innych operacji.


4
Myślę, że mylisz „podłogę” z „obciętą”. Wczesne komputery stosowały dzielenie obcięte, ponieważ często jest ono łatwiejsze do obliczenia niż dzielenie liczbowe, nawet w przypadkach, gdy rzeczy dzielą się równomiernie. Widziałem bardzo niewiele przypadków, w których dzielenie obcięte było bardziej użyteczne niż dzielenie liczbowe, ale wiele języków podąża za przykładem FORTRAN-a, który używa dzielenia obciętego.
supercat

@supercat Matematycznie rzecz biorąc, podłoga jest obcięta. Oba mają ten sam efekt. Mogą nie być zaimplementowane tak samo w komputerze, ale robią to samo.
user64742

5
@TheGreatDuck: Nie są takie same dla liczb ujemnych. Podłoga -2.3jest -3, a jeśli skrócisz -2.3do liczby całkowitej, otrzymasz -2. Zobacz en.wikipedia.org/wiki/Truncation . „w przypadku liczb ujemnych obcięcie nie jest zaokrąglane w tym samym kierunku, co funkcja podłogi”. A zachowanie %dla liczb ujemnych jest dokładnie powodem, dla którego PO widzi opisane zachowanie.
Mark Dickinson

@MarkDickinson Jestem prawie pewien, że modulo w c ++ daje dodatnie wartości dodatnich dzielników, ale nie zamierzam się spierać.
user64742

1
@TheGreatDuck - zobacz przykład: cpp.sh/3g7h (Zwróć uwagę, że C ++ 98 nie zdefiniował, który z dwóch możliwych wariantów został użyty, ale to robi nowsze standardy, więc możliwe , że użyłeś implementacji C ++ w przeszłości zrobiło to inaczej ...)
Periata Breatta
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.