Perché (a% 256) è diverso da (a & 0xFF)?


145

Ho sempre pensato che, quando facevo (a % 256)l'ottimizzatore, avrei naturalmente usato un'operazione bit a bit efficiente, come se avessi scritto (a & 0xFF).

Durante i test su compilatore 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

E quando provi l'altro codice:

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

mod(int):
    movzx   eax, dil
    ret

Sembra che mi manchi completamente qualcosa. Qualche idea?


64
0xFF è 255 non 256.
Rishikesh Raje

186
@RishikeshRaje: Quindi? %non è &neanche.
usr2564301

27
@RishikeshRaje: sono sicuro che l'OP ne sia consapevole. Sono utilizzati con diverse operazioni.
Saluti e hth. - Alf,

28
Per interesse, ottieni risultati migliori se lo numè unsigned?
Bathsheba,

20
@RishikeshRaje Bitwise e 0xFF è equivalente a modulo 2 ^ 8 per numeri interi senza segno.
2501

Risposte:


230

Non è lo stesso. Prova num = -79e otterrai risultati diversi da entrambe le operazioni. (-79) % 256 = -79, mentre (-79) & 0xffè un numero positivo.

Utilizzando unsigned int, le operazioni sono le stesse e il codice sarà probabilmente lo stesso.

PS- Qualcuno ha commentato

Non dovrebbero essere gli stessi, a % bè definito come a - b * floor (a / b).

Non è così che viene definito in C, C ++, Objective-C (cioè tutti i linguaggi in cui il codice nella domanda verrebbe compilato).


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Martijn Pieters

52

Risposta breve

-1 % 256cede -1e non 255quale sia -1 & 0xFF. Pertanto, l'ottimizzazione sarebbe errata.

Risposta lunga

Il C ++ ha la convenzione che (a/b)*b + a%b == a, che sembra abbastanza naturale. a/brestituisce sempre il risultato aritmetico senza la parte frazionaria (troncando verso 0). Di conseguenza, a%bha lo stesso segno di ao è 0.

La divisione -1/256produce 0e quindi -1%256deve essere -1al fine di soddisfare la condizione di cui sopra ( (-1%256)*256 + -1%256 == -1). Questo è ovviamente diverso da quello -1&0xFFche è 0xFF. Pertanto, il compilatore non può ottimizzare nel modo desiderato.

La sezione pertinente nella norma C ++ [expr.mul §4] a partire da N4606 afferma:

Per gli operandi integrali l' /operatore fornisce il quoziente algebrico con qualsiasi parte frazionaria scartata; se il quoziente a/bè rappresentabile nel tipo di risultato, (a/b)*b + a%bè uguale a a[...].

Abilitazione dell'ottimizzazione

Tuttavia, utilizzando i unsignedtipi, l'ottimizzazione sarebbe completamente corretta , soddisfacendo la convenzione di cui sopra:

unsigned(-1)%256 == 0xFF

Si veda anche questo .

Altre lingue

Questo è gestito in modo molto diverso nei diversi linguaggi di programmazione, come puoi cercare su Wikipedia .


50

Dal C ++ 11, num % 256deve essere non positivo se numè negativo.

Quindi il modello di bit dipende dall'implementazione dei tipi con segno sul tuo sistema: per un primo argomento negativo, il risultato non è l'estrazione degli 8 bit meno significativi.

Sarebbe una questione diversa se numnel tuo caso è stato unsigned: in questi giorni mi piacerebbe quasi si aspettano un compilatore per rendere l'ottimizzazione che lei cita.


6
Quasi ma non del tutto. Se numè negativo, allora num % 256è zero o negativo (aka non positivo).
Nayuki,

5
Quale IMO, è un errore nello standard: matematicamente l'operazione modulo dovrebbe prendere il segno del divisore, 256 in questo caso. Per capire perché considerarlo (-250+256)%256==6, ma (-250%256)+(256%256)deve essere, secondo lo standard, "non positivo", e quindi no 6. Rompere l'associatività in questo modo ha effetti collaterali nella vita reale: ad esempio quando si calcola il rendering "zoom out" in coordinate intere si deve spostare l'immagine per avere tutte le coordinate non negative.
Michael,

2
@Michael Modulus non è mai stato distributivo per addizione ("associativo" è il nome sbagliato per questa proprietà!), Anche se segui la definizione matematica alla lettera. Ad esempio, (128+128)%256==0ma (128%256)+(128%256)==256. Forse c'è una buona obiezione al comportamento specificato, ma non mi è chiaro che sia quello che hai detto.
Daniel Wagner,

1
@DanielWagner, hai ragione, certo, ho sbagliato a parlare di "associativo". Tuttavia, se si mantiene il segno del divisore e si calcola tutto nell'aritmetica modulare, la proprietà distributiva detiene; nel tuo esempio avresti 256==0. La chiave è avere esattamente Ni valori possibili Nnell'aritmetica del modulo , che è possibile solo se tutti i risultati sono nell'intervallo 0,...,(N-1), no -(N-1),...,(N-1).
Michael,

6
@Michael: Tranne% non è un operatore modulo, è un operatore restante .
Joren,

11

Non ho una visione telepatica del ragionamento del compilatore, ma nel caso %c'è la necessità di trattare valori negativi (e arrotondamenti di divisione verso lo zero), mentre con &il risultato sono sempre gli 8 bit inferiori.

L' saristruzione mi suona come "sposta l'aritmetica a destra", riempiendo i bit vuoti con il valore del bit di segno.


0

Matematicamente parlando, modulo è definito come il seguente:

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

Questo qui dovrebbe chiarirlo per te. Possiamo eliminare floor per numeri interi perché la divisione dei numeri interi è equivalente a floor (a / b). Tuttavia, se il compilatore dovesse usare un trucco generale come suggerisci, dovrebbe funzionare per tutti a e tutti b. Sfortunatamente, questo non è il caso. Dal punto di vista matematico, il tuo trucco è corretto al 100% per numeri interi senza segno (vedo che una risposta indica che gli interi con segno si interrompono, ma non posso confermarlo né negarlo poiché -a% b dovrebbe essere positivo). Tuttavia, puoi fare questo trucco per tutti b? Probabilmente no. Ecco perché il compilatore non lo fa. Dopotutto, se modulo fosse facilmente scritto come un'operazione bit a bit, aggiungeremmo un circuito modulo come per l'aggiunta e le altre operazioni.


4
Penso che tu confonda "piano" con "troncato". I primi computer usavano il troncamento della divisione perché spesso è più facile da calcolare rispetto alla divisione pavimentata anche nei casi in cui le cose si dividono uniformemente. Ho visto pochissimi casi in cui la divisione troncata era più utile di quanto sarebbe stata la divisione pavimentata, ma molte lingue seguono l'esempio di FORTRAN nell'uso della divisione troncata.
supercat

@supercat Matematicamente parlando, il pavimento è troncato. Entrambi hanno lo stesso effetto. Potrebbero non essere implementati allo stesso modo in un computer, ma fanno la stessa cosa.
user64742

5
@TheGreatDuck: non sono gli stessi per i numeri negativi. Il piano di -2.3è -3, mentre se si tronca -2.3a un numero intero si ottiene -2. Vedi en.wikipedia.org/wiki/Truncation . "per il troncamento di numeri negativi non si arrotonda nella stessa direzione della funzione piano". E il comportamento di %numeri negativi è precisamente la ragione per cui l'OP sta vedendo il comportamento descritto.
Mark Dickinson,

@MarkDickinson Sono abbastanza sicuro che modulo in c ++ dia valori positivi per divisori positivi, ma non ho intenzione di discutere.
user64742

1
@TheGreatDuck - vedi esempio: cpp.sh/3g7h (Nota che C ++ 98 non ha definito quale delle due possibili varianti è stata usata, ma che standard più recenti lo fanno, quindi è possibile che tu abbia usato un'implementazione di C ++ in passato lo faceva diversamente ...)
Periata Breatta l'
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.