Hoje eu precisava de um algoritmo simples para verificar se um número é uma potência de 2.
O algoritmo precisa ser:
- Simples
- Correto para qualquer
ulong
valor.
Eu vim com este algoritmo simples:
private bool IsPowerOfTwo(ulong number)
{
if (number == 0)
return false;
for (ulong power = 1; power > 0; power = power << 1)
{
// This for loop used shifting for powers of 2, meaning
// that the value will become 0 after the last shift
// (from binary 1000...0000 to 0000...0000) then, the 'for'
// loop will break out.
if (power == number)
return true;
if (power > number)
return false;
}
return false;
}
Mas então pensei: que tal verificar se é um número exatamente redondo? Mas quando verifiquei 2 ^ 63 + 1, retornei exatamente 63 por causa do arredondamento. Então eu verifiquei se 2 na potência 63 é igual ao número original - e é, porque o cálculo é feito em se não em números exatos:log2 x
Math.Log
double
private bool IsPowerOfTwo_2(ulong number)
{
double log = Math.Log(number, 2);
double pow = Math.Pow(2, Math.Round(log));
return pow == number;
}
Este voltou true
para o valor errado: 9223372036854775809
.
Existe um algoritmo melhor?
(x & (x - 1))
pode retornar falsos positivos quandoX
é uma soma de potências de dois, por exemplo8 + 16
.Respostas:
Há um truque simples para esse problema:
Observe que esta função reportará
true
para0
, o que não é uma potência de2
. Se você deseja excluir isso, veja como:Explicação
Em primeiro lugar, o binário e operador bit a bit da definição do MSDN:
Agora vamos dar uma olhada em como tudo isso acontece:
A função retorna booleano (verdadeiro / falso) e aceita um parâmetro de entrada do tipo não assinado por muito tempo (x, neste caso). Por uma questão de simplicidade, suponha que alguém tenha passado o valor 4 e chamado a função da seguinte maneira:
Agora substituímos cada ocorrência de x por 4:
Bem, nós já sabemos que 4! = 0 evals para true, até agora tudo bem. Mas e quanto a:
Isso se traduz no seguinte:
Mas o que exatamente é
4&3
?A representação binária de 4 é 100 e a representação binária de 3 é 011 (lembre-se de & assume a representação binária desses números). Então nós temos:
Imagine esses valores sendo empilhados de maneira semelhante à adição elementar. O
&
operador diz que, se ambos os valores são iguais a 1, então o resultado é 1, caso contrário é 0. Assim1 & 1 = 1
,1 & 0 = 0
,0 & 0 = 0
, e0 & 1 = 0
. Então, fazemos as contas:O resultado é simplesmente 0. Então, voltamos e olhamos para o que nossa declaração de retorno agora se traduz em:
Que agora se traduz em:
Todos sabemos que
true && true
é simplestrue
, e isso mostra que, para o nosso exemplo, 4 é uma potência de 2.fonte
Alguns sites que documentam e explicam esse e outros hacks de manipulação de bits são:
( http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 )
( http://bits.stephan-brumme.com/isPowerOfTwo.html )
E o avô deles, o livro "O prazer do hacker", de Henry Warren, Jr .:
Como a página de Sean Anderson explica, a expressão
((x & (x - 1)) == 0)
indica incorretamente que 0 é uma potência de 2. Ele sugere usar:para corrigir esse problema.
fonte
!
só pode ser aplicada a tipos booleanos e&&
também requer que ambos os operandos sejam booleanos (exceto que operadores definidos pelo usuário fazer outras coisas possíveis, mas isso não é relevante paraulong
).return (i & -i) == i
fonte
i
definido também será definido-i
. Os bits abaixo desse valor serão 0 (em ambos os valores), enquanto os bits acima dele serão invertidos um em relação ao outro. O valor dei & -i
será, portanto, o bit de conjunto menos significativoi
(que é uma potência de dois). Sei
tiver o mesmo valor, esse foi o único bit definido. Ele falha quandoi
é 0 pelo mesmo motivo que oi & (i - 1) == 0
faz.i
é um tipo não assinado, o complemento dois não tem nada a ver com isso. Você está apenas aproveitando as propriedades da aritmética modular e bit a bit e.i==0
(retorna(0&0==0)
qual étrue
). Deveria serreturn i && ( (i&-i)==i )
fonte
Eu escrevi um artigo sobre isso recentemente em http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/ . Ele cobre a contagem de bits, como usar os logaritmos corretamente, a verificação clássica "x &&! (X & (x - 1))" e outros.
fonte
Aqui está uma solução simples em C ++ :
fonte
__builtin_popcount
. Infelizmente, uma família de processadores ainda não possui uma única instrução de montagem para fazer isso (x86); portanto, é o método mais rápido para contagem de bits. Em qualquer outra arquitetura, esta é uma única instrução de montagem.popcnt
O seguinte adendo à resposta aceita pode ser útil para algumas pessoas:
Uma potência de dois, quando expressa em binário, sempre se parecerá com 1 seguida por n zeros em que n é maior ou igual a 0. Ex:
e assim por diante.
Quando subtraímos
1
esses tipos de números, eles se tornam 0 seguidos por n e novamente n é o mesmo que acima. Ex:e assim por diante.
Chegando ao ponto crucial
O de
x
se alinha com o zero dex - 1
e todos os zeros dex
se alinham com os dex - 1
, fazendo com que o E bit a bit resulte em 0. E é assim que temos a resposta de linha única mencionada acima correta.Aumentando ainda mais a beleza da resposta aceita acima -
Portanto, agora temos uma propriedade à nossa disposição:
Um uso impressionante dessa propriedade é descobrir - quantos 1s estão presentes na representação binária de um determinado número? O código curto e suave para fazer isso para um determinado número inteiro
x
é:Outro aspecto dos números que pode ser provado a partir do conceito explicado acima é "Todo número positivo pode ser representado como a soma das potências de 2?".
Sim, todo número positivo pode ser representado como a soma das potências de 2. Para qualquer número, considere sua representação binária. Ex: pegue o número
117
.fonte
Depois de postar a pergunta, pensei na seguinte solução:
Precisamos verificar se exatamente um dos dígitos binários é um. Então, simplesmente mudamos o número para a direita, um dígito de cada vez, e retornamos
true
se for igual a 1. Se a qualquer momento chegarmos a um número ímpar ((number & 1) == 1
), sabemos que o resultado éfalse
. Isso provou (usando uma referência) um pouco mais rápido que o método original para valores verdadeiros (grandes) e muito mais rápido para valores falsos ou pequenos.Obviamente, a solução de Greg é muito melhor.
fonte
E aqui está um algoritmo geral para descobrir se um número é uma potência de outro número.
fonte
fonte
c#
? Eu acho que isso éc++
comox
é retornado como um bool.Descubra se o número fornecido é uma potência de 2.
fonte
frexp
vez delog
coisas desagradáveis se você quiser usar ponto flutuante.fonte
Isso é realmente rápido. Demora cerca de 6 minutos e 43 segundos para verificar todos os 2 ^ 32 números inteiros.
fonte
Se
x
for uma potência de dois, seu único bit 1 está em posiçãon
. Isso significa quex – 1
tem um 0 na posiçãon
. Para entender por que, lembre-se de como uma subtração binária funciona. Ao subtrair 1 dex
, o empréstimo se propaga até a posiçãon
; o bitn
passa a 0 e todos os bits inferiores passam a 1. Agora, comox
não possui 1 bit em comum comx – 1
,x & (x – 1)
é 0 e!(x & (x – 1))
é verdadeiro.fonte
Um número é uma potência de 2 se contiver apenas 1 bit definido. Podemos usar essa propriedade e a função genérica
countSetBits
para descobrir se um número é 2 ou não.Este é um programa C ++:
Não precisamos verificar explicitamente se 0 é uma potência de 2, pois retorna False para 0 também.
RESULTADO
fonte
while
vez de umif
? Pessoalmente, não vejo uma razão, mas parece que funciona. EDIT: - não ... ele retornará 1 para algo maior que0
!?Aqui está outro método que eu criei, neste caso usando em
|
vez de&
:fonte
(x > 0)
aqui?para qualquer potência de 2, o seguinte também é válido.
n & (- n) == n
OBSERVAÇÃO: falha para n = 0, portanto, é necessário verificar a
razão. Isso explica por que:
-n é o complemento 2s de n. -n terá todos os bits à esquerda do bit mais à direita definido de n invertido em comparação com n. Para potências de 2, existe apenas um bit definido.
fonte
Exemplo
Algoritmo
Usando uma máscara de bit, divida
NUM
a variável em binárioIF R > 0 AND L > 0: Return FALSE
Caso contrário,
NUM
torna-se diferente de zeroIF NUM = 1: Return TRUE
Caso contrário, vá para a Etapa 1
Complexidade
Hora ~
O(log(d))
onded
é o número de dígitos bináriosfonte
Melhorando a resposta de @ user134548, sem bits aritméticos:
Isso funciona bem para:
fonte
Mark Gravell sugeriu isso se você tiver o .NET Core 3, System.Runtime.Intrinsics.X86.Popcnt.PopCount
Instrução única, mais rápida do que
(x != 0) && ((x & (x - 1)) == 0)
mas menos portátil.fonte
(x != 0) && ((x & (x - 1)) == 0)
? Eu duvido disso, esp. em sistemas mais antigos onde o popcnt não está disponívelEm C, testei o
i && !(i & (i - 1)
truque e o comparei com__builtin_popcount(i)
, usando gcc no Linux, com o sinalizador -mpopcnt para garantir o uso da instrução POPCNT da CPU. Meu programa de teste contou o número de números inteiros entre 0 e 2 ^ 31 que eram uma potência de dois.No começo, pensei que
i && !(i & (i - 1)
era 10% mais rápido, apesar de ter verificado que o POPCNT era usado na desmontagem em que eu usava__builtin_popcount
.No entanto, percebi que havia incluído uma instrução if, e a previsão de ramificação provavelmente estava se saindo melhor na versão de twiddling. Eu removi o if e o POPCNT acabou mais rápido, conforme o esperado.
Resultados:
CPU Intel (R) Core (TM) i7-4771 no máximo 3,90GHz
Processador AMD Ryzen Threadripper 2950X de 16 núcleos, máximo de 3,50 GHz
Observe que aqui o CPU Intel parece um pouco mais lento que o AMD com o bit girando, mas possui um POPCNT muito mais rápido; o AMD POPCNT não oferece tanto impulso.
popcnt_test.c:
Execute testes:
fonte
Vejo muitas respostas sugerindo retornar n &&! (N & (n - 1)), mas, para minha experiência, se os valores de entrada forem negativos, ele retornará valores falsos. Compartilharei outra abordagem simples aqui, pois sabemos que uma potência de dois números tem apenas um bit definido; portanto, simplesmente contaremos o número de bits definido, isso levará tempo O (log N).
Verifique este artigo para contar não. de bits definidos
fonte
fonte
Este programa em java retorna "true" se número for uma potência de 2 e retorna "false" se não for uma potência de 2
fonte