• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mlocati / ip-lib / 18010325950

25 Sep 2025 02:11PM UTC coverage: 99.003% (+0.02%) from 98.988%
18010325950

push

github

web-flow
Add PHPStan checks (#108)

76 of 77 new or added lines in 12 files covered. (98.7%)

1 existing line in 1 file now uncovered.

1192 of 1204 relevant lines covered (99.0%)

194.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.99
/src/Service/NumberInChunks.php
1
<?php
2

3
namespace IPLib\Service;
4

5
use InvalidArgumentException;
6

7
/**
8
 * @internal
9
 *
10
 * @readonly
11
 */
12
class NumberInChunks
13
{
14
    const CHUNKSIZE_BYTES = 8;
15

16
    const CHUNKSIZE_WORDS = 16;
17

18
    /**
19
     * @var bool
20
     */
21
    public $negative;
22

23
    /**
24
     * @var int[]
25
     */
26
    public $chunks;
27

28
    /**
29
     * @var int
30
     */
31
    public $chunkSize;
32

33
    /**
34
     * @param bool $negative
35
     * @param int[] $chunks
36
     * @param int $chunkSize
37
     */
38
    public function __construct($negative, array $chunks, $chunkSize)
39
    {
40
        $this->negative = $negative;
116✔
41
        $this->chunks = $chunks;
116✔
42
        $this->chunkSize = $chunkSize;
116✔
43
    }
44

45
    /**
46
     * @throws \InvalidArgumentException if $other has a $chunkSize that's not the same as the $chunkSize of this
47
     *
48
     * @return \IPLib\Service\NumberInChunks
49
     */
50
    public function negate()
51
    {
52
        return new self($this->chunks === array(0) ? false : !$this->negative, $this->chunks, $this->chunkSize);
13✔
53
    }
54

55
    /**
56
     * @throws \InvalidArgumentException if $other has a $chunkSize that's not the same as the $chunkSize of this
57
     *
58
     * @return \IPLib\Service\NumberInChunks
59
     */
60
    public function add(NumberInChunks $that)
61
    {
62
        if ($this->chunkSize !== $that->chunkSize) {
90✔
63
            throw new InvalidArgumentException('Incompatible chunk size');
1✔
64
        }
65
        if ($this->negative === $that->negative) {
89✔
66
            return new self($this->negative, self::addChunks($this->chunks, $that->chunks, $this->chunkSize), $this->chunkSize);
73✔
67
        }
68
        if ($that->negative) {
55✔
69
            list($negative, $chunks) = self::substractChunks($this->chunks, $that->chunks, $this->chunkSize);
54✔
70
        } else {
71
            list($negative, $chunks) = self::substractChunks($that->chunks, $this->chunks, $this->chunkSize);
8✔
72
        }
73

74
        return new self($negative, $chunks, $this->chunkSize);
55✔
75
    }
76

77
    /**
78
     * @param int $int
79
     * @param int $chunkSize
80
     *
81
     * @return \IPLib\Service\NumberInChunks
82
     */
83
    public static function fromInteger($int, $chunkSize)
84
    {
85
        if ($int === 0) {
89✔
86
            return new self(false, array(0), $chunkSize);
12✔
87
        }
88
        $negative = $int < 0;
81✔
89
        if ($negative) {
81✔
90
            $positiveInt = -$int;
58✔
91
            /** @var int|float $positiveInt may be float because -PHP_INT_MIN is bigger than PHP_INT_MAX */
92
            if (is_float($positiveInt)) {
58✔
UNCOV
93
                return self::fromNumericString((string) $int, $chunkSize);
×
94
            }
95
            $int = $positiveInt;
58✔
96
        }
97
        $bitMask = (1 << $chunkSize) - 1;
81✔
98
        $chunks = array();
81✔
99
        while ($int !== 0) {
81✔
100
            $chunks[] = $int & $bitMask;
81✔
101
            $int >>= $chunkSize;
81✔
102
        }
103

104
        return new self($negative, array_reverse($chunks), $chunkSize);
81✔
105
    }
106

107
    /**
108
     * @param string $numericString a string normalized with BinaryMath::normalizeIntegerString()
109
     * @param int $chunkSize
110
     *
111
     * @return \IPLib\Service\NumberInChunks
112
     */
113
    public static function fromNumericString($numericString, $chunkSize)
114
    {
115
        if ($numericString === '0') {
27✔
116
            return new self(false, array(0), $chunkSize);
2✔
117
        }
118
        $negative = $numericString[0] === '-';
25✔
119
        if ($negative) {
25✔
120
            $numericString = substr($numericString, 1);
12✔
121
        }
122
        $chunks = array();
25✔
123
        while ($numericString !== '0') {
25✔
124
            $chunks[] = self::modulo($numericString, $chunkSize);
25✔
125
            $numericString = self::divide($numericString, $chunkSize);
25✔
126
        }
127

128
        return new self($negative, array_reverse($chunks), $chunkSize);
25✔
129
    }
130

131
    /**
132
     * @param string $numericString
133
     * @param int $chunkSize
134
     *
135
     * @return int
136
     */
137
    private static function modulo($numericString, $chunkSize)
138
    {
139
        $divisor = 1 << $chunkSize;
25✔
140
        $carry = 0;
25✔
141
        $len = strlen($numericString);
25✔
142
        for ($i = 0; $i < $len; $i++) {
25✔
143
            $digit = (int) $numericString[$i];
25✔
144
            $carry = ($carry * 10 + $digit) % $divisor;
25✔
145
        }
146

147
        return $carry;
25✔
148
    }
149

150
    /**
151
     * @param string $numericString
152
     * @param int $chunkSize
153
     *
154
     * @return string
155
     */
156
    private static function divide($numericString, $chunkSize)
157
    {
158
        $divisor = 1 << $chunkSize;
25✔
159
        $quotient = '';
25✔
160
        $carry = 0;
25✔
161
        $len = strlen($numericString);
25✔
162
        for ($i = 0; $i < $len; $i++) {
25✔
163
            $digit = (int) $numericString[$i];
25✔
164
            $value = $carry * 10 + $digit;
25✔
165
            $quotient .= (string) ($value >> $chunkSize);
25✔
166
            $carry = $value % $divisor;
25✔
167
        }
168

169
        return ltrim($quotient, '0') ?: '0';
25✔
170
    }
171

172
    /**
173
     * @param int[] $addend1
174
     * @param int[] $addend2
175
     * @param int $chunkSize
176
     *
177
     * @return int[]
178
     */
179
    private static function addChunks(array $addend1, array $addend2, $chunkSize)
180
    {
181
        $divisor = 1 << $chunkSize;
73✔
182
        $result = array();
73✔
183
        $carry = 0;
73✔
184
        while ($addend1 !== array() || $addend2 !== array()) {
73✔
185
            $sum = $carry + (array_pop($addend1) ?: 0) + (array_pop($addend2) ?: 0);
73✔
186
            $result[] = $sum % $divisor;
73✔
187
            $carry = $sum >> $chunkSize;
73✔
188
        }
189
        if ($carry !== 0) {
73✔
190
            $result[] = $carry;
16✔
191
        }
192

193
        return array_reverse($result);
73✔
194
    }
195

196
    /**
197
     * @param int[] $minuend
198
     * @param int[] $subtrahend
199
     * @param int $chunkSize
200
     *
201
     * @return array{bool, int[]}
202
     */
203
    private static function substractChunks(array $minuend, array $subtrahend, $chunkSize)
204
    {
205
        $minuendCount = count($minuend);
55✔
206
        $subtrahendCount = count($subtrahend);
55✔
207
        if ($minuendCount > $subtrahendCount) {
55✔
208
            $count = $minuendCount;
20✔
209
            $negative = false;
20✔
210
        } elseif ($minuendCount < $subtrahendCount) {
37✔
211
            $count = $subtrahendCount;
4✔
212
            $negative = true;
4✔
213
        } else {
214
            $count = $minuendCount;
34✔
215
            $negative = false;
34✔
216
            for ($i = 0; $i < $count; $i++) {
34✔
217
                $delta = $minuend[$i] - $subtrahend[$i];
34✔
218
                if ($delta === 0) {
34✔
219
                    continue;
17✔
220
                }
221
                if ($delta < 0) {
25✔
222
                    $negative = true;
13✔
223
                }
224
                break;
25✔
225
            }
226
        }
227
        if ($negative) {
55✔
228
            list($minuend, $subtrahend) = array($subtrahend, $minuend);
16✔
229
        }
230
        $subtrahend = array_pad($subtrahend, -$count, 0);
55✔
231
        $borrowValue = 1 << $chunkSize;
55✔
232
        $result = array();
55✔
233
        $borrow = 0;
55✔
234
        for ($i = $count - 1; $i >= 0; $i--) {
55✔
235
            $value = $minuend[$i] - $subtrahend[$i] - $borrow;
55✔
236
            if ($value < 0) {
55✔
237
                $value += $borrowValue;
19✔
238
                $borrow = 1;
19✔
239
            } else {
240
                $borrow = 0;
55✔
241
            }
242
            $result[] = $value;
55✔
243
        }
244
        while (isset($result[1])) {
55✔
245
            $value = array_pop($result);
36✔
246
            if ($value !== 0) {
36✔
247
                $result[] = $value;
22✔
248
                break;
22✔
249
            }
250
        }
251

252
        return array($negative, array_reverse($result));
55✔
253
    }
254
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc