Voglio definire una funzione che accetta un unsigned int
argomento e restituisce un int
modulo congruente UINT_MAX + 1 all'argomento.
Un primo tentativo potrebbe essere simile a questo:
int unsigned_to_signed(unsigned n)
{
return static_cast<int>(n);
}
Ma come sa qualsiasi avvocato linguistico, il casting da non firmato a firmato per valori maggiori di INT_MAX è definito dall'implementazione.
Voglio implementarlo in modo tale che (a) si basi solo sul comportamento richiesto dalle specifiche; e (b) si compila in un no-op su qualsiasi macchina moderna e ottimizza il compilatore.
Per quanto riguarda le macchine bizzarre ... Se non c'è un int con segno congruente modulo UINT_MAX + 1 con l'int senza segno, diciamo che voglio lanciare un'eccezione. Se ce n'è più di uno (non sono sicuro che sia possibile), diciamo che voglio quello più grande.
OK, secondo tentativo:
int unsigned_to_signed(unsigned n)
{
int int_n = static_cast<int>(n);
if (n == static_cast<unsigned>(int_n))
return int_n;
// else do something long and complicated
}
Non mi interessa molto dell'efficienza quando non sono su un tipico sistema a due complementi, poiché a mio modesto parere ciò è improbabile. E se il mio codice diventa un collo di bottiglia sugli onnipresenti sistemi di grandezza dei segni del 2050, beh, scommetto che qualcuno può capirlo e ottimizzarlo.
Ora, questo secondo tentativo è abbastanza vicino a quello che voglio. Sebbene il cast to int
sia definito dall'implementazione per alcuni input, il cast back to unsigned
è garantito dallo standard per preservare il valore modulo UINT_MAX + 1. Quindi il condizionale controlla esattamente quello che voglio e non si compilerà in nulla su nessun sistema che potrei incontrare.
Tuttavia ... sto ancora trasmettendo int
senza prima controllare se richiamerà il comportamento definito dall'implementazione. Su qualche ipotetico sistema nel 2050 potrebbe fare chissà cosa. Quindi diciamo che voglio evitarlo.
Domanda: come dovrebbe essere il mio "terzo tentativo"?
Per ricapitolare, voglio:
- Trasmetti da int non firmato a int firmato
- Conserva il valore mod UINT_MAX + 1
- Invoca solo comportamenti obbligatori standard
- Compilare in modo non operativo su una tipica macchina a complemento a due con ottimizzazione del compilatore
[Aggiornare]
Faccio un esempio per mostrare perché questa non è una domanda banale.
Considera un'ipotetica implementazione C ++ con le seguenti proprietà:
sizeof(int)
è uguale a 4sizeof(unsigned)
è uguale a 4INT_MAX
è uguale a 32767INT_MIN
è uguale a -2 32 + 32768UINT_MAX
è uguale a 2 32 - 1- L'aritmetica su
int
è modulo 2 32 (nell'intervalloINT_MIN
fino aINT_MAX
) std::numeric_limits<int>::is_modulo
è vero- Il cast di unsigned
n
a int conserva il valore per 0 <= n <= 32767 e restituisce zero in caso contrario
In questa ipotetica implementazione, esiste esattamente un int
valore congruente (mod UINT_MAX + 1) a ciascun unsigned
valore. Quindi la mia domanda sarebbe ben definita.
Affermo che questa ipotetica implementazione C ++ è completamente conforme alle specifiche C ++ 98, C ++ 03 e C ++ 11. Ammetto di non aver memorizzato ogni parola di tutti loro ... Ma credo di aver letto attentamente le sezioni pertinenti. Quindi, se vuoi che accetti la tua risposta, devi (a) citare una specifica che esclude questa ipotetica implementazione o (b) gestirla correttamente.
Una risposta corretta, infatti, deve gestire ogni ipotetica implementazione consentita dalla norma. Questo è ciò che significa "invocare solo comportamenti obbligatori standard", per definizione.
Per inciso, nota che std::numeric_limits<int>::is_modulo
è assolutamente inutile qui per molteplici ragioni. Per prima cosa, può essere true
anche se i cast da non firmato a firmato non funzionano per valori grandi senza segno. Per un altro, può essere true
anche su sistemi di complemento a uno o di grandezza dei segni, se l'aritmetica è semplicemente modulo dell'intero intervallo di numeri interi. E così via. Se la tua risposta dipende is_modulo
, è sbagliata.
[Aggiorna 2]
La risposta di hvd mi ha insegnato qualcosa: la mia ipotetica implementazione C ++ per interi non è consentita dal C. moderno. Gli standard C99 e C11 sono molto specifici sulla rappresentazione di interi con segno; in effetti, consentono solo il complemento a due, il complemento a uno e la grandezza del segno (sezione 6.2.6.2 paragrafo (2);).
Ma C ++ non è C. A quanto pare, questo fatto è al centro della mia domanda.
Lo standard C ++ 98 originale era basato sul C89 molto più vecchio, che dice (sezione 3.1.2.5):
Per ciascuno dei tipi di interi con segno, esiste un tipo di intero senza segno corrispondente (ma diverso) (designato con la parola chiave unsigned) che utilizza la stessa quantità di memoria (comprese le informazioni sul segno) e ha gli stessi requisiti di allineamento. L'intervallo di valori non negativi di un tipo intero con segno è un sottointervallo del corrispondente tipo intero senza segno e la rappresentazione dello stesso valore in ogni tipo è la stessa.
C89 non dice nulla sull'avere solo un bit di segno o consentire solo complementi a due / complementi a uno / magnitudine del segno.
Lo standard C ++ 98 ha adottato questo linguaggio quasi letteralmente (sezione 3.9.1 paragrafo (3)):
Per ciascuno dei tipi di interi con segno , esiste un tipo di intero senza segno corrispondente (ma diverso) : "
unsigned char
", "unsigned short int
", "unsigned int
" e "unsigned long int
", ognuno dei quali occupa la stessa quantità di memoria e ha gli stessi requisiti di allineamento (3.9 ) come tipo intero con segno corrispondente; cioè, ogni intero con segno tipo ha la stessa rappresentazione dell'oggetto come corrispondente intero senza segno tipo. L'intervallo di valori non negativi di un tipo intero con segno è un sottointervallo del corrispondente tipo intero senza segno e la rappresentazione del valore di ciascun tipo con segno / senza segno corrispondente deve essere la stessa.
Lo standard C ++ 03 utilizza un linguaggio essenzialmente identico, così come C ++ 11.
Nessuna specifica C ++ standard vincola le sue rappresentazioni di interi con segno a qualsiasi specifica C, per quanto ne so. E non c'è niente che imponga un singolo segno o qualcosa del genere. Tutto ciò che dice è che gli interi con segno non negativo devono essere un sottointervallo del corrispondente senza segno.
Quindi, ancora una volta sostengo che INT_MAX = 32767 con INT_MIN = -2 32 +32768 è consentito. Se la tua risposta presume il contrario, non è corretta a meno che tu non citi uno standard C ++ che mi dimostra che ho torto.