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 ~

  1. Quando a expressão é null ou undefined… 0.
  2. Objetos são convertidos para strings.
  3. Strings são convertidas para números, se possível. Quando não é possível… 0.
  4. Valores booleanos são tratados como números (0 para false, 1 para true).
  5. Floating-points são convertidos excluindo a parte fracionada.
  6. 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. 😉