GenericGFPoly.php 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <?php
  2. /**
  3. * Class GenericGFPoly
  4. *
  5. * @filesource GenericGFPoly.php
  6. * @created 16.01.2021
  7. * @package chillerlan\QRCode\Common
  8. * @author ZXing Authors
  9. * @author Smiley <smiley@chillerlan.net>
  10. * @copyright 2021 Smiley
  11. * @license Apache-2.0
  12. */
  13. namespace chillerlan\QRCode\Common;
  14. use InvalidArgumentException;
  15. use function array_fill, array_slice, array_splice, count;
  16. /**
  17. * <p>Represents a polynomial whose coefficients are elements of a GF.
  18. * Instances of this class are immutable.</p>
  19. *
  20. * <p>Much credit is due to William Rucklidge since portions of this code are an indirect
  21. * port of his C++ Reed-Solomon implementation.</p>
  22. *
  23. * @author Sean Owen
  24. */
  25. final class GenericGFPoly{
  26. private array $coefficients;
  27. /**
  28. * @param array $coefficients array coefficients as ints representing elements of GF(size), arranged
  29. * from most significant (highest-power term) coefficient to least significant
  30. * @param int|null $degree
  31. *
  32. * @throws \InvalidArgumentException if argument is null or empty, or if leading coefficient is 0 and this is not a
  33. * constant polynomial (that is, it is not the monomial "0")
  34. */
  35. public function __construct(array $coefficients, int $degree = null){
  36. $degree ??= 0;
  37. if(empty($coefficients)){
  38. throw new InvalidArgumentException('arg $coefficients is empty');
  39. }
  40. if($degree < 0){
  41. throw new InvalidArgumentException('negative degree');
  42. }
  43. $coefficientsLength = count($coefficients);
  44. // Leading term must be non-zero for anything except the constant polynomial "0"
  45. $firstNonZero = 0;
  46. while($firstNonZero < $coefficientsLength && $coefficients[$firstNonZero] === 0){
  47. $firstNonZero++;
  48. }
  49. if($firstNonZero === $coefficientsLength){
  50. $this->coefficients = [0];
  51. }
  52. else{
  53. $this->coefficients = array_fill(0, $coefficientsLength - $firstNonZero + $degree, 0);
  54. for($i = 0; $i < $coefficientsLength - $firstNonZero; $i++){
  55. $this->coefficients[$i] = $coefficients[$i + $firstNonZero];
  56. }
  57. }
  58. }
  59. /**
  60. * @return int $coefficient of x^degree term in this polynomial
  61. */
  62. public function getCoefficient(int $degree):int{
  63. return $this->coefficients[count($this->coefficients) - 1 - $degree];
  64. }
  65. /**
  66. * @return int[]
  67. */
  68. public function getCoefficients():array{
  69. return $this->coefficients;
  70. }
  71. /**
  72. * @return int $degree of this polynomial
  73. */
  74. public function getDegree():int{
  75. return count($this->coefficients) - 1;
  76. }
  77. /**
  78. * @return bool true if this polynomial is the monomial "0"
  79. */
  80. public function isZero():bool{
  81. return $this->coefficients[0] === 0;
  82. }
  83. /**
  84. * @return int evaluation of this polynomial at a given point
  85. */
  86. public function evaluateAt(int $a):int{
  87. if($a === 0){
  88. // Just return the x^0 coefficient
  89. return $this->getCoefficient(0);
  90. }
  91. $result = 0;
  92. foreach($this->coefficients as $c){
  93. // if $a === 1 just the sum of the coefficients
  94. $result = GF256::addOrSubtract(($a === 1 ? $result : GF256::multiply($a, $result)), $c);
  95. }
  96. return $result;
  97. }
  98. /**
  99. * @param \chillerlan\QRCode\Common\GenericGFPoly $other
  100. *
  101. * @return \chillerlan\QRCode\Common\GenericGFPoly
  102. */
  103. public function multiply(GenericGFPoly $other):GenericGFPoly{
  104. if($this->isZero() || $other->isZero()){
  105. return new self([0]);
  106. }
  107. $product = array_fill(0, count($this->coefficients) + count($other->coefficients) - 1, 0);
  108. foreach($this->coefficients as $i => $aCoeff){
  109. foreach($other->coefficients as $j => $bCoeff){
  110. $product[$i + $j] ^= GF256::multiply($aCoeff, $bCoeff);
  111. }
  112. }
  113. return new self($product);
  114. }
  115. /**
  116. * @param \chillerlan\QRCode\Common\GenericGFPoly $other
  117. *
  118. * @return \chillerlan\QRCode\Common\GenericGFPoly[] [quotient, remainder]
  119. */
  120. public function divide(GenericGFPoly $other):array{
  121. if($other->isZero()){
  122. throw new InvalidArgumentException('Division by 0');
  123. }
  124. $quotient = new self([0]);
  125. $remainder = clone $this;
  126. $denominatorLeadingTerm = $other->getCoefficient($other->getDegree());
  127. $inverseDenominatorLeadingTerm = GF256::inverse($denominatorLeadingTerm);
  128. while($remainder->getDegree() >= $other->getDegree() && !$remainder->isZero()){
  129. $scale = GF256::multiply($remainder->getCoefficient($remainder->getDegree()), $inverseDenominatorLeadingTerm);
  130. $diff = $remainder->getDegree() - $other->getDegree();
  131. $quotient = $quotient->addOrSubtract(GF256::buildMonomial($diff, $scale));
  132. $remainder = $remainder->addOrSubtract($other->multiplyByMonomial($diff, $scale));
  133. }
  134. return [$quotient, $remainder];
  135. }
  136. /**
  137. * @param int $scalar
  138. *
  139. * @return \chillerlan\QRCode\Common\GenericGFPoly
  140. */
  141. public function multiplyInt(int $scalar):GenericGFPoly{
  142. if($scalar === 0){
  143. return new self([0]);
  144. }
  145. if($scalar === 1){
  146. return $this;
  147. }
  148. $product = array_fill(0, count($this->coefficients), 0);
  149. foreach($this->coefficients as $i => $c){
  150. $product[$i] = GF256::multiply($c, $scalar);
  151. }
  152. return new self($product);
  153. }
  154. /**
  155. * @param int $degree
  156. * @param int $coefficient
  157. *
  158. * @return \chillerlan\QRCode\Common\GenericGFPoly
  159. */
  160. public function multiplyByMonomial(int $degree, int $coefficient):GenericGFPoly{
  161. if($degree < 0){
  162. throw new InvalidArgumentException();
  163. }
  164. if($coefficient === 0){
  165. return new self([0]);
  166. }
  167. $product = array_fill(0, count($this->coefficients) + $degree, 0);
  168. foreach($this->coefficients as $i => $c){
  169. $product[$i] = GF256::multiply($c, $coefficient);
  170. }
  171. return new self($product);
  172. }
  173. /**
  174. * @param \chillerlan\QRCode\Common\GenericGFPoly $other
  175. *
  176. * @return \chillerlan\QRCode\Common\GenericGFPoly
  177. */
  178. public function mod(GenericGFPoly $other):GenericGFPoly{
  179. if(count($this->coefficients) - count($other->coefficients) < 0){
  180. return $this;
  181. }
  182. $ratio = GF256::log($this->coefficients[0]) - GF256::log($other->coefficients[0]);
  183. foreach($other->coefficients as $i => $c){
  184. $this->coefficients[$i] ^= GF256::exp(GF256::log($c) + $ratio);
  185. }
  186. return (new self($this->coefficients))->mod($other);
  187. }
  188. /**
  189. * @param \chillerlan\QRCode\Common\GenericGFPoly $other
  190. *
  191. * @return \chillerlan\QRCode\Common\GenericGFPoly
  192. */
  193. public function addOrSubtract(GenericGFPoly $other):GenericGFPoly{
  194. if($this->isZero()){
  195. return $other;
  196. }
  197. if($other->isZero()){
  198. return $this;
  199. }
  200. $smallerCoefficients = $this->coefficients;
  201. $largerCoefficients = $other->coefficients;
  202. if(count($smallerCoefficients) > count($largerCoefficients)){
  203. $temp = $smallerCoefficients;
  204. $smallerCoefficients = $largerCoefficients;
  205. $largerCoefficients = $temp;
  206. }
  207. $sumDiff = array_fill(0, count($largerCoefficients), 0);
  208. $lengthDiff = count($largerCoefficients) - count($smallerCoefficients);
  209. // Copy high-order terms only found in higher-degree polynomial's coefficients
  210. array_splice($sumDiff, 0, $lengthDiff, array_slice($largerCoefficients, 0, $lengthDiff));
  211. $countLargerCoefficients = count($largerCoefficients);
  212. for($i = $lengthDiff; $i < $countLargerCoefficients; $i++){
  213. $sumDiff[$i] = GF256::addOrSubtract($smallerCoefficients[$i - $lengthDiff], $largerCoefficients[$i]);
  214. }
  215. return new self($sumDiff);
  216. }
  217. }