O Til no JavaScript
Incompreendido, o operador til (~
) é um mistério que ninguém discute. Primeiro, pois é um operador bitwise, e, segundo, pois seus casos de uso são bastante misteriosos…
Você consegue dizer…
em valor e tipo de dado, o que será impresso no console nas situações abaixo?
console.log(~~"1");
console.log(~8.2);
console.log(~false);
console.log(~~"5.9");
console.log(~~{});
console.log(~~-5.5);
console.log(~"Tudo bem?");
Não sabe? Well…
O operador til
Ou melhor, o operador bitwise NOT.
É importante falar isso pois é importante entender o que bitwise significa. Operadores bitwise são especiais em JavaScript: eles tratam todos seus operandos como uma sequência de 32 bits (0 e 1) – ou seja, trabalham com representações binárias (não decimais, como de praxe) de nossos operandos, e nos entregam valores númericos como se nada tivesse acontecido.
O NOT significa que todos os bits do operando serão invertidos (0 vira 1, 1 vira 0). Parece inútil quando não estamos trabalhando com números binários, mas as aplicações criativas tornam o operador mais interessante.
Regras do ~
- Quando a expressão é
null
ouundefined
… 0. - Objetos são convertidos para strings.
- Strings são convertidas para números, se possível. Quando não é possível… 0.
- Valores booleanos são tratados como números (0 para
false
, 1 paratrue
). - Floating-points são convertidos excluindo a parte fracionada.
- Todo número inteiro n é convertido para -(n + 1).
As duas últimas regras são as mais importantes, pois elas implicam nas principais aplicações do operador. Já temos a resposta de alguns:
console.log(~8.2); // => -9
console.log(~false); // => -1
console.log(~"Tudo bem?"); // => -1
Os números foram convertidos de acordo com a fórmula -(n + 1). -9 é resultado de - 8 - 1, por exemplo.
Dupla negação
Podemos usar dois operadores til (double bitwise NOT). Aqui, basta entender o trabalho do outro til: reinverter os bits.
Considerando ~n
, como anteriormente, temos -(n + 1) = - n - 1. Considerando ~~n
, temos -[-(n + 1)] = n + 1.
Perceba que as equações, quando somadas, se anulam. Pois essa é a grande sacada de usar dois operadores til! Temos, então:
console.log(~~"1"); // => 1
console.log(~~"5.9"); // => 5
console.log(~~{}); // => 0
console.log(~~-5.5); // => -5
Aplicações
Truncar números
Pelo fato do operador converter removendo a parte fracionada, utilizá-lo para truncar números de forma fácil é a aplicação mais comum:
let data = 7.8926152;
const integer = ~~data;
console.log(integer); // => 7
Embora o ~~
tenha sido, por muito tempo, usado no lugar de Math.floor()
, o método mais parecido com ele que temos em JavaScript hoje é o Math.trunc()
do ES2015.
Converter string para número
Uma aplicação simples que se baseia em uma das regras de funcionamento do til: converter strings para números sempre que possível; afinal, é com números que o operador trabalha.
let data = "2";
const dataAsNumber = ~~data;
console.log(dataAsNumber); // => 2
Verificar a existência de um item no array
let women = ["Ada Lovelace", "Joan of Arc", "Marie Curie"];
if (~women.indexOf("Ada Lovelace")) {
console.log("Ada Lovelace was such an important woman!");
}
É difícil de compreender humanamente que o if
está verificando se Ada Lovelace está incluída no array women
, mas é exatamente isso que está acontecendo. Você entendeu a razão pela qual isso funciona?
Poderíamos verificar sem o ~
, mas não funcionaria em casos em que o item do array é o primeiro (índice zero, e 0 retorna false
como booleano). O operador til viabiliza essa verificação, em razão da conversão de um número inteiro n para -(n + 1).
O equivalente humano, utilizando Lodash (_
), seria:
let women = ["Ada Lovelace", "Joan of Arc", "Marie Curie"];
if (_(women).contains("Ada Lovelace")) {
console.log("Ada Lovelace was such an important woman!");
}
Performance
Usar ~~
em vez de Math.floor
ou Math.trunc
é geralmente mais rápido. Dependendo da JavaScript engine do navegador e do caso de uso, no entanto, pode não fazer muita diferença e até ser mais lento. Veja o teste no JSPerf.
De qualquer forma, a péssima performance de quem lê um código com o desumano ~
pode não valer o ganho de performance – que é ignorável de tão mínimo – em uma aplicação.
Considerações
No geral, operadores bitwise, principalmente ~
, |
e &
, possuem aplicações interessantes e bastante criativas.
Quanto ao ~~
, não acredito que chegará a se popularizar como aconteceu com o !!
, que converte para um valor booleano. Em anos, a prática com til nunca se tornou realmente popular, talvez pelo fato de ser um operador bitwise – e, portanto, pouco compreendido – e ter casos de uso bastante excêntricos.
Ficam à título de curiosidade suas aplicações bastante criativas. ;-)