GenericGFPoly.php 6.5 KB

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