Dzisiaj potrzebowałem prostego algorytmu do sprawdzania, czy liczba jest potęgą 2.
Algorytm musi być:
- Prosty
- Prawidłowe dla dowolnej
ulong
wartości.
Wymyśliłem ten prosty algorytm:
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;
}
Ale potem pomyślałem, co powiesz na sprawdzenie, czy liczba jest dokładnie okrągła? Ale kiedy sprawdziłem 2 ^ 63 + 1, zwróciłem dokładnie 63 z powodu zaokrąglenia. Sprawdziłem więc, czy 2 do potęgi 63 jest równe pierwotnej liczbie - i jest tak, ponieważ obliczenia są wykonywane ws, a nie w dokładnych liczbach: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;
}
Ten wrócił true
do danej niewłaściwej wartości: 9223372036854775809
.
Czy istnieje lepszy algorytm?
(x & (x - 1))
może zwrócić wyniki fałszywie dodatnie, gdyX
jest sumą potęg dwóch, np8 + 16
.Odpowiedzi:
Istnieje prosty sposób na rozwiązanie tego problemu:
Uwaga: ta funkcja zgłosi się
true
za0
, co nie jest potęgą2
. Jeśli chcesz to wykluczyć, oto jak:Wyjaśnienie
Przede wszystkim bitowy operator binarny i operator z definicji MSDN:
Przyjrzyjmy się teraz, jak to wszystko wygląda:
Funkcja zwraca wartość logiczną (prawda / fałsz) i przyjmuje jeden przychodzący parametr typu bez znaku długi (w tym przypadku x). Załóżmy dla uproszczenia, że ktoś przekroczył wartość 4 i wywołał funkcję w ten sposób:
Teraz zamieniamy każde wystąpienie x na 4:
Cóż, wiemy już, że 4! = 0 ewaluuje do prawdy, jak dotąd tak dobrze. Ale co z:
To oczywiście tłumaczy:
Ale czym dokładnie jest
4&3
?Binarna reprezentacja 4 wynosi 100, a binarna reprezentacja 3 to 011 (pamiętaj, że & przyjmuje binarną reprezentację tych liczb). Więc mamy:
Wyobraź sobie, że te wartości są zestawiane podobnie jak elementarne dodawanie.
&
Operator mówi, że jeśli obie wartości są równe 1 to wynikiem jest 1, w przeciwnym wypadku 0. Więc1 & 1 = 1
,1 & 0 = 0
,0 & 0 = 0
, i0 & 1 = 0
. Więc wykonujemy matematykę:Wynik to po prostu 0. Wracamy więc do tego, co teraz przekłada się na naszą instrukcję return:
Co przekłada się teraz na:
Wszyscy wiemy, że
true && true
jest to po prostutrue
, a to pokazuje, że w naszym przykładzie 4 jest potęgą 2.źródło
Niektóre strony, które dokumentują i wyjaśniają to i inne kręcące się hacki, to:
( http://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2 )
( http://bits.stephan-brumme.com/isPowerOfTwo.html )
I Grandaddy z nich, że książka „Delight hackerów” Henry Warren Jr :
Jak wyjaśnia strona Seana Andersona , wyrażenie
((x & (x - 1)) == 0)
niepoprawnie wskazuje, że 0 to potęga 2. Sugeruje użycie:naprawić ten problem.
źródło
!
może być stosowane tylko do typów boolowskich, a&&
także wymaga, aby oba operandy były logiczne - (z wyjątkiem operatorów zdefiniowanych przez użytkownika umożliwiają inne rzeczy, ale nie dotyczy toulong
.)return (i & -i) == i
źródło
i
Ustawiony zostanie również najmniej znaczący bit, który zostanie ustawiony-i
. Bity poniżej tego będą wynosić 0 (w obu wartościach), a bity powyżej będą odwrócone względem siebie. Wartośći & -i
będzie zatem najmniej znaczącym ustawionym bitem wi
(który jest potęgą dwóch). Jeślii
ma taką samą wartość, to był to jedyny ustawiony bit. Błąd kończy się, gdyi
wynosi 0 z tego samego powodui & (i - 1) == 0
.i
jest to typ bez znaku, dopełnianie dwójki nie ma z tym nic wspólnego. Po prostu wykorzystujesz właściwości modułowej arytmetyki i bitowej i.i==0
(zwraca,(0&0==0)
co jesttrue
). Powinno byćreturn i && ( (i&-i)==i )
źródło
Ostatnio napisałem o tym artykuł na http://www.exploringbinary.com/ten-ways-to-check-if-an-integer-is-a-power-of-two-in-c/ . Obejmuje liczenie bitów, prawidłowe używanie logarytmów, klasyczne sprawdzanie „x &&! (X & (x - 1))” i inne.
źródło
Oto proste rozwiązanie C ++ :
źródło
__builtin_popcount
. Niestety, jedna rodzina procesorów nie ma jeszcze jednej instrukcji asemblera (x86), więc jest to najszybsza metoda liczenia bitów. W każdej innej architekturze jest to pojedyncza instrukcja montażu.popcnt
Poniższy dodatek do zaakceptowanej odpowiedzi może być przydatny dla niektórych osób:
Potęga dwóch wyrażona w postaci binarnej zawsze będzie wyglądać jak 1, po której następuje n zer, gdzie n jest większe lub równe 0. Przykład:
i tak dalej.
Kiedy odejmiemy
1
od tego rodzaju liczb, stają się one 0, po których następuje n, i znowu n jest takie samo jak powyżej. Dawny:i tak dalej.
Przechodząc do sedna
Jeden z
x
zostaje wyrównany do zerax - 1
i wszystkie zerax
zostają wyrównane z zeramix - 1
, powodując, że bitowe AND daje w wyniku 0. I w ten sposób otrzymaliśmy odpowiedź na jedną linię, o której mowa powyżej.Dalsze zwiększenie piękna przyjętej odpowiedzi powyżej -
Mamy więc teraz do dyspozycji nieruchomość:
Jednym z niesamowitych zastosowań tej właściwości jest ustalenie - ile jedynek jest obecnych w reprezentacji binarnej danej liczby? Krótki i słodki kod do zrobienia tego dla danej liczby całkowitej
x
to:Innym aspektem liczb, który można udowodnić na podstawie powyższej koncepcji, jest „czy każda liczba dodatnia może być reprezentowana jako suma potęg 2?” .
Tak, każda liczba dodatnia może być reprezentowana jako suma potęg 2. Liczby binarne reprezentuje dowolna liczba. Np .: Weź numer
117
.źródło
Po opublikowaniu pytania pomyślałem o następującym rozwiązaniu:
Musimy sprawdzić, czy dokładnie jedna z cyfr binarnych jest jedną. Więc po prostu przesuwamy liczbę w prawo o jedną cyfrę na raz i wracamy,
true
jeśli jest równa 1. Jeśli w dowolnym momencie otrzymamy nieparzystą liczbę ((number & 1) == 1
), wiemy, że wynik jestfalse
. Okazało się to (przy użyciu testu porównawczego) nieco szybciej niż oryginalna metoda dla (dużych) wartości prawdziwych i znacznie szybciej dla wartości fałszywych lub małych.Oczywiście rozwiązanie Grega jest znacznie lepsze.
źródło
A oto ogólny algorytm sprawdzania, czy liczba jest potęgą innej liczby.
źródło
źródło
c#
jest Wydaje mi się, że to jestc++
tak, jakx
jest zwracane jako bool.Sprawdź, czy podana liczba jest potęgą 2.
źródło
frexp
raczej nieprzyjemnychlog
rzeczy, jeśli chcesz użyć zmiennoprzecinkowego.źródło
To jest naprawdę szybkie. Sprawdzanie wszystkich liczb całkowitych 2 ^ 32 zajmuje około 6 minut i 43 sekund.
źródło
Jeśli
x
jest potęgą dwóch, jego samotny 1 bit jest na swoim miejscun
. Oznacza to, żex – 1
ma pozycję 0n
. Aby zobaczyć dlaczego, przypomnij sobie, jak działa odejmowanie binarne. Odejmując 1 odx
, pożyczka rozprzestrzenia się aż do pozycjin
; bitn
staje się 0, a wszystkie niższe bity stają się 1. Teraz, ponieważx
nie ma 1 wspólnego bitux – 1
,x & (x – 1)
ma wartość 0 i!(x & (x – 1))
jest prawdziwe.źródło
Liczba jest potęgą 2, jeśli zawiera tylko 1 ustawiony bit. Możemy użyć tej właściwości i funkcji ogólnej
countSetBits
aby sprawdzić, czy liczba jest potęgą 2, czy nie.To jest program w C ++:
Nie musimy jawnie sprawdzać, czy 0 jest potęgą 2, ponieważ zwraca również False dla 0.
WYNIK
źródło
while
zamiast zamiastif
? Osobiście nie widzę powodu, ale wydaje się, że działa. EDYCJA: - nie ... zwróci 1 za coś większego niż0
!?Oto inna metoda, którą opracowałem, w tym przypadku używając
|
zamiast&
:źródło
(x > 0)
trochę tutaj?dla dowolnej potęgi 2, obowiązuje również:
n & (- n) == n
UWAGA: kończy się niepowodzeniem dla n = 0, więc należy to sprawdzić.
Powód, dla którego to działa:
-n jest uzupełnieniem 2s n. -n będzie miał odwrócony bit po lewej stronie najbardziej ustawionego bitu n w porównaniu do n. Dla mocy 2 jest tylko jeden ustawiony bit.
źródło
Przykład
Algorytm
Za pomocą maski bitowej podziel
NUM
zmienną na binarnąIF R > 0 AND L > 0: Return FALSE
W przeciwnym razie
NUM
staje się niezerowymIF NUM = 1: Return TRUE
W przeciwnym razie przejdź do kroku 1
Złożoność
Czas ~
O(log(d))
gdzied
jest liczbą cyfr binarnychźródło
Poprawa odpowiedzi @ user134548, bez arytmetyki bitów:
Działa to dobrze dla:
źródło
Mark gravell zasugerował to, jeśli masz .NET Core 3, System.Runtime.Intrinsics.X86.Popcnt.PopCount
Pojedyncza instrukcja, szybsza niż,
(x != 0) && ((x & (x - 1)) == 0)
ale mniej przenośna.źródło
(x != 0) && ((x & (x - 1)) == 0)
? Wątpię w to, szczególnie. na starszych systemach, w których popcnt nie jest dostępnyW C przetestowałem
i && !(i & (i - 1)
sztuczkę i porównałem ją__builtin_popcount(i)
, używając gcc w Linuksie, z flagą -mpopcnt, aby mieć pewność, że użyję instrukcji POPCNT CPU. Mój program testowy policzył liczbę całkowitą od 0 do 2 ^ 31, która była potęgą dwóch.Na początku myślałem, że
i && !(i & (i - 1)
to 10% szybciej, mimo że zweryfikowałem, że POPCNT został użyty przy demontażu, w którym go użyłem__builtin_popcount
.Uświadomiłem sobie jednak, że załączyłem instrukcję if, a przewidywanie gałęzi prawdopodobnie lepiej działało w wersji z nieco zmienną wersją. Usunąłem if i POPCNT skończyło się szybciej, zgodnie z oczekiwaniami.
Wyniki:
Procesor Intel (R) Core (TM) i7-4771 maks. 3,90 GHz
16-rdzeniowy procesor AMD Ryzen Threadripper 2950X maks. 3,50 GHz
Zauważ, że tutaj procesor Intel wydaje się nieco wolniejszy niż AMD z nieco kręcącym się, ale ma znacznie szybszy POPCNT; AMD POPCNT nie zapewnia tak dużej poprawy.
popcnt_test.c:
Uruchom testy:
źródło
Widzę, że wiele odpowiedzi sugeruje zwrócenie n &&! (N & (n - 1)), ale według mojego doświadczenia, jeśli wartości wejściowe są ujemne, zwraca wartości fałszywe. Podzielę się tutaj innym prostym podejściem, ponieważ wiemy, że potęga dwóch liczb ma tylko jeden ustawiony bit, więc po prostu policzymy liczbę ustawionych bitów, zajmie to czas O (log N).
Sprawdź ten artykuł, aby policzyć nie. ustawionych bitów
źródło
źródło
Ten program w Javie zwraca „prawda”, jeśli liczba jest potęgą 2, i zwraca „fałsz”, jeśli nie jest potęgą 2
źródło