Qual è la differenza tra "4 - 12" e "4 + (-12)"? [duplicare]

Nov 20 2020

Provo a confrontare le seguenti espressioni:

1) 
    mov al, 4
    mov bl, 12
    sub al, bl ; CF == 1

    0000 0100 (4)
   +
    1111 0100 (-12)
   = 
    1111 1000 (-8 == 248) 

2) 
    mov al, 4
    mov bl, -12
    add al, bl ; CF == 0

    0000 0100 (4)
   +
    1111 0100 (-12)
   = 
    1111 1000 (-8 == 248) 

I risultati sono identici, ma i flag carry non lo sono. Perché? La sottrazione realizzata aggiungendo al valore del complemento a due.

Risposte

4 fuz Nov 20 2020 at 22:56

La sottrazione non è uguale all'addizione del complemento a due su x86. Per capire quale valore assumerà il carry flag, devi invece eseguire una lunga sottrazione:

    0000 0100
-   0000 1100
-------------
  1 1111 1000

Vedi come rimane un prestito alla fine? Questo prestito è ciò che imposta il contrassegno di riporto (cioè il trasporto è uguale a prendere in prestito ).

Alcune altre architetture come ARM implementano effettivamente la sottrazione come addizione, ma non lo fanno come aggiunta del complemento a due ma piuttosto come aggiunta del complemento a uno e di un riporto extra. Questo è significativo quando si sottrae 0.

Ad esempio, il tuo metodo produrrebbe per 12-0:

    0000 1100
+   0000 0000 (- 0000 0000 => + 0000 0000)
-------------
  0 0000 1100

con un chiaro riporto. Ma quello che succede in realtà è

    0000 1100
+   1111 1111 (- 0000 0000 => +1111 1111 + 1)
+           1
-------------
  1 0000 1100

con un riporto. Questo dettaglio è importante perché altrimenti i confronti con 0 non funzionerebbero correttamente. In questo schema, il carry è indicato ogni volta che non c'è prestito (cioè il carry è un prestito complementare).

Il modo in cui lo fa Intel e il modo in cui lo fa ARM producono sempre lo stesso risultato, tranne per il fatto che il carry flag è esattamente il contrario. Quindi, ogni volta che ARM imposta il carry, Intel lo cancella e viceversa.

Entrambi gli approcci alla semantica di sottrazione sono abbastanza comuni. L'approccio ARM è leggermente più facile da implementare in quanto consente l'uso diretto del sommatore per la sottrazione senza dover toccare affatto il riporto. Con l'approccio Intel, il carry in e out deve essere completato quando viene eseguita una sottrazione, ma le porte extra per farlo non contano nel grande schema delle cose. D'altra parte, l'approccio di Intel è più intuitivo per i programmatori in quanto pensare al carry flag indicando anche il prestito ha più senso se si visualizza l'operazione eseguita come una lunga sottrazione.