This post comes from the first version of this blog.
Please send me an email if anything needs an update. Thanks!

Dzisiejszy wpis po raz kolejny [poprzednio pisałem o flagach bitowych] zabiera nas w świat manipulacji niskopoziomowymi danymi, odzierając nieco PHP z jego wygodnej, wysokopoziomowej abstrakcji. Skupimy się w nim na wewnętrznym sposobie reprezentacji liczb i odczytywaniu informacji o wartościach ich kolejnych bajtów.

Fotografia: Nic McPhee, CC-BY-SA.

Wstęp: Liczby.

Liczby w PHP można przedstawić na wiele różnych sposobów, a także na wiele sposobów je przekształcać w inne typy danych, czy też konwertować do różnych podstaw liczbowych. Jako programiści tego języka traktujemy to jako normalną i zupełnie zwyczajną funkcjonalność. W każdym razie liczba w PHP jest swojego rodzaju "integralnym typem" - niezależnie od jej postaci nie musimy się martwić o to, jak interpreter przetwarza ją w swoich zaawansowanych algorytmach.

Zupełnie inaczej podchodzą do tego programiści języków niższych poziomów - np. przywoływanych przeze mnie często [z sentymentu ;]] C i C++. Dla nich liczba to, zależnie od typu ciąg bitów / bajtów, którymi w dowolny sposób można manipulować - odczytywać, zapisywać, czy też zmieniać. W PHP nie jest to już takie proste - wymienione w poprzednim zdaniu operacje możemy wykonywać jedynie na liczbie, jako całości. Aby “dobrać się” do faktycznej reprezentacji jej fragmentu musimy stosować różne sztuczki, które z moich obserwacji nie są zbyt szeroko opisywane.

Oczywiście można chcieć odczytać wiele różnych informacji z potencjalnej liczby, aczkolwiek omówienie ich wszystkich jest raczej niemożliwe, bo każdemu przyszłoby do głowy wiele różnych wyników, potrzebnych właśnie jemu. Dlatego w niniejszym wpisie skupię się na pokazaniu w jaki sposób odczytać poszczególne bajty liczby całkowitej.

PHP: Odczytywanie wartości bajtów liczby całkowitej.

Weźmy pod uwagę przykładową liczbę:
287454020

W czym jest ona lepsza od innych? W sumie w niczym, poza tym, że jej postać heksadecymalna przyjmuje bardzo ładną formę:

11223344

Gdybyśmy programowali w C, czy C++, odczytanie konkretnych informacji o zawartości bitowej, czy też bajtowej dowolnych zmiennych prostych sprowadzałaby się do zrzutowania jej na jednobajtowy typ char, a następnie potraktowanie jak zwykłej tablicy. W PHP musimy się posłużyć bitowymi operatorami przesunięcia “»” oraz bitowym operatorem AND “&". Aby zatem odczytać wartość ostatniego bajtu naszej liczby, musimy wykonać następujący fragment kodu:

1
echo dechex(0x11223344 & 0xFF); // 44

Wynik tej operacji jest równy wartości z komentarza - 44. Specjalnie skonwertowałem ją do formatu szesnastkowego za pomocą funkcji dechex(), aby można było zobaczyć, że jest ona równa dokładnie tyle, ile wynoszą ostatnie dwie cyfry tej reprezentacji.

Wszystko wydaje się proste i jasne, ale jak w takim razie odczytać wartości kolejnych bajtów? Musimy wykorzystać w tym celu operator przesunięcia bitowego w prawo o 8 bitów, który umożliwi nam odczytanie przedostatniego bajtu, który tym razem będzie ostatni. Zdaję sobie sprawę z tego, że moja definicja jest cokolwiek zagmatwana, dlatego myślę, że poniższy fragment kodu wszystko wyjaśni:

1
echo dechex((0x11223344 >> 8) & 0xFF); // 33

W ten sposób możemy dobrać się do dowolnego bajtu wpisanej tam liczby. Na horyzoncie pojawia się jednak kolejny problem - jak odczytać wartość arbitralnie wybranego bajtu danej liczby? Oczywiście można przesuwać bitowo o 16, 24 bity i wtedy stosować podaną wyżej linijkę, jednak nie jest to optymalny sposób, kiedy np. potrzebujemy wartości bajtów całej liczby. Szczególnie, jeśli jest ona mniejsza niż 2^32 i określony numer bajtu będzie w niej równy po prostu 0. Wychodząc naprzeciw temu problemowi stworzyłem dla Was funkcję, która zwraca wartości kolejnych bajtów liczby w ładnej tablicy i określonej podstawie liczbowej:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function getBytes($number, $base = 10)
	{
	$bytesNumber = ceil(ceil(log($number, 2)) / 8);
	$bytes = array();
	for($i = $bytesNumber - 1; $i >= 0; $i--)
		{
		$bytes[] = base_convert(($number >> ($i * 8)) & 0xFF, 10, $base);
		}
	return $bytes;
	}

Wywołanie i wynik:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
var_dump(getBytes(0x11223344, 16));

array(4) {
  [0]=>
  string(2) "11"
  [1]=>
  string(2) "22"
  [2]=>
  string(2) "33"
  [3]=>
  string(2) "44"
}

Działanie tej funkcji jest relatywnie proste: najpierw sprawdzamy, ile bajtów zajmuje podana liczba, a następnie tyle razy przesuwamy ją o kolejną wielokrotność ósemki i na koniec zapisujemy w tablicy w wybranym formacie. Licencja: “bierz jak chcesz, bo ja nie skąpię [ale kuku, nie ustąpię]” - Julian Tuwim, “Ptasie Radio” [licencja nieznana]. Dlatego gdybyście kiedykolwiek potrzebowali takiego gotowego rozwiązania - bierzcie i częstujcie się! ;]