c++ - ¿Por qué es diferente el signo después de restar sin firmar y firmado?
language-lawyer unsigned (2)
unsigned int t = 10;
int d = 16;
float c = t - d;
int e = t - d;
¿Por qué es positivo el valor de c
pero e
negativo?
Comencemos por analizar el resultado de t - d
.
t
es un unsigned int
mientras que d
es un int
, por lo que para hacer aritmética, el valor de d
se convierte en un unsigned int
(las reglas de C ++ dicen que unsigned tiene preferencia aquí). Entonces obtenemos 10u - 16u
, que (suponiendo que int
32 bits) se ajusta a 4294967290u
.
Este valor luego se convierte a float
en la primera declaración, y a int
en la segunda.
Asumiendo la implementación típica de float
(IEEE de precisión simple de 32 bits), su valor más alto representa aproximadamente 1e38
, por lo que 4294967290u
está dentro de ese rango. Habrá errores de redondeo, pero la conversión a flotar no se desbordará.
Para int
, la situación es diferente. 4294967290u
es demasiado grande para encajar en un int
, por lo que ocurre un envolvente y volvemos al valor -6
. Tenga en cuenta que tal ajuste no está garantizado por el estándar: el valor resultante en este caso está definido por la implementación (1) , lo que significa que depende del compilador cuál es el valor del resultado, pero debe estar documentado.
(1) C ++ 17 (N4659), [conv.integral] 7.8 / 3:
Si el tipo de destino está firmado, el valor no cambia si se puede representar en el tipo de destino; de lo contrario, el valor está definido por la implementación.
Primero, debes entender las "conversiones aritméticas habituales" (ese enlace es para C, pero las reglas son las mismas en C ++). En C ++, si haces aritmética con tipos mixtos (por cierto, debes evitar que sea posible), hay un conjunto de reglas que deciden en qué tipo de cálculo se realiza el cálculo.
En su caso, está restando un int firmado de un int sin firmar. Las reglas de la promoción dicen que el cálculo real se realiza utilizando unsigned int
.
Por lo tanto, su cálculo es 10 - 16
en aritmética int sin signo. La aritmética no firmada es la aritmética de módulo, lo que significa que se envuelve. Por lo tanto, suponiendo que se trate de un int de 32 bits típico, el resultado de este cálculo es 2 ^ 32 - 6.
Esto es lo mismo para ambas líneas. Tenga en cuenta que la resta es completamente independiente de la tarea; el tipo en el lado izquierdo no tiene absolutamente ninguna influencia en cómo se realiza el cálculo. Es un error común de los principiantes pensar que el tipo en el lado izquierdo influye de alguna manera en el cálculo; pero float f = 5 / 6
es cero, porque la división aún usa aritmética de enteros.
La diferencia, entonces, es lo que sucede durante la tarea. El resultado de la resta se convierte implícitamente a float
en un caso e int
en el otro.
La conversión a flotación intenta encontrar el valor más cercano al real que puede representar el tipo. Esto será un valor muy grande; Sin embargo, no es exactamente el que resta el original.
La conversión a int dice que si el valor se ajusta al rango de int, el valor no se modificará. Pero 2 ^ 32 - 6 es mucho más grande que el 2 ^ 31 - 1 que puede contener un int de 32 bits, por lo que obtiene la otra parte de la regla de conversión, que dice que el valor resultante está definido por la implementación. Este es un término en el estándar que significa "diferentes compiladores pueden hacer cosas diferentes, pero tienen que documentar lo que hacen".
Para todos los propósitos prácticos, todos los compiladores que probablemente encuentres dicen que el patrón de bits permanece igual y solo se interpreta como firmado. Debido a la forma en que funciona el complemento aritmético de 2 (la forma en que casi todas las computadoras representan números negativos), el resultado es el -6 que se esperaría del cálculo.
Pero todo esto es una manera muy larga de repetir el primer punto, que es "no hacer aritmética de tipo mixto". Convierta los tipos primero, explícitamente, a los tipos que sabe que harán lo correcto.