Interi firmati o non firmati

Sono corretto nel dire che la differenza tra un intero con segno e senza segno è:

  1. Unsigned può contenere un valore positivo più grande e nessun valore negativo.
  2. Unsigned utilizza il bit iniziale come parte del valore, mentre la versione firmata utilizza il left-most-bit per identificare se il numero è positivo o negativo.
  3. gli interi con segno possono contenere sia numeri positivi che negativi.

Qualche altra differenza?

  1. Sì.

  2. Esistono diversi modi per rappresentare gli interi con segno. Il modo più semplice per visualizzare è usare il bit più a sinistra come bandiera ( segno e grandezza ), ma più comune è il complemento a due . Entrambi sono in uso nella maggior parte dei microprocessori moderni: il punto mobile usa il segno e la magnitudine, mentre l’aritmetica dei numeri interi utilizza il complemento a due.

Entrerò in differenze a livello di hardware, su x86. Questo è per lo più irrilevante a meno che non si stia scrivendo un compilatore o usando il linguaggio assembly. Ma è bello saperlo.

Innanzitutto, x86 ha il supporto nativo per la rappresentazione del complemento a due dei numeri firmati. È ansible utilizzare altre rappresentazioni, ma ciò richiederebbe più istruzioni e in genere perdere tempo al processore.

Cosa intendo per “supporto nativo”? Fondamentalmente intendo che ci sono un insieme di istruzioni che usi per i numeri senza segno e un altro che usi per i numeri firmati. I numeri non firmati possono essere contenuti negli stessi registri dei numeri firmati e, in effetti, è ansible unire istruzioni firmate e non firmate senza preoccuparsi del processore. Spetta al compilatore (o al programmatore dell’assembly) tenere traccia di se un numero è firmato o meno, e utilizzare le istruzioni appropriate.

In primo luogo, i numeri del complemento a due hanno la proprietà che l’addizione e la sottrazione sono identiche a quelle dei numeri senza segno. Non fa differenza se i numeri sono positivi o negativi. (Quindi vai avanti e ADD e SUB tuoi numeri senza preoccupazioni.)

Le differenze iniziano a mostrare quando si tratta di confronti. x86 ha un modo semplice per differenziarli: sopra / sotto indica un confronto senza segno e maggiore / minore di indica un confronto firmato. (Ad esempio, JAE significa “Salta se sopra o uguale” ed è senza segno.)

Ci sono anche due serie di istruzioni di moltiplicazione e divisione per gestire interi con segno e senza segno.

Infine: se vuoi verificare, ad esempio, un overflow, lo faresti diversamente per i numeri firmati e per quelli senza segno.

Fondamentalmente ha chiesto solo su firmato e non firmato. Non so perché la gente stia aggiungendo cose extra in questo. Lascia che ti dica una risposta.

  1. Unsigned: Consiste solo di valore positivo, ovvero da 0 a 255.

  2. Firmato: Consiste sia di valori negativi che positivi ma in diversi formati come

    • Da -1 a -128
    • Da 0 a +127

E questa spiegazione completa riguarda il sistema a 8 bit.

Solo pochi punti per la completezza:

  • questa risposta sta discutendo solo rappresentazioni di interi. Potrebbero esserci altre risposte per virgola mobile;

  • la rappresentazione di un numero negativo può variare. Il più comune (di gran lunga – è quasi universale oggi) in uso oggi è il complemento a due . Altre rappresentazioni includono il complemento (piuttosto raro) e la magnitudine firmata (incredibilmente raro – probabilmente usato solo su pezzi di musei) che sta semplicemente usando il bit più alto come indicatore di segno con i bit rimanenti che rappresentano il valore assoluto del numero.

  • Quando si utilizza il complemento a due, la variabile può rappresentare un intervallo più ampio (di uno) di numeri negativi rispetto ai numeri positivi. Questo perché lo zero è incluso nei numeri ‘positivi’ (poiché il bit del segno non è impostato su zero), ma non i numeri negativi. Ciò significa che il valore assoluto del numero negativo più piccolo non può essere rappresentato.

  • quando si utilizza il complemento o la magnitudine firmata, è ansible avere zero come numero positivo o negativo (che è uno dei motivi per cui queste rappresentazioni non sono in genere utilizzate).

Secondo quanto appreso in class, gli interi con segno possono rappresentare sia numeri positivi che negativi, mentre gli interi senza segno sono solo non negativi.

Ad esempio, guardando un numero di 8 bit :

valori senza segno da 0 a 255

i valori con -128 vanno da -128 a 127

Tutto tranne il punto 2 è corretto. Esistono molte notazioni diverse per i segni firmati, alcune implementazioni usano il primo, altri usano l’ultimo e altri ancora usano qualcosa di completamente diverso. Tutto dipende dalla piattaforma con cui stai lavorando.

Un’altra differenza è quando si converte tra numeri interi di dimensioni diverse.

Ad esempio, se si estrae un numero intero da un stream di byte (ad esempio 16 bit per semplicità), con valori senza segno, è ansible:

 i = ((int) b[j]) < < 8 | b[j+1] 

(probabilmente dovrebbe lanciare il secondo byte, ma suppongo che il compilatore farà la cosa giusta)

Con i valori firmati dovresti preoccuparti dell'estensione del segno e fare:

 i = (((int) b[i]) & 0xFF) < < 8 | ((int) b[i+1]) & 0xFF 

In generale è corretto. Senza sapere nulla di più sul perché stai cercando le differenze non riesco a pensare a nessun altro differenziatore tra firmato e non firmato.

Al di là di ciò che altri hanno detto, in C, non è ansible traboccare un intero senza segno; il comportamento è definito come modulo aritmetico. È ansible eseguire l’overflow di un intero con segno e, in teoria (sebbene non in pratica sugli attuali sistemi mainstream), l’overflow potrebbe causare un errore (forse simile a un errore di divisione per zero).

  1. Sì, il numero intero senza segno può memorizzare un valore elevato.
  2. No, ci sono diversi modi per mostrare valori positivi e negativi.
  3. Sì, il numero intero con segno può contenere valori sia positivi sia negativi.

(in risposta alla seconda domanda) Usando solo un bit di segno (e non il complemento a 2), si può finire con -0. Non molto carina.

Gli interi senza segno sono molto più propensi a catturarti in una trappola particolare rispetto agli interi con segno. La trappola deriva dal fatto che mentre 1 e 3 sopra sono corretti, entrambi i tipi di numeri interi possono essere assegnati a un valore al di fuori dei limiti di ciò che può “contenere” e verrà convertito automaticamente.

 unsigned int ui = -1; signed int si = -1; if (ui < 0) { printf("unsigned < 0\n"); } if (si < 0) { printf("signed < 0\n"); } if (ui == si) { printf("%d == %d\n", ui, si); printf("%ud == %ud\n", ui, si); } 

Quando lo esegui, otterrai il seguente risultato anche se entrambi i valori sono stati assegnati a -1 e sono stati dichiarati in modo diverso.

 signed < 0 -1 == -1 4294967295d == 4294967295d 

Gli interi firmati in C rappresentano i numeri. Se a e b sono variabili di tipi interi con segno, lo standard non richiederà mai che un compilatore renda l’espressione a+=b store in a diverso dalla sum aritmetica dei rispettivi valori. Per essere sicuro, se la sum aritmetica non si adatterebbe in a , il processore potrebbe non essere in grado di metterlo lì, ma lo standard non richiederebbe al compilatore di troncare o avvolgere il valore, o fare qualsiasi altra cosa per quella questione se i valori che superare i limiti per i loro tipi. Si noti che mentre lo standard non lo richiede, le implementazioni C sono in grado di intercettare gli overflow aritmetici con valori firmati.

Gli interi senza segno in C si comportano come anelli algebrici astratti di interi che sono congruenti al modulo di una potenza di due, tranne che in scenari che implicano conversioni o operazioni con tipi più grandi. Convertire un numero intero di qualsiasi dimensione in un tipo senza segno a 32 bit produrrà il membro corrispondente alle cose che sono congruenti a quel numero intero di 4.294.967.296. Il motivo per cui sottrarre 3 da 2 produce 4.294.967.295 è che l’aggiunta di qualcosa di congruente a 3 a qualcosa di congruente a 4.294.967.295 produrrà qualcosa di congruente a 2.

I tipi di anelli algebrici astratti sono spesso cose pratiche da avere; sfortunatamente, C usa la firma come fattore decisivo per stabilire se un tipo debba comportarsi come un anello. Peggio ancora, i valori senza segno vengono considerati come numeri anziché come membri dell’anello quando vengono convertiti in tipi più grandi, e i valori senza segno più piccoli di quelli int vengono convertiti in numeri quando su di essi viene eseguita un’aritmetica. Se v è un uint32_t che equivale a 4,294,967,294 , quindi v*=v; dovrebbe rendere v=4 . Sfortunatamente, se int è a 64 bit, non si può dire cosa v*=v; potrebbe fare.

Dato lo standard così com’è, suggerirei di utilizzare i tipi senza segno nelle situazioni in cui si desidera il comportamento associato agli anelli algebrici e i tipi firmati quando si vuole rappresentare i numeri. È un peccato che C abbia fatto le distinzioni come ha fatto, ma sono quello che sono.

L’unica differenza garantita tra un valore firmato e un valore non firmato in C è che il valore firmato può essere negativo, 0 o positivo, mentre un non firmato può essere solo 0 o positivo. Il problema è che C non definisce il formato dei tipi (quindi non sai che i tuoi numeri interi sono in complemento a due). A rigor di termini i primi due punti che hai citato non sono corretti.

È necessario utilizzare numeri interi non firmati durante la programmazione su sistemi embedded. Nei loop, quando non è necessario inserire interi con segno, l’uso di numeri interi senza segno salverà la sicurezza necessaria per la progettazione di tali sistemi.