number - one''s complement calculator
¿Por qué es-(- 2147483648)=- 2147483648 en una máquina de 32 bits? (6)
Negar una constante entera (sin sufijo):
La expresión
-(-2147483648)
está perfectamente definida en C, sin embargo, puede no ser obvio por qué es así.
Cuando escribe
-2147483648
, se forma como unario menos operador aplicado a la constante entera.
Si
2147483648
no se puede expresar como
int
, entonces s se representa como
long
o
long long
*
(lo que quepa primero), donde el último tipo está garantizado por el Estándar C para cubrir ese valor
†
.
Para confirmar eso, puede examinarlo:
printf("%zu/n", sizeof(-2147483648));
que produce
8
en mi máquina.
El siguiente paso es aplicar el segundo operador, en cuyo caso el valor final es
2147483648L
(suponiendo que finalmente se haya representado como
long
).
Si intenta asignarlo al objeto
int
, de la siguiente manera:
int n = -(-2147483648);
entonces el comportamiento real está definido por la implementación . Refiriéndose a la norma:
C11 §6.3.1.3 / 3 Enteros con y sin signo
De lo contrario, el nuevo tipo se firma y el valor no se puede representar en él; el resultado está definido por la implementación o se genera una señal definida por la implementación.
La forma más común es simplemente cortar los bits más altos. Por ejemplo, GCC lo documents como:
Para la conversión a un tipo de ancho N, el valor se reduce módulo 2 ^ N para estar dentro del rango del tipo; No se eleva ninguna señal.
Conceptualmente, la conversión al tipo de ancho 32 puede ilustrarse mediante la operación AND a nivel de bits:
value & (2^32 - 1) // preserve 32 least significant bits
De acuerdo con
la
aritmética
del complemento
a
dos
, el valor de
n
se forma con todos los ceros y el conjunto de bits MSB (signo), que representa el valor de
-2^31
, es decir
-2147483648
.
Negar un objeto
int
:
Si intenta negar el objeto
int
, que tiene un valor de
-2147483648
, y suponiendo que la máquina del complemento a dos, el programa exhibirá
un comportamiento indefinido
:
n = -n; // UB if n == INT_MIN and INT_MAX == 2147483647
C11 §6.5 / 5 Expresiones
Si se produce una condición excepcional durante la evaluación de una expresión (es decir, si el resultado no está matemáticamente definido o no está en el rango de valores representables para su tipo), el comportamiento es indefinido.
Referencias adicionales:
- INT32-C. Asegúrese de que las operaciones en enteros firmados no den como resultado un desbordamiento
*) En el estándar C90 retirado, no había un tipo
long long
y las reglas eran diferentes.
Específicamente, la secuencia del decimal sin sufijo fue
int
,
long int
,
unsigned long int
(C90 §6.1.3.2 Constantes enteras).
†) Esto se debe a
LLONG_MAX
, que debe ser al menos
+9223372036854775807
(C11 §5.2.4.2.1 / 1).
Creo que la pregunta se explica por sí misma, supongo que probablemente tenga algo que ver con el desbordamiento, pero aún no lo entiendo. ¿Qué está pasando, bit a bit, debajo del capó?
¿Por qué
-(-2147483648) = -2147483648
(al menos mientras compila en C)?
Depende de la versión de C, los detalles de la implementación y si estamos hablando de variables o valores literales.
Lo primero que hay que entender es que no hay literales enteros negativos en C "-2147483648" es una operación unaria menos seguida de un literal entero positivo.
Supongamos que estamos corriendo en una plataforma típica de 32 bits donde int y long son 32 bits y long long es 64 bits y consideremos la expresión.
(- (- 2147483648) == -2147483648)
El compilador necesita encontrar un tipo que pueda contener 2147483648, en un compilador C99 compatible usará el tipo "long long" pero un compilador C90 puede usar el tipo "unsigned long".
Si el compilador usa el tipo long long, entonces nada se desborda y la comparación es falsa. Si el compilador usa unsigned long, las reglas envolventes sin firmar entran en juego y la comparación es verdadera.
Esta no es una pregunta en C, ya que en una implementación en C con representación de complemento de dos de 32 bits para el tipo
int
, el efecto de aplicar el operador de negación unario a un
int
tiene el valor
-2147483648
no
está
definido
.
Es decir, el lenguaje C rechaza específicamente la designación del resultado de la evaluación de dicha operación.
Considere de manera más general, sin embargo, cómo se define el operador unario en la aritmética del complemento a dos: la inversa de un número positivo
x
se forma volteando todos los bits de su representación binaria y sumando
1
.
Esta misma definición sirve también para cualquier número negativo que tenga al menos un bit distinto de su conjunto de bits de signo.
Sin embargo, surgen problemas menores para los dos números que no tienen ningún conjunto de bits de valor: 0, que no tiene ningún conjunto de bits, y el número que solo tiene establecido su bit de signo (-2147483648 en representación de 32 bits). Cuando voltea todos los bits de cualquiera de estos, termina con todos los bits de valor establecidos. Por lo tanto, cuando agrega 1 posteriormente, el resultado desborda los bits de valor. Si imagina realizar la suma como si el número no estuviera firmado, tratando el bit de signo como un bit de valor, entonces obtendrá
-2147483648 (decimal representation)
--> 0x80000000 (convert to hex)
--> 0x7fffffff (flip bits)
--> 0x80000000 (add one)
--> -2147483648 (convert to decimal)
Similar se aplica a la inversión de cero, pero en ese caso el desbordamiento al agregar 1 desborda también el bit de signo anterior. Si se ignora el desbordamiento, los 32 bits de orden inferior resultantes son todos cero, por lo tanto, -0 == 0.
Por la misma razón que enrollar un contador de pletina 500 pasos hacia adelante desde 000 (hasta 001 002 003 ...) mostrará 500, y enrollarlo hacia atrás 500 pasos hacia atrás desde 000 (hasta 999 998 997 ...) también mostrará 500 .
Esta es la notación del complemento a dos. Por supuesto, dado que la convención de signos complementarios de 2 es considerar el bit superior como el bit de signo, el resultado desborda el rango representable, al igual que 2000000000 + 2000000000 desborda el rango representable.
Como resultado, se establecerá el bit de "desbordamiento" del procesador (ver esto requiere acceso a los indicadores aritméticos de la máquina, generalmente no es el caso en la mayoría de los lenguajes de programación fuera del ensamblador). Este es el único valor que establecerá el bit de "desbordamiento" al negar el número de complemento de 2: la negación de cualquier otro valor se encuentra en el rango representable por el complemento de 2.
Usaré un número de 4 bits, solo para simplificar las matemáticas, pero la idea es la misma.
En un número de 4 bits, los valores posibles están entre 0000 y 1111. Eso sería de 0 a 15, pero si desea representar números negativos, el primer bit se usa para indicar el signo (0 para positivo y 1 para negativo).
Entonces 1111 no es 15. Como el primer bit es 1, es un número negativo. Para conocer su valor, utilizamos el método de dos complementos como ya se describió en respuestas anteriores: "invierte los bits y suma 1":
- invertir los bits: 0000
- sumando 1: 0001
0001 en binario es 1 en decimal, entonces 1111 es -1.
El método de dos complementos va en ambos sentidos, por lo que si lo usa con cualquier número, le dará la representación binaria de ese número con el signo invertido.
Ahora veamos 1000. El primer bit es 1, entonces es un número negativo. Usando el método de dos complementos:
- invertir los bits: 0111
- sumar 1: 1000 (8 en decimal)
Entonces 1000 es -8.
Si hacemos
-(-8)
, en binario significa
-(1000)
, lo que en realidad significa usar el método de dos complementos en 1000. Como vimos anteriormente, el resultado también es 1000. Entonces, en un número de 4 bits,
-(-8)
es igual a -8.
En un número de 32 bits,
-2147483648
en binario es
1000..(31 zeroes)
, pero si usa el método de dos complementos, terminará con el mismo valor (el resultado es el mismo número).
Es por eso que en el número de 32 bits
-(-2147483648)
es igual a
-2147483648
Nota: esta respuesta no se aplica como tal en el obsoleto estándar ISO C90 que todavía utilizan muchos compiladores
En primer lugar, en C99, C11, la expresión
-(-2147483648) == -2147483648
es de hecho
falsa
:
int is_it_true = (-(-2147483648) == -2147483648);
printf("%d/n", is_it_true);
huellas dactilares
0
Entonces, ¿cómo es posible que esto se evalúe como verdadero?
La máquina está utilizando enteros de
complemento de dos de
32 bits.
El
2147483648
es una constante entera que no cabe en 32 bits, por lo tanto, será
long int
o
long long int
dependiendo de cuál sea el primero donde quepa.
Esto negado dará como resultado
-2147483648
- y nuevamente, aunque el número
-2147483648
puede caber en un entero de 32 bits, la expresión
-2147483648
consiste en un entero positivo> 32 bits precedido de unario
-
!
Puedes probar el siguiente programa:
#include <stdio.h>
int main() {
printf("%zu/n", sizeof(2147483647));
printf("%zu/n", sizeof(2147483648));
printf("%zu/n", sizeof(-2147483648));
}
La salida en tal máquina probablemente sería 4, 8 y 8.
Ahora,
-2147483648
negado nuevamente dará como resultado
+214783648
, que todavía es de tipo
long int
o
long long int
, y todo está bien.
En C99, C11, la expresión constante entera
-(-2147483648)
está bien definida en todas las implementaciones conformes.
Ahora, cuando este valor se asigna a una variable de tipo
int
, con 32 bits y representación de complemento a dos, el valor no es representable en él; los valores en el complemento de 32 bits 2 oscilarían entre -2147483648 y 2147483647.
El estándar C11 6.3.1.3p3 dice lo siguiente de las conversiones de enteros:
- [Cuando] el nuevo tipo está firmado y el valor no puede representarse en él; el resultado está definido por la implementación o se genera una señal definida por la implementación .
Es decir, el estándar C en realidad no define cuál sería el valor en este caso, o no excluye la posibilidad de que la ejecución del programa se detenga debido a que se genera una señal, sino que se deja a las implementaciones (es decir, compiladores ) para decidir cómo manejarlo (C11 3.4.1) :
comportamiento definido por la implementación
comportamiento no especificado donde cada implementación documenta cómo se realiza la elección
y (3.19.1) :
valor definido por la implementación
valor no especificado donde cada implementación documenta cómo se realiza la elección
En su caso, el comportamiento definido por la implementación es que el valor son los 32 bits de orden más bajo [*].
Debido al complemento de 2, el valor int largo (largo)
0x80000000
tiene el bit 31 establecido y todos los demás bits borrados.
En los enteros de complemento de dos de 32 bits, el bit 31 es el bit de signo, lo que significa que el número es negativo;
todos los bits de valor puestos a cero significan que el valor es el número mínimo representable, es decir,
INT_MIN
.
[*] GCC documenta su comportamiento definido de implementación en este caso de la siguiente manera :
El resultado de, o la señal generada por, convertir un número entero a un tipo entero con signo cuando el valor no puede representarse en un objeto de ese tipo (C90 6.2.1.2, C99 y C11 6.3.1.3).
Para la conversión a un tipo de ancho
N
, el valor se reduce módulo2^N
para estar dentro del rango del tipo; No se eleva ninguna señal.