Perché vengono implementati i numeri senza segno?


12

Non riesco a capire perché i sistemi a microprocessore implementano numeri senza segno. Immagino che il costo sia solo il doppio del numero di rami condizionali, dal momento che maggiore, minore di, ecc., Ha bisogno di un algoritmo diverso da quello firmato, ci sono ancora algoritmi per i quali i numeri senza segno sono un vantaggio significativo?

la mia domanda in parte è perché hanno bisogno di essere nel set di istruzioni invece di essere supportati da un compilatore?


27
Fondamentalmente i numeri senza segno sono lo standard, i segni firmati sono implementati per fornire numeri negativi.
Pieter B,

37
Molti dati del mondo non sono numerici. I dati non numerici possono essere facilmente manipolati utilizzando tipi non firmati. Il fatto che Java non abbia tipi numerici non firmati è un errore, il che causa molti bug in cose che devono manipolare dati non numerici (ad es. Compressione, ecc.).
Erik Eidt,

6
@jtw Erik afferma che non esiste un colore di pixel negativo o un carattere negativo. Quindi sarebbe inutile utilizzare numeri interi con segno per questo, si dovrebbe rinunciare a metà dello spazio degli indirizzi.
Martin Maat,

26
Non sono sicuro di essere solo qui, ma trovo sorprendentemente raro che ho bisogno di numeri interi firmati durante lo sviluppo di applicazioni. Quasi sempre ciò di cui ho bisogno è un numero naturale (senza segno) (una dimensione positiva, di solito) o un numero in virgola mobile con segno. Le eccezioni sono cose come la valuta, ma quelle sono molto rare; per me, gli interi senza segno sono la norma e gli interi con segno sono l'eccezione!
Thomas,

11
Dal punto di vista della CPU, praticamente tutti i numeri sono senza segno. Alcune istruzioni possono interpretare i bit come firmati (.eg arithmetic-right-shift), ma in realtà il complemento a due consente alla CPU di trattare gli interi con segno come interi senza segno, il che significa che la CPU non richiede (o molto poco) circuiti speciali per supportare entrambi .
Cornstalks,

Risposte:


39

I numeri senza segno sono un'interpretazione di una sequenza di bit. È anche l'interpretazione più semplice e più utilizzata internamente alla CPU perché indirizzi e codici operativi sono semplicemente bit. L'indirizzamento della memoria / stack e l'aritmetica sono i fondamenti del microprocessore, beh, dell'elaborazione. Salendo nella piramide dell'astrazione, un'altra interpretazione frequente dei bit è come carattere (ASCII, Unicode, EBCDIC). Quindi ci sono altre interpretazioni come IEEE virgola mobile, RGBA per la grafica e così via. Nessuno di questi sono numeri con segno semplice (IEEE FP non è semplice e l'aritmetica che li utilizza è molto complicata).

Inoltre, con l'aritmetica senza segno è abbastanza semplice (se non più efficacemente) implementare gli altri. Il contrario non è vero.


3
EBCDIC ha solo un "I".
Ruslan,

4
@Ruslan - ma è pronunciato come se ne avesse due. <g>
Pete Becker,

5
@PeteBecker no non lo è. EBCDIC è pronunciato eb -see-dick.
Mike Nakis,

19

La maggior parte del costo dell'hardware per le operazioni di confronto è la sottrazione. L'output della sottrazione utilizzata dal confronto è essenzialmente tre bit di stato:

  • se tutti i bit sono zero (ovvero la condizione uguale),
  • il bit di segno del risultato
  • il bit di trasferimento della sottrazione (ovvero il 33 ° bit di ordine superiore su un computer a 32 bit)

Con la corretta combinazione di test di questi tre bit dopo l'operazione di sottrazione, possiamo determinare tutte le operazioni relazionali con segno, nonché tutte le operazioni relazionali senza segno (questi bit sono anche il modo in cui viene rilevato l'overflow, con segno rispetto a senza segno). Lo stesso hardware ALU di base può essere condiviso per implementare tutti questi confronti (per non parlare delle istruzioni di sottrazione), fino al controllo finale di quei tre bit di stato, che differisce secondo il confronto relazionale desiderato. Quindi, non è molto hardware aggiuntivo.

L'unico costo reale è la necessità della codifica di ulteriori modalità di confronto nell'architettura del set di istruzioni, che può ridurre marginalmente la densità delle istruzioni. Tuttavia, è abbastanza normale che l'hardware abbia molte istruzioni che non sono utilizzate da nessuna lingua.


1
Il confronto di numeri non firmati non richiede sottrazione. può essere ottenuto dal confronto bit a destra da sinistra a destra.
Jonathan Rosenne,

10
@JonathanRosenne Ma non è così che i processori lo implementano. Al contrario, è quasi impensabile per un processore a complemento a 2 non implementare la sottrazione (con o senza carry / loan) nella sua ALU. Il pensiero immediato di un designer è quello di utilizzare questo ALU necessario per uccidere un altro uccello con la stessa pietra, confronto. Il confronto diventa quindi semplicemente una sottrazione in cui il risultato non viene riscritto nel file di registro.
Iwillnotexist Idonotexist,

4
+1: questa è la risposta corretta alla domanda posta. Riassumendo: perché l'implementazione di operazioni non firmate è quasi gratuita quando hai già implementato firmato .
Periata Breatta,

10
@PeriataBreatta Funziona anche al contrario. I numeri con e senza segno nelle CPU moderne sono quasi identici, il che è il punto principale che l'OP non ha riconosciuto. Anche le istruzioni di confronto sono le stesse per quelle firmate e non firmate - questo è uno dei motivi per cui il complemento di due ha vinto le guerre di interi firmati :)
Luaan,

3
@svidgen> come hanno detto altre risposte, funziona al contrario. La preoccupazione principale sono i numeri senza segno, che sono usati praticamente per tutto (indirizzo di memoria, io / porte, rappresentazioni di caratteri, ...). I numeri firmati diventano poco costosi una volta che non hai firmato e sono utili nel raro caso in cui siano desiderabili.
extra

14

Perché, se devi contare qualcosa che è sempre >= 0 , dovresti tagliare inutilmente lo spazio di conteggio a metà usando numeri interi con segno.

Prendi in considerazione INT PK con incremento automatico che potresti inserire nelle tabelle del database. Se si utilizza un numero intero con segno lì, la tabella memorizza HALF il maggior numero di record possibile per le stesse dimensioni del campo senza alcun vantaggio.

O gli ottetti di un colore RGBa. Non vogliamo iniziare goffamente a contare questo concetto di numero naturalmente positivo con un numero negativo. Un numero con segno o spezzerebbe il modello mentale o dimezzerebbe il nostro spazio. Un numero intero senza segno non solo corrisponde al concetto, ma fornisce il doppio della risoluzione.

Dal punto di vista hardware, i numeri interi senza segno sono semplici. Sono probabilmente la struttura di bit più semplice su cui eseguire la matematica. E, senza dubbio, potremmo semplificare l'hardware simulando i tipi interi (o anche in virgola mobile!) In un compilatore. Quindi, perché sono implementati sia interi che non firmati e firmati nell'hardware?

Bene ... prestazione!

È più efficiente implementare interi con segno nell'hardware che nel software. L'hardware può essere istruito per eseguire la matematica su entrambi i tipi di numero intero in una singola istruzione. E questo è molto buono , perché l'hardware rompe i bit più o meno in parallelo. Se si tenta di simularlo nel software, il tipo intero che si sceglie di "simulare" richiederà indubbiamente molte istruzioni e sarà notevolmente più lento.


2
Lungo queste linee, è possibile salvare un'operazione durante il controllo dei limiti dell'array. Se si utilizza un numero intero senza segno, è necessario solo verificare che l'indice fornito sia inferiore alla dimensione dell'array (poiché non può essere negativo).
riwalk

2
@ dan04 Certamente può ... Ma, se stai usando un int auto-incrementante a partire da 0 o 1, che è una pratica abbastanza comune, hai precluso l'uso della metà dei tuoi numeri disponibili. E mentre è possibile pensare che inizi a contare da -2 ^ 31 (o qualsiasi altra cosa), avrai un potenziale caso "limite" nel mezzo del tuo spazio ID.
svidgen,

1
Dividere il campo a metà è una specie di argomento debole. È probabile che se la tua app richiede più di 2 miliardi, ne richiedono anche più di 4 miliardi.
corsiKa

1
@corsiKa: per questo motivo, se richiede più di 4, probabilmente ne richiede 8, quindi 16, ecc. Dove finisce?
whatsisname

1
@whatsisname generalmente, usi tipi interi di 8, 16, 32 o 64 bit. Dire che unsigned è meglio perché ottieni tutti i 32 bit invece dell'intervallo limitato di 31 bit di spazio intero positivo in un byte con segno non è molto importante nella maggior parte dei casi.
corsiKa

9

La tua domanda è composta da due parti:

  1. Qual è lo scopo degli interi senza segno?

  2. Gli interi senza segno valgono il problema?

1. Qual è lo scopo degli interi senza segno?

I numeri senza segno, abbastanza semplicemente, rappresentano una classe di quantità per le quali i valori negativi sono privi di significato. Certo, potresti dire che la risposta alla domanda "quante mele ho?" potrebbe essere un numero negativo se devi delle mele a qualcuno, ma per quanto riguarda la domanda "quanta memoria ho?" --non puoi avere una quantità negativa di memoria. Pertanto, gli interi senza segno sono molto adatti a rappresentare tali quantità e hanno il vantaggio di poter rappresentare il doppio dell'intervallo di valori positivi rispetto a quelli con segno. Ad esempio, il valore massimo che è possibile rappresentare con un numero intero con segno a 16 bit è 32767, mentre con un numero intero senza segno a 16 bit è 65535.

2. I numeri interi senza segno valgono il problema?

Gli interi senza segno non rappresentano alcun problema, quindi sì, ne valgono la pena. Vedete, non richiedono un set aggiuntivo di "algoritmi"; i circuiti richiesti per implementarli sono un sottoinsieme dei circuiti necessari per implementare numeri interi con segno.

Una CPU non ha un moltiplicatore per numeri interi con segno e un moltiplicatore diverso per numeri senza segno; ha solo un moltiplicatore, che funziona in modo leggermente diverso a seconda della natura dell'operazione. Il supporto della moltiplicazione firmata richiede un po 'più di circuiti rispetto a quelli non firmati, ma poiché deve essere comunque supportato, la moltiplicazione senza segno è praticamente gratuita, è inclusa nel pacchetto.

Per quanto riguarda l'addizione e la sottrazione, non vi è alcuna differenza nei circuiti. Se leggete sulla cosiddetta rappresentazione di complementi di due di interi , scoprirete che è progettato in modo così intelligente che queste operazioni possono essere eseguite esattamente allo stesso modo, indipendentemente dalla natura degli interi.

Anche il confronto funziona allo stesso modo, poiché non è altro che sottrarre e scartare il risultato, l'unica differenza è nelle istruzioni del ramo condizionale (salta), che funzionano osservando i diversi flag della CPU impostati dal istruzione precedente (confronto). In questa risposta: /programming//a/9617990/773113 puoi trovare una spiegazione di come funzionano sull'architettura Intel x86. Quello che succede è che la designazione di un'istruzione di salto condizionale come firmata o non firmata dipende dalle bandiere che esamina.


1
la mia domanda presupponeva tutto questo, con algoritmo intendevo che la regola per meno di maggiore di ecc era diversa. Il costo che vedo sta avendo molte istruzioni extra. Se i programmi di alto livello amano vedere i dati come schemi di bit, questo può essere facilmente implementato, per esempio da un compilatore
jtw

3
@jtw - ma il punto è che quelle istruzioni extra sono in realtà molto simili alle istruzioni richieste per i numeri firmati, e quasi tutti i circuiti richiesti per loro possono essere condivisi . Il costo aggiuntivo per l'implementazione di entrambi i tipi è quasi zero.
Periata Breatta,

1
sì, questo risponde alla mia domanda, aggiungendo che le istruzioni extra sul ramo hanno un piccolo costo e sono spesso utili in pratica
jtw

1
"Le operazioni non firmate richiedono una gestione extra quando si tratta di divisione e moltiplicazione" Penso che tu l'abbia indietro. La moltiplicazione e la divisione sono più semplici con valori non firmati. La gestione aggiuntiva è necessaria per gestire gli operandi firmati.
Cody Gray,

@CodyGray Sapevo che qualcuno si sarebbe presentato per dire questo. Hai ragione, ovviamente. Questo è il ragionamento alla base della mia affermazione, che inizialmente avevo omesso per brevità: una CPU non poteva probabilmente offrire una moltiplicazione e divisione non firmate, perché le versioni firmate sono così utili. È un dato di fatto, la moltiplicazione e la divisione firmate sono un must; unsigned sono opzionali. Pertanto, se deve essere offerto anche unsigned , questo può essere visto come richiedendo un po 'più di circuiti.
Mike Nakis,

7

I microprocessori sono intrinsecamente non firmati. I numeri firmati sono la cosa implementata, non viceversa.

I computer possono e funzionano bene senza numeri firmati, ma noi, umani che hanno bisogno di numeri negativi, è stata inventata la firma.


4
Molti microprocessori hanno istruzioni firmate e non firmate per varie operazioni.
whatsisname

1
@whatsisname: è il contrario: molti microprocessori hanno solo istruzioni non firmate. Un paio hanno istruzioni firmati. Questo perché con l'aritmetica del complemento 2s il valore del bit è lo stesso indipendentemente dal tempo in cui il numero è firmato o non firmato e il modo in cui si legge il numero è solo una questione di interpretazione, quindi è più facile implementarlo come una funzione di compilazione. Generalmente solo i vecchi micro che presumono che i programmatori non usino i compilatori hanno istruzioni firmate per rendere leggibile il codice assembly.
slebetman,

3

Perché hanno un altro bit che è facilmente disponibile per l'archiviazione e non devi preoccuparti di numeri negativi. Non c'è molto altro.

Ora, se hai bisogno di un esempio di dove avresti bisogno di questo bit in più, ci sono molte cose da trovare se guardi.

Il mio esempio preferito viene dai bitboard nei motori di scacchi. Ci sono 64 quadrati su una scacchiera, quindi unsigned longfornisce una memoria perfetta per una varietà di algoritmi che ruotano attorno alla generazione di mosse. Considerando il fatto che è necessario eseguire operazioni binarie (così come operazioni di spostamento !!), è facile capire perché è più facile non doversi preoccupare di ciò che accade di speciale se l'MSB è impostato. Può essere fatto con firmato a lungo, ma è molto più facile da usare senza segno.


3

Avere un background matematico puro, questa è una presa leggermente più matematica per chiunque sia interessato.

Se iniziamo con un intero con segno e senza segno a 8 bit, ciò che abbiamo è fondamentalmente gli interi modulo 256, per quanto riguarda l'addizione e la moltiplicazione, a condizione che il complemento di 2 sia usato per rappresentare numeri interi negativi (ed è così che fa ogni processore moderno) .

Dove le cose differiscono è in due posti: uno sono le operazioni di confronto. In un certo senso, gli interi modulo 256 sono meglio considerati un cerchio di numeri (come gli interi modulo 12 fanno su un quadrante analogico vecchio stile). Per rendere significativi i confronti numerici (è x <y), è necessario decidere quali numeri sono inferiori rispetto ad altri. Dal punto di vista del matematico, vogliamo in qualche modo incorporare gli interi modulo 256 nell'insieme di tutti gli interi. Mappare l'intero a 8 bit la cui rappresentazione binaria è tutti zeri sull'intero 0 è la cosa ovvia da fare. Possiamo quindi procedere a mappare gli altri in modo che '0 + 1' (il risultato dell'azzeramento di un registro, ad esempio ax, e l'incremento di uno, tramite 'inc ax') vada all'intero 1 e così via. Possiamo fare lo stesso con -1, ad esempio mappando '0-1' sull'intero -1 e '0-1-1' all'intero -2. Dobbiamo garantire che questo incorporamento sia una funzione, quindi non è possibile mappare un singolo numero intero a 8 bit su due numeri interi. In quanto tale, ciò significa che se mappiamo tutti i numeri nell'insieme di numeri interi, 0 sarà lì, insieme ad alcuni numeri inferiori a 0 e alcuni più di 0. Esistono essenzialmente 255 modi per farlo con un numero intero a 8 bit (secondo al minimo desiderato, da 0 a -255). Quindi puoi definire 'x <y' in termini di '0 <y - x'.

Esistono due casi d'uso comuni, per i quali è ragionevole il supporto hardware: uno con tutti i numeri interi diversi da zero che è maggiore di 0 e uno con una divisione di circa 50/50 attorno a 0. Tutte le altre possibilità sono facilmente emulabili traducendo i numeri tramite un 'aggiunta aggiuntiva e sub "prima delle operazioni, e la necessità di questo è così rara che non riesco a pensare a un esempio esplicito nel software moderno (dal momento che puoi semplicemente lavorare con una mantissa più grande, diciamo 16 bit).

L'altro problema è quello di mappare un numero intero a 8 bit nello spazio di numeri interi a 16 bit. -1 va a -1? Questo è quello che vuoi se 0xFF deve rappresentare -1. In questo caso, l'estensione dei segni è la cosa ragionevole da fare, in modo che 0xFF vada a 0xFFFF. D'altra parte, se 0xFF doveva rappresentare 255, lo si desidera mappare a 255, quindi a 0x00FF, anziché a 0xFFFF.

Questa è la differenza tra le operazioni 'shift' e 'shift aritmetico'.

Alla fine, tuttavia, si riduce al fatto che gli int nel software non sono numeri interi, ma rappresentazioni in binario e solo alcuni possono essere rappresentati. Quando si progetta hardware, è necessario fare delle scelte su cosa fare in modo nativo nell'hardware. Poiché con il complemento a 2 le operazioni di addizione e moltiplicazione sono identiche, ha senso rappresentare numeri negativi in ​​questo modo. Quindi è solo una questione di operazioni che dipendono da quali numeri interi rappresentino le tue rappresentazioni binarie.


Mi piace l'approccio matematico, ma invece di pensare semplicemente alla promozione a una dimensione specifica più grande, penso che sia più bello generalizzare in termini di operazioni su numeri binari di lunghezza infinita. Sottrai 1 da qualsiasi numero le cui k cifre più a destra sono 0 e le k cifre più a destra del risultato saranno 1, e si può dimostrare per induzione che se si eseguisse la matematica con un numero infinito di bit, ogni bit sarebbe 1. Per senza segno matematica, si ignorano tutti tranne i bit inferiori di un numero.
supercat,

2

Esaminiamo i costi di implementazione per l'aggiunta di numeri interi senza segno a un progetto di CPU con numeri interi con segno esistenti.

Una CPU tipica richiede le seguenti istruzioni aritmetiche:

  • ADD (che aggiunge due valori e imposta un flag se l'operazione trabocca)
  • SUB (che sottrae un valore da un altro e imposta vari flag - ne discuteremo di seguito)
  • CMP (che è essenzialmente "SUB e scarta il risultato, mantieni solo i flag")
  • LSH (spostamento a sinistra, imposta una bandiera su overflow)
  • RSH (spostamento a destra, imposta un flag se un 1 viene spostato fuori)
  • Varianti di tutte le istruzioni di cui sopra che gestiscono il trasporto / prestito dalle bandiere, consentendo quindi di incatenare le istruzioni comodamente per operare su tipi più grandi rispetto ai registri della CPU
  • MUL (moltiplica, imposta flag, ecc. - non universalmente disponibile)
  • DIV (dividi, imposta flag, ecc. - molte architetture CPU mancano di questo)
  • Passa da un tipo intero più piccolo (ad es. 16 bit) a uno più grande (ad es. 32 bit). Per numeri interi con segno, questo di solito si chiama MOVSX (sposta con estensione del segno).

Ha anche bisogno di istruzioni logiche:

  • Ramo su zero
  • Ramo su maggiore
  • Ramo su meno
  • Ramo su troppo pieno
  • Versioni negate di tutto quanto sopra

Per eseguire i rami sopra su confronti di numeri interi con segno, il modo più semplice è di impostare l'istruzione SUB per impostare i seguenti flag:

  • Zero. Impostare se la sottrazione ha prodotto un valore pari a zero.
  • Overflow. Impostare se la sottrazione ha preso in prestito un valore dal bit più significativo.
  • Cartello. Impostato sul bit di segno del risultato.

Quindi i rami aritmetici vengono implementati come segue:

  • Branch on zero: se è impostato lo zero flag
  • Ramo su meno: se il flag di segno è diverso dal flag di overflow
  • Ramo su maggiore: se il flag del segno è uguale al flag di overflow e il flag zero è chiaro.

Le negazioni di questi dovrebbero ovviamente seguire da come sono implementate.

Quindi il tuo progetto esistente implementa già tutti questi per numeri interi con segno. Ora consideriamo cosa dobbiamo fare per aggiungere numeri interi senza segno:

  • ADD: l'implementazione di ADD è identica.
  • SUB: è necessario aggiungere un flag aggiuntivo: il flag carry viene impostato quando un valore viene preso in prestito oltre il bit più significativo del registro.
  • CMP - non cambia
  • LSH - non cambia
  • RSH: lo spostamento corretto per i valori con segno mantiene il valore del bit più significativo. Per i valori senza segno, dovremmo invece impostarlo su zero.
  • MUL - se la dimensione di uscita è la stessa come input, non è necessario alcun trattamento speciale (x86 ha avere un trattamento speciale, ma solo perché ha in uscita in un paio registro, ma è da notare che questa struttura è in realtà abbastanza raramente utilizzato, in modo da sarebbe un candidato più ovvio per uscire da un processore rispetto ai tipi non firmati)
  • DIV - nessuna modifica richiesta
  • Passa da un tipo più piccolo a un tipo più grande - devi aggiungere MOVZX, spostare con zero estensione. Si noti che MOVZX è estremamente semplice da implementare.
  • Branch su zero - invariato
  • Ramo su meno - salti quando si porta la bandiera impostata.
  • Ramo su maggiore - salti se portano bandiera e zero entrambi liberi.

Si noti che in ogni caso, le modifiche sono molto semplici e possono essere implementate semplicemente attivando o disattivando una piccola sezione dei circuiti o aggiungendo un nuovo registro flag che può essere controllato da un valore che deve essere calcolato come parte di l'implementazione dell'istruzione comunque.

Pertanto, il costo dell'aggiunta di istruzioni non firmate è molto ridotto . Per quanto riguarda il motivo per cui dovrebbe essere fatto , si noti che gli indirizzi di memoria (e gli offset negli array) sono valori intrinsecamente non firmati. Dato che i programmi impiegano molto tempo a manipolare gli indirizzi di memoria, avere un tipo che li gestisce correttamente rende i programmi più facili da scrivere.


grazie, questo risponde alla mia domanda, il costo è piccolo e le istruzioni sono spesso utili
jtw

1
La moltiplicazione senza segno a doppia dimensione è essenziale quando si esegue l'aritmetica a precisione multipla ed è probabilmente utile per un miglioramento della velocità complessivo superiore a 2x quando si esegue la crittografia RSA. Inoltre, la divisione è diversa nei casi firmati e non firmati, ma poiché il caso non firmato è più facile e la divisione è abbastanza rara e abbastanza lenta da aggiungere alcune istruzioni non farà molto male, la cosa più semplice da fare sarebbe implementare solo la divisione non firmata e poi avvolgilo con una logica di gestione dei segni.
supercat,

2

I numeri senza segno esistono in gran parte per gestire situazioni in cui è necessario un anello algebrico avvolgente (per un tipo senza segno a 16 bit, sarebbe l'anello di interi congruenti mod 65536). Prendi un valore, aggiungi un importo inferiore al modulo e la differenza tra i due valori sarà l'importo che è stato aggiunto. Come esempio nel mondo reale, se un contatore di utilità legge 9995 all'inizio di un mese e uno utilizza 23 unità, il contatore leggerà 0018 alla fine del mese. Quando si utilizza un tipo di anello algebrico, non è necessario fare nulla di speciale per gestire l'overflow. Sottraendo 9995 da 0018 si otterrà 0023, precisamente il numero di unità utilizzate.

Sul PDP-11, la macchina per la quale C fu implementata per la prima volta, non c'erano tipi interi senza segno ma i tipi con segno potevano essere usati per l'aritmetica modulare compresa tra 32767 e -32768 anziché tra 65535 e 0. Le istruzioni per numeri interi su alcuni altri le piattaforme non avvolgevano le cose in modo pulito, tuttavia; piuttosto che richiedere che le implementazioni devono emulare gli interi in complemento a due utilizzati nel PDP-11, la lingua invece aggiunto tipi senza segno, che per lo più hanno dovuto comportarsi come anelli algebriche, e ha permesso firmato intero tipi a comportarsi in altri modi in caso di overflow.

All'inizio di C, c'erano molte quantità che potevano superare 32767 (il comune INT_MAX) ma non 65535 (il comune UINT_MAX). È diventato quindi comune utilizzare tipi senza segno per contenere tali quantità (ad esempio size_t). Sfortunatamente, non c'è nulla nella lingua per distinguere tra tipi che dovrebbero comportarsi come numeri con un po 'più di intervallo positivo, rispetto ai tipi che dovrebbero comportarsi come anelli algebrici. Invece, il linguaggio fa sì che i tipi più piccoli di "int" si comportino come numeri mentre i tipi a grandezza naturale si comportano come anelli algebrici. Di conseguenza, chiamando la funzione come:

uint32_t mul(uint16_t a, uint16_t b) { return a*b; }

con (65535, 65535) avrà un comportamento definito su sistemi in cui intè 16 bit (ovvero restituisce 1), un comportamento diverso in cui intè pari o superiore a 33 bit (restituisce 0xFFFE0001) e comportamento indefinito su sistemi in cui "int" è ovunque in tra [nota che gcc di solito produrrà risultati aritmeticamente corretti con risultati tra INT_MAX + 1u e UINT_MAX, ma a volte genererà codice per la funzione sopra che fallisce con tali valori!]. Non molto utile.

Tuttavia, la mancanza di tipi che si comportano costantemente come numeri o costantemente come un anello algebrico non cambia il fatto che i tipi di anello algebrico sono quasi indispensabili per alcuni tipi di programmazione.

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.