Passed
Push — master ( 0c19f1...8279ff )
by Ondřej
06:33
created

FixedBitString::fromNumber()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
declare(strict_types=1);
3
namespace Ivory\Value;
4
5
/**
6
 * Fixed-length bit string - a string of 1's and 0's.
7
 *
8
 * The objects are immutable, i.e., operations always produce a new object.
9
 * The representation and operations resemble the specification of the `BIT` type in PostgreSQL.
10
 *
11
 * It is possible to access individual bits using the array indices (readonly). The leftmost bit is at offset 0. Testing
12
 * whether the bit string has a bit at a given offset may be performed using `isset($this[$offset])`. Note that, apart
13
 * from reading out of such a call, it does not test whether the given bit is *set* (i.e., whether it is 1) - it merely
14
 * tests whether it is legal to access it. Negative offsets may be used to get bits off the end of the string.
15
 *
16
 * @see http://www.postgresql.org/docs/current/static/datatype-bit.html PostgreSQL Bit String Types
17
 * @see http://www.postgresql.org/docs/current/static/functions-bitstring.html PostgreSQL Bit String Functions and Operators
18
 */
19
class FixedBitString extends BitString
20
{
21
    /**
22
     * @param string $bits the bit string, i.e., a string of 1's and 0's
23
     * @param int|null $length length of the bit string in bits;
24
     *                         <tt>null</tt> for taking the length of <tt>$bits</tt>;
25
     *                         if less or greater than <tt>strlen($bits)</tt>, the bits get zero-padded or truncated on
26
     *                           the right to be exactly <tt>$length</tt> bits (and a warning is issued is case of
27
     *                           truncation)
28
     * @return FixedBitString
29
     * @throws \InvalidArgumentException if <tt>$length</tt> is a non-positive number (PostgreSQL forbids it)
30
     */
31
    public static function fromString(string $bits, ?int $length = null): FixedBitString
32
    {
33
        $bits = (string)$bits;
34
        $bitsLen = strlen($bits);
35
36
        if ($length === null) {
37
            return new FixedBitString($bits);
38
        } elseif ($length <= 0) {
39
            throw new \InvalidArgumentException('length is non-positive');
40
        } elseif ($length == $bitsLen) {
41
            return new FixedBitString($bits);
42
        } elseif ($length > $bitsLen) {
43
            return new FixedBitString(str_pad($bits, $length, '0'));
44
        } else { // $length < $bitsLen
45
            trigger_error("Bit string truncated to the length of $length", E_USER_WARNING);
46
            return new FixedBitString(substr($bits, 0, $length));
47
        }
48
    }
49
50
    /**
51
     * Creates a bit string as the two's complement of a given integer represented on a given number of bits.
52
     *
53
     * The least significant bit is the rightmost one in the resulting bit string.
54
     *
55
     * Overflows are not detected - if `$length` is not sufficient for representing the whole `$int`, only the `$length`
56
     * least significant bits of the representation are used quietly, without any notice.
57
     *
58
     * @param int $int integer to represent
59
     * @param int $length number of bits
60
     * @return FixedBitString
61
     * @throws \InvalidArgumentException if <tt>$length</tt> is a non-positive number
62
     */
63
    public static function fromInt(int $int, int $length): FixedBitString
64
    {
65
        if ($length <= 0) {
66
            throw new \InvalidArgumentException('length <= 0');
67
        }
68
69
        $bin = decbin($int);
70
71
        $padBit = ($int >= 0 ? '0' : '1');
72
        $padded = str_pad($bin, $length, $padBit, STR_PAD_LEFT);
73
74
        $truncated = substr($padded, -$length);
75
        return self::fromString($truncated);
76
    }
77
78
    /**
79
     * Returns a non-negative integer encoded by the bits in the bit string.
80
     *
81
     * The standard binary encoding is used. The rightmost bit in the string is the least significant in the integer.
82
     *
83
     * Only that many rightmost bits are taken which allow the encoded integer be represented correctly by the PHP `int`
84
     * type. That is 31 bits or 63 bits depending on whether this is a 32-bit or 64-bit compilation of PHP.
85
     *
86
     * For getting an arbitrary-length integer instead of the truncated `int`, use {@link toNumber()}.
87
     *
88
     * Note that, unlike {@link fromInt()}, this method does NOT work with the two's complement, but rather with the
89
     * standard binary encoding, and thus never returns any negative number. This is to resemble the PostgreSQL
90
     * behaviour.
91
     *
92
     * @return int
93
     */
94
    public function toInt(): int
95
    {
96
        return bindec(substr($this->bits, -(PHP_INT_SIZE * 8 - 1)));
97
    }
98
}
99