Passed
Push — main ( b62539...4e03cd )
by smiley
01:53
created

GenericGFPoly::getCoefficients()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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