javascript - example - bitwise traduccion
¿Por qué bitwise "no 1" es igual a-2? (7)
En informática, todo se trata de interpretación. Para una computadora, todo es una secuencia de bits que se pueden interpretar de muchas maneras. Por ejemplo, 0100001
puede ser el número 33 o !
(Así es como ASCII mapea esta secuencia de bits).
Todo es una secuencia de bits para una computadora, sin importar si lo ve como un dígito, número, letra, texto, documento de Word, píxel en su pantalla, imagen mostrada o un archivo JPG en su disco duro. Si sabes cómo interpretar esa secuencia de bits, puede convertirse en algo significativo para un humano, pero en la RAM y la CPU solo hay bits.
Entonces, cuando quiere almacenar un número en una computadora, tiene que codificarlo . Para números no negativos es bastante simple, solo tienes que usar la representación binaria. Pero ¿qué hay de los números negativos?
Puede utilizar una codificación llamada complemento de dos . En esta codificación, debe decidir cuántos bits tendrá cada número (por ejemplo, 8 bits). El bit más significativo está reservado como bit de signo. Si es 0
, entonces el número debe interpretarse como no negativo, de lo contrario es negativo. Otros 7 bits contienen un número real.
00000000
significa cero, al igual que para los números sin firmar. 00000001
es uno, 00000010
es dos y así sucesivamente. El número positivo más grande que puede almacenar en 8 bits en el complemento de dos es 127 ( 01111111
).
El siguiente número binario ( 10000000
) es -128. Puede parecer extraño, pero en un segundo explicaré por qué tiene sentido. 10000001
es -127, 10000010
es 10000010
y así sucesivamente. 11111111
es -1.
¿Por qué usamos una codificación tan extraña? Por sus interesantes propiedades. Específicamente, mientras realiza la suma y la resta, la CPU no tiene que saber que es un número firmado almacenado como complemento de dos. Puede interpretar ambos números como sin firmar, sumarlos y el resultado será correcto.
Intentemos esto: -5 + 5. -5 es 11111011
, 5
es 00000101
.
11111011
+ 00000101
----------
000000000
El resultado es de 9 bits de longitud. El bit más significativo se desborda y nos queda 00000000
que es 0. Parece funcionar.
Otro ejemplo: 23 + -7. 23 es 00010111
, -7 es 11111001
.
00010111
+ 11111001
----------
100010000
Nuevamente, el MSB se pierde y obtenemos 00010000
== 16. ¡Funciona!
Así es como funciona el complemento de dos. Las computadoras lo utilizan internamente para almacenar enteros con signo.
Es posible que haya notado que en los complementos de dos cuando niega bits de un número N
, se convierte en -N-1
. Ejemplos:
- 0 negado ==
~00000000
==11111111
== -1 - 1 negado ==
~00000001
==11111110
== -2 - 127 negado ==
~01111111
==10000000
== -128 - 128 negado ==
~10000000
==01111111
== 127
Esto es exactamente lo que has observado: JS está fingiendo que está utilizando el complemento de dos. Entonces, ¿por qué parseInt(''11111111111111111111111111111110'', 2)
es 4294967294? Bueno, porque solo es fingir.
Internamente, JS siempre usa la representación de números de punto flotante. Funciona de una manera completamente diferente a la del complemento de dos y su negación a nivel de bits es en su mayoría inútil, por lo que JS pretende que un número sea el complemento de dos, luego niega sus bits y lo convierte de nuevo en representación de punto flotante. Esto no sucede con parseInt
, por lo que obtienes 4294967294, aunque el valor binario es aparentemente el mismo.
Supongamos que tenemos 1
y este número en la base 2 es:
00000000000000000000000000000001
Ahora quiero voltear todos los bits para obtener el siguiente resultado:
11111111111111111111111111111110
Por lo que sé, la solución es usar el ~
(operador NO bit a bit) para voltear todos los bits, pero el resultado de ~1
es -2
:
console.log(~1); //-2
console.log((~1).toString(2)); //-10 (binary representation)
¿Por qué obtengo este extraño resultado?
Es la aritmética del complemento de dos. Que es el equivalente de "contador de cinta" aritmética. Las grabadoras de cinta tendían a tener contadores adjuntos (agregar máquinas probablemente sería una analogía aún mejor, pero ya estaban obsoletos cuando el complemento 2s se puso de moda).
Cuando retrocede 2 pasos desde 000, llega a 998. Así que 998 es la representación aritmética del complemento de 10s del contador de la cinta para -2: viento hacia adelante 2 pasos, llegue a 000 nuevamente.
El complemento 2s es así. Viento hacia adelante 1 desde 1111111111111111 y llegas a 0000000000000000, por lo que 1111111111111111 es la representación de -1. En lugar de eso, devuelve otro 1 desde allí, y obtienes 1111111111111110, que es la representación de -2.
Este es el comportamiento esperado. Según mdn:bitwise-not .
La parte que probablemente no entiendas es que [11111111111111111111111111111110]₂ = [10]₂
¹, si se expresa como un entero con signo . Los 1
s iniciales pueden ser tantos como desee y sigue siendo el mismo número, similar a 0
s en enteros / decimales sin signo.
¹ [10]₂
especifica que 10
debe interpretarse como base 2 (binario)
Hay 2 enteros entre 1
y -2
: 0
y -1
1
en binario es 00000000000000000000000000000001
0
en binario es 00000000000000000000000000000000
-1
en binario es 11111111111111111111111111111111
-2
en binario es 11111111111111111111111111111110
( "binario" es el complemento de 2, en el caso de un bit a bit no ~
)
Como puede ver, no es muy sorprendente que ~1
igual a -2
, ya que ~0
es igual a -1
.
Como explained @Derek , estos operadores bitwise tratan sus operandos como una secuencia de 32 bits. parseInt
, por otro lado, no lo hace. Es por eso que obtienes algunos resultados diferentes.
Aquí hay una demostración más completa:
for (var i = 5; i >= -5; i--) {
console.log(''Decimal: '' + pad(i, 3, '' '') + '' | Binary: '' + bin(i));
if (i === 0)
console.log(''Decimal: -0 | Binary: '' + bin(-0)); // There is no `-0`
}
function pad(num, length, char) {
var out = num.toString();
while (out.length < length)
out = char + out;
return out
}
function bin(bin) {
return pad((bin >>> 0).toString(2), 32, ''0'');
}
.as-console-wrapper { max-height: 100% !important; top: 0; }
Un entero con signo de 32 bits complementario de A 2 (Javascript insiste en que es el formato utilizado para un entero con signo de 32 bits) almacenará -2 como 11111111111111111111111111111110
Así que todo como se esperaba.
Los números en JavaScript son números de punto flotante , almacenados y representados por el estándar IEEE 754.
Sin embargo, para las operaciones bitwise, los operandos se tratan internamente como enteros de 32 bits con signo representados por el formato de complemento de dos :
Los operandos de todos los operadores bitwise se convierten en enteros de 32 bits firmados en formato de complemento a dos. El formato de complemento de dos significa que la contraparte negativa de un número (por ejemplo, 5 vs. -5) es todos los bits del número invertidos (NO a nivel de bit del número, también conocido como el complemento de uno del número) más uno.
La contraparte positiva de un número negativo se calcula de la misma manera. Así tenemos:
1 = 00000000000000000000000000000001b
~1 = 11111111111111111111111111111110b
11111111111111111111111111111110b = -2
Tenga en cuenta que Number.toString()
no debe devolver la representación complementaria de los dos para base-2.
La expresión (-2).toString(2)
produce -10
que es el signo menos ( -
) seguido de la representación en base 2 de 2
( 10
).
100 -4
101 -3
110 -2
111 -1
000 0
001 1
010 2
011 3
Una forma sencilla de recordar cómo funciona la notación de complemento de dos es imaginar que es solo un binario normal, excepto que su último bit corresponde al mismo valor negado. En mi complemento de tres bits, el primer bit es 1
, el segundo es 2
, el tercero es -4
(note el menos).
Como puede ver, un complemento de bit a bit no en dos es -(n + 1)
. Sorprendentemente, aplicarlo a un número dos veces da el mismo número:
-(-(n + 1) + 1) = (n + 1) - 1 = n
Es obvio cuando se habla poco a poco, pero no tanto en su efecto aritmético.
Varias observaciones más que hacen un poco más fácil recordar cómo funciona:
Observe cómo ascienden los valores negativos. Muy las mismas reglas, con solo 0 y 1 intercambiado. Bitwise NOTted, por así decirlo.
100 -4 011 - I bitwise NOTted this half
101 -3 010
110 -2 001
111 -1 000
----------- - Note the symmetry of the last column
000 0 000
001 1 001
010 2 010
011 3 011 - This one''s left as-is
Al ciclar esa lista de binarios por la mitad de la cantidad total de números allí, obtienes una secuencia típica de números binarios ascendentes comenzando desde cero.
- 100 -4 /
- 101 -3 |
- 110 -2 |-/ - these are in effect in signed types
- 111 -1 / |
*************|
000 0 |
001 1 |
010 2 |
011 3 |
*************|
+ 100 4 / |
+ 101 5 |-/ - these are in effect in unsigned types
+ 110 6 |
+ 111 7 /