BitArray.php 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <?php
  2. declare(strict_types = 1);
  3. namespace BaconQrCode\Common;
  4. use BaconQrCode\Exception\InvalidArgumentException;
  5. use SplFixedArray;
  6. /**
  7. * A simple, fast array of bits.
  8. */
  9. final class BitArray
  10. {
  11. /**
  12. * Bits represented as an array of integers.
  13. *
  14. * @var SplFixedArray<int>
  15. */
  16. private $bits;
  17. /**
  18. * Size of the bit array in bits.
  19. *
  20. * @var int
  21. */
  22. private $size;
  23. /**
  24. * Creates a new bit array with a given size.
  25. */
  26. public function __construct(int $size = 0)
  27. {
  28. $this->size = $size;
  29. $this->bits = SplFixedArray::fromArray(array_fill(0, ($this->size + 31) >> 3, 0));
  30. }
  31. /**
  32. * Gets the size in bits.
  33. */
  34. public function getSize() : int
  35. {
  36. return $this->size;
  37. }
  38. /**
  39. * Gets the size in bytes.
  40. */
  41. public function getSizeInBytes() : int
  42. {
  43. return ($this->size + 7) >> 3;
  44. }
  45. /**
  46. * Ensures that the array has a minimum capacity.
  47. */
  48. public function ensureCapacity(int $size) : void
  49. {
  50. if ($size > count($this->bits) << 5) {
  51. $this->bits->setSize(($size + 31) >> 5);
  52. }
  53. }
  54. /**
  55. * Gets a specific bit.
  56. */
  57. public function get(int $i) : bool
  58. {
  59. return 0 !== ($this->bits[$i >> 5] & (1 << ($i & 0x1f)));
  60. }
  61. /**
  62. * Sets a specific bit.
  63. */
  64. public function set(int $i) : void
  65. {
  66. $this->bits[$i >> 5] = $this->bits[$i >> 5] | 1 << ($i & 0x1f);
  67. }
  68. /**
  69. * Flips a specific bit.
  70. */
  71. public function flip(int $i) : void
  72. {
  73. $this->bits[$i >> 5] ^= 1 << ($i & 0x1f);
  74. }
  75. /**
  76. * Gets the next set bit position from a given position.
  77. */
  78. public function getNextSet(int $from) : int
  79. {
  80. if ($from >= $this->size) {
  81. return $this->size;
  82. }
  83. $bitsOffset = $from >> 5;
  84. $currentBits = $this->bits[$bitsOffset];
  85. $bitsLength = count($this->bits);
  86. $currentBits &= ~((1 << ($from & 0x1f)) - 1);
  87. while (0 === $currentBits) {
  88. if (++$bitsOffset === $bitsLength) {
  89. return $this->size;
  90. }
  91. $currentBits = $this->bits[$bitsOffset];
  92. }
  93. $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
  94. return $result > $this->size ? $this->size : $result;
  95. }
  96. /**
  97. * Gets the next unset bit position from a given position.
  98. */
  99. public function getNextUnset(int $from) : int
  100. {
  101. if ($from >= $this->size) {
  102. return $this->size;
  103. }
  104. $bitsOffset = $from >> 5;
  105. $currentBits = ~$this->bits[$bitsOffset];
  106. $bitsLength = count($this->bits);
  107. $currentBits &= ~((1 << ($from & 0x1f)) - 1);
  108. while (0 === $currentBits) {
  109. if (++$bitsOffset === $bitsLength) {
  110. return $this->size;
  111. }
  112. $currentBits = ~$this->bits[$bitsOffset];
  113. }
  114. $result = ($bitsOffset << 5) + BitUtils::numberOfTrailingZeros($currentBits);
  115. return $result > $this->size ? $this->size : $result;
  116. }
  117. /**
  118. * Sets a bulk of bits.
  119. */
  120. public function setBulk(int $i, int $newBits) : void
  121. {
  122. $this->bits[$i >> 5] = $newBits;
  123. }
  124. /**
  125. * Sets a range of bits.
  126. *
  127. * @throws InvalidArgumentException if end is smaller than start
  128. */
  129. public function setRange(int $start, int $end) : void
  130. {
  131. if ($end < $start) {
  132. throw new InvalidArgumentException('End must be greater or equal to start');
  133. }
  134. if ($end === $start) {
  135. return;
  136. }
  137. --$end;
  138. $firstInt = $start >> 5;
  139. $lastInt = $end >> 5;
  140. for ($i = $firstInt; $i <= $lastInt; ++$i) {
  141. $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
  142. $lastBit = $i < $lastInt ? 31 : $end & 0x1f;
  143. if (0 === $firstBit && 31 === $lastBit) {
  144. $mask = 0x7fffffff;
  145. } else {
  146. $mask = 0;
  147. for ($j = $firstBit; $j < $lastBit; ++$j) {
  148. $mask |= 1 << $j;
  149. }
  150. }
  151. $this->bits[$i] = $this->bits[$i] | $mask;
  152. }
  153. }
  154. /**
  155. * Clears the bit array, unsetting every bit.
  156. */
  157. public function clear() : void
  158. {
  159. $bitsLength = count($this->bits);
  160. for ($i = 0; $i < $bitsLength; ++$i) {
  161. $this->bits[$i] = 0;
  162. }
  163. }
  164. /**
  165. * Checks if a range of bits is set or not set.
  166. * @throws InvalidArgumentException if end is smaller than start
  167. */
  168. public function isRange(int $start, int $end, bool $value) : bool
  169. {
  170. if ($end < $start) {
  171. throw new InvalidArgumentException('End must be greater or equal to start');
  172. }
  173. if ($end === $start) {
  174. return true;
  175. }
  176. --$end;
  177. $firstInt = $start >> 5;
  178. $lastInt = $end >> 5;
  179. for ($i = $firstInt; $i <= $lastInt; ++$i) {
  180. $firstBit = $i > $firstInt ? 0 : $start & 0x1f;
  181. $lastBit = $i < $lastInt ? 31 : $end & 0x1f;
  182. if (0 === $firstBit && 31 === $lastBit) {
  183. $mask = 0x7fffffff;
  184. } else {
  185. $mask = 0;
  186. for ($j = $firstBit; $j <= $lastBit; ++$j) {
  187. $mask |= 1 << $j;
  188. }
  189. }
  190. if (($this->bits[$i] & $mask) !== ($value ? $mask : 0)) {
  191. return false;
  192. }
  193. }
  194. return true;
  195. }
  196. /**
  197. * Appends a bit to the array.
  198. */
  199. public function appendBit(bool $bit) : void
  200. {
  201. $this->ensureCapacity($this->size + 1);
  202. if ($bit) {
  203. $this->bits[$this->size >> 5] = $this->bits[$this->size >> 5] | (1 << ($this->size & 0x1f));
  204. }
  205. ++$this->size;
  206. }
  207. /**
  208. * Appends a number of bits (up to 32) to the array.
  209. * @throws InvalidArgumentException if num bits is not between 0 and 32
  210. */
  211. public function appendBits(int $value, int $numBits) : void
  212. {
  213. if ($numBits < 0 || $numBits > 32) {
  214. throw new InvalidArgumentException('Num bits must be between 0 and 32');
  215. }
  216. $this->ensureCapacity($this->size + $numBits);
  217. for ($numBitsLeft = $numBits; $numBitsLeft > 0; $numBitsLeft--) {
  218. $this->appendBit((($value >> ($numBitsLeft - 1)) & 0x01) === 1);
  219. }
  220. }
  221. /**
  222. * Appends another bit array to this array.
  223. */
  224. public function appendBitArray(self $other) : void
  225. {
  226. $otherSize = $other->getSize();
  227. $this->ensureCapacity($this->size + $other->getSize());
  228. for ($i = 0; $i < $otherSize; ++$i) {
  229. $this->appendBit($other->get($i));
  230. }
  231. }
  232. /**
  233. * Makes an exclusive-or comparision on the current bit array.
  234. *
  235. * @throws InvalidArgumentException if sizes don't match
  236. */
  237. public function xorBits(self $other) : void
  238. {
  239. $bitsLength = count($this->bits);
  240. $otherBits = $other->getBitArray();
  241. if ($bitsLength !== count($otherBits)) {
  242. throw new InvalidArgumentException('Sizes don\'t match');
  243. }
  244. for ($i = 0; $i < $bitsLength; ++$i) {
  245. $this->bits[$i] = $this->bits[$i] ^ $otherBits[$i];
  246. }
  247. }
  248. /**
  249. * Converts the bit array to a byte array.
  250. *
  251. * @return SplFixedArray<int>
  252. */
  253. public function toBytes(int $bitOffset, int $numBytes) : SplFixedArray
  254. {
  255. $bytes = new SplFixedArray($numBytes);
  256. for ($i = 0; $i < $numBytes; ++$i) {
  257. $byte = 0;
  258. for ($j = 0; $j < 8; ++$j) {
  259. if ($this->get($bitOffset)) {
  260. $byte |= 1 << (7 - $j);
  261. }
  262. ++$bitOffset;
  263. }
  264. $bytes[$i] = $byte;
  265. }
  266. return $bytes;
  267. }
  268. /**
  269. * Gets the internal bit array.
  270. *
  271. * @return SplFixedArray<int>
  272. */
  273. public function getBitArray() : SplFixedArray
  274. {
  275. return $this->bits;
  276. }
  277. /**
  278. * Reverses the array.
  279. */
  280. public function reverse() : void
  281. {
  282. $newBits = new SplFixedArray(count($this->bits));
  283. for ($i = 0; $i < $this->size; ++$i) {
  284. if ($this->get($this->size - $i - 1)) {
  285. $newBits[$i >> 5] = $newBits[$i >> 5] | (1 << ($i & 0x1f));
  286. }
  287. }
  288. $this->bits = $newBits;
  289. }
  290. /**
  291. * Returns a string representation of the bit array.
  292. */
  293. public function __toString() : string
  294. {
  295. $result = '';
  296. for ($i = 0; $i < $this->size; ++$i) {
  297. if (0 === ($i & 0x07)) {
  298. $result .= ' ';
  299. }
  300. $result .= $this->get($i) ? 'X' : '.';
  301. }
  302. return $result;
  303. }
  304. }