2015-09-09 40 views
23

I seguenti risultati mi fanno davvero confuso:sottrazione tra firmata e senza segno seguita da divisione

int i1 = 20-80u; // -60 
int i2 = 20-80;  // -60 
int i3 =(20-80u)/2; // 2147483618 
int i4 =(20-80)/2; // -30 
int i5 =i1/2;  // -30 
  1. i3 sembra essere calcolato come (20u-80u)/2, invece di (20-80u)/2
  2. presumibilmente i3 è lo stesso i5.

risposta

12

IIRC, un'operazione aritmetica tra int signed e unsigned produrrà un risultato senza segno.

Così, 20 - 80u produce il risultato senza segno equivalente a -60: se unsigned int è un tipo a 32 bit, che è risultato 4294967236.

Incidentalmente, assegnando a che i1 produce un'implementazione definito risultato perché il numero è troppo grande per adattarsi. Ottenere -60 è tipico, ma non è garantito.

+4

_Incidentalmente, assegnare quel valore a i1 è un comportamento indefinito_ Ne sei sicuro? Ho insegnato che la conversione da unsigned int a signed int è ben definita per tutti i valori di unsigned int. – rozina

+3

Qui non c'è overflow di interi con segno qui. Ci sono conversioni Vedi [conv.integral] (http://eel.is/c++draft/conv.integral). – Sebivor

+0

@rozina: Huh, non avevo mai visto prima che la conversione funzioni diversamente da questo punto di vista. Risolto – Hurkyl

10
int i1 = 20-80u; // -60 

Gli operandi sono diversi, quindi una conversione è necessario. Entrambi gli operandi vengono convertiti in un tipo comune (uno unsigned int, in questo caso). Il risultato, che sarà un grande valore unsigned int (60 meno di UINT_MAX + 1 se i miei calcoli sono corretti) verrà convertito in un int prima che venga archiviato in i1. Poiché tale valore non rientra nell'intervallo int, il risultato sarà definito dall'implementazione, potrebbe essere una rappresentazione trap e quindi potrebbe causare un comportamento non definito quando si tenta di utilizzarlo. Tuttavia, nel tuo caso converte casualmente in -60.


int i3 =(20-80u)/2; // 2147483618 

Proseguendo dal primo esempio, la mia ipotesi è che il risultato di 20-80u sarebbe inferiore a 60 UINT_MAX + 1. Se UINT_MAX è 4294967295 (un valore comune per UINT_MAX), ciò significherebbe 20-80u è 4294967236 ... e 4294967236/2 è 2147483618.


Per quanto riguarda i2 e gli altri, non ci dovrebbero essere sorprese. Seguono i calcoli matematici convenzionali senza conversioni, troncamenti o overflow.

+0

Quindi, se ho capito bene, la conversione da -1 a unsigned è ben definita ed è UINT_MAX.Ma se quindi si converte UINT_MAX in INT, all'improvviso viene definita l'implementazione? E non potrebbe essere -1? – rozina

+0

@rozina Ciò è corretto. – Sebivor

+0

Bel giorno di risposta :) – LPs

3

Gli operatori aritmetici binari eseguiranno lo usual arithmetic conversions sui loro operandi per portarli a un tipo comune.

Nel caso di i1, i3 e i5 il tipo corrente sarà unsigned int e così il risultato sarà anche unsigned int. I numeri senza segno si avvolgeranno con l'aritmetica modulo e quindi sottraendo un valore non firmato leggermente più grande si otterrà un numero vicino a int max senza segno che non può essere rappresentato da un int.

Quindi nel caso di i1 si finisce con una conversione definita dall'implementazione poiché il valore non può essere rappresentato. Nel caso di i3 che divide per 2 riporta il valore unsigned nell'intervallo di int e quindi si ottiene un valore int con segno elevato dopo la conversione.

Le sezioni pertinenti formano lo standard di bozza C++ sono le seguenti. Sezione 5.7[expr.add]:

Gli operatori additivi + e - gruppo da sinistra a destra. Le normali conversioni aritmetiche vengono eseguite per gli operandi di tipo aritmetico o di enumerazione.

I normali conversioni aritmetiche sono coperti in sezione 5 e dice:

Molti operatori binari che prevedono operandi di operazioni aritmetiche o censimento tipo causano conversioni e producono tipi di risultato in modo simile. Lo scopo è quello di produrre un tipo comune, che è anche il tipo del risultato. Questo modello è chiamato usuali conversioni aritmetiche, che sono definite come segue:

[...]

  • Altrimenti, se l'operando che ha senza segno tipo intero ha rango superiore o uguale al rango del tipo di altro operando, l'operando con tipo intero con segno deve essere convertito in il tipo di operando con tipo intero senza segno.

e per la conversione da un valore che non può essere rappresentato da un tipo firmata, sezione 4.7[conv.integral]:

Se il tipo di destinazione è firmato, il valore è invariato se può essere rappresentato nel tipo di destinazione (e nella larghezza del campo di bit ); in caso contrario, il valore è definito dall'implementazione.

e per interi senza segno obbedisce modulo sezione aritmetica 3.9.1[basic.fundamental]:

interi senza segno devono rispettare le leggi dell'aritmetica modulo 2n dove n è il numero di bit nel valore rappresentazione di quella particolare dimensione del numero intero.48

+0

@Hurkyl: Dannato, sto dormendo in piedi oggi, ho distrutto l'overflow senza firma e la conversione da non firmata a firmata (quest'ultima definita come implementazione). Mi autodistruggerò il mio commento ... –