ReedSolomonDecoder.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. <?php
  2. /*
  3. * Copyright 2007 ZXing authors
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. namespace Zxing\Common\Reedsolomon;
  18. /**
  19. * <p>Implements Reed-Solomon decoding, as the name implies.</p>
  20. *
  21. * <p>The algorithm will not be explained here, but the following references were helpful
  22. * in creating this implementation:</p>
  23. *
  24. * <ul>
  25. * <li>Bruce Maggs.
  26. * <a href="http://www.cs.cmu.edu/afs/cs.cmu.edu/project/pscico-guyb/realworld/www/rs_decode.ps">
  27. * "Decoding Reed-Solomon Codes"</a> (see discussion of Forney's Formula)</li>
  28. * <li>J.I. Hall. <a href="www.mth.msu.edu/~jhall/classes/codenotes/GRS.pdf">
  29. * "Chapter 5. Generalized Reed-Solomon Codes"</a>
  30. * (see discussion of Euclidean algorithm)</li>
  31. * </ul>
  32. *
  33. * <p>Much credit is due to William Rucklidge since portions of this code are an indirect
  34. * port of his C++ Reed-Solomon implementation.</p>
  35. *
  36. * @author Sean Owen
  37. * @author William Rucklidge
  38. * @author sanfordsquires
  39. */
  40. final class ReedSolomonDecoder
  41. {
  42. public function __construct(private $field)
  43. {
  44. }
  45. /**
  46. * <p>Decodes given set of received codewords, which include both data and error-correction
  47. * codewords. Really, this means it uses Reed-Solomon to detect and correct errors, in-place,
  48. * in the input.</p>
  49. *
  50. * @param data $received and error-correction codewords
  51. * @param number $twoS of error-correction codewords available
  52. *
  53. * @throws ReedSolomonException if decoding fails for any reason
  54. */
  55. public function decode(&$received, $twoS)
  56. {
  57. $poly = new GenericGFPoly($this->field, $received);
  58. $syndromeCoefficients = fill_array(0, $twoS, 0);
  59. $noError = true;
  60. for ($i = 0; $i < $twoS; $i++) {
  61. $eval = $poly->evaluateAt($this->field->exp($i + $this->field->getGeneratorBase()));
  62. $syndromeCoefficients[(is_countable($syndromeCoefficients) ? count($syndromeCoefficients) : 0) - 1 - $i] = $eval;
  63. if ($eval != 0) {
  64. $noError = false;
  65. }
  66. }
  67. if ($noError) {
  68. return;
  69. }
  70. $syndrome = new GenericGFPoly($this->field, $syndromeCoefficients);
  71. $sigmaOmega =
  72. $this->runEuclideanAlgorithm($this->field->buildMonomial($twoS, 1), $syndrome, $twoS);
  73. $sigma = $sigmaOmega[0];
  74. $omega = $sigmaOmega[1];
  75. $errorLocations = $this->findErrorLocations($sigma);
  76. $errorMagnitudes = $this->findErrorMagnitudes($omega, $errorLocations);
  77. $errorLocationsCount = is_countable($errorLocations) ? count($errorLocations) : 0;
  78. for ($i = 0; $i < $errorLocationsCount; $i++) {
  79. $position = (is_countable($received) ? count($received) : 0) - 1 - $this->field->log($errorLocations[$i]);
  80. if ($position < 0) {
  81. throw new ReedSolomonException("Bad error location");
  82. }
  83. $received[$position] = GenericGF::addOrSubtract($received[$position], $errorMagnitudes[$i]);
  84. }
  85. }
  86. private function runEuclideanAlgorithm($a, $b, $R)
  87. {
  88. // Assume a's degree is >= b's
  89. if ($a->getDegree() < $b->getDegree()) {
  90. $temp = $a;
  91. $a = $b;
  92. $b = $temp;
  93. }
  94. $rLast = $a;
  95. $r = $b;
  96. $tLast = $this->field->getZero();
  97. $t = $this->field->getOne();
  98. // Run Euclidean algorithm until r's degree is less than R/2
  99. while ($r->getDegree() >= $R / 2) {
  100. $rLastLast = $rLast;
  101. $tLastLast = $tLast;
  102. $rLast = $r;
  103. $tLast = $t;
  104. // Divide rLastLast by rLast, with quotient in q and remainder in r
  105. if ($rLast->isZero()) {
  106. // Oops, Euclidean algorithm already terminated?
  107. throw new ReedSolomonException("r_{i-1} was zero");
  108. }
  109. $r = $rLastLast;
  110. $q = $this->field->getZero();
  111. $denominatorLeadingTerm = $rLast->getCoefficient($rLast->getDegree());
  112. $dltInverse = $this->field->inverse($denominatorLeadingTerm);
  113. while ($r->getDegree() >= $rLast->getDegree() && !$r->isZero()) {
  114. $degreeDiff = $r->getDegree() - $rLast->getDegree();
  115. $scale = $this->field->multiply($r->getCoefficient($r->getDegree()), $dltInverse);
  116. $q = $q->addOrSubtract($this->field->buildMonomial($degreeDiff, $scale));
  117. $r = $r->addOrSubtract($rLast->multiplyByMonomial($degreeDiff, $scale));
  118. }
  119. $t = $q->multiply($tLast)->addOrSubtract($tLastLast);
  120. if ($r->getDegree() >= $rLast->getDegree()) {
  121. throw new ReedSolomonException("Division algorithm failed to reduce polynomial?");
  122. }
  123. }
  124. $sigmaTildeAtZero = $t->getCoefficient(0);
  125. if ($sigmaTildeAtZero == 0) {
  126. throw new ReedSolomonException("sigmaTilde(0) was zero");
  127. }
  128. $inverse = $this->field->inverse($sigmaTildeAtZero);
  129. $sigma = $t->multiply($inverse);
  130. $omega = $r->multiply($inverse);
  131. return [$sigma, $omega];
  132. }
  133. private function findErrorLocations($errorLocator)
  134. {
  135. // This is a direct application of Chien's search
  136. $numErrors = $errorLocator->getDegree();
  137. if ($numErrors == 1) { // shortcut
  138. return [$errorLocator->getCoefficient(1)];
  139. }
  140. $result = fill_array(0, $numErrors, 0);
  141. $e = 0;
  142. for ($i = 1; $i < $this->field->getSize() && $e < $numErrors; $i++) {
  143. if ($errorLocator->evaluateAt($i) == 0) {
  144. $result[$e] = $this->field->inverse($i);
  145. $e++;
  146. }
  147. }
  148. if ($e != $numErrors) {
  149. throw new ReedSolomonException("Error locator degree does not match number of roots");
  150. }
  151. return $result;
  152. }
  153. private function findErrorMagnitudes($errorEvaluator, $errorLocations)
  154. {
  155. // This is directly applying Forney's Formula
  156. $s = is_countable($errorLocations) ? count($errorLocations) : 0;
  157. $result = fill_array(0, $s, 0);
  158. for ($i = 0; $i < $s; $i++) {
  159. $xiInverse = $this->field->inverse($errorLocations[$i]);
  160. $denominator = 1;
  161. for ($j = 0; $j < $s; $j++) {
  162. if ($i != $j) {
  163. //denominator = field.multiply(denominator,
  164. // GenericGF.addOrSubtract(1, field.multiply(errorLocations[j], xiInverse)));
  165. // Above should work but fails on some Apple and Linux JDKs due to a Hotspot bug.
  166. // Below is a funny-looking workaround from Steven Parkes
  167. $term = $this->field->multiply($errorLocations[$j], $xiInverse);
  168. $termPlus1 = ($term & 0x1) == 0 ? $term | 1 : $term & ~1;
  169. $denominator = $this->field->multiply($denominator, $termPlus1);
  170. }
  171. }
  172. $result[$i] = $this->field->multiply(
  173. $errorEvaluator->evaluateAt($xiInverse),
  174. $this->field->inverse($denominator)
  175. );
  176. if ($this->field->getGeneratorBase() != 0) {
  177. $result[$i] = $this->field->multiply($result[$i], $xiInverse);
  178. }
  179. }
  180. return $result;
  181. }
  182. }