Passed
Push — master ( 0f1498...d1cca9 )
by Fabio
18:34 queued 12:41
created

TBitHelper::crc32()   F

Complexity

Conditions 32
Paths 3960

Size

Total Lines 108
Code Lines 87

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 32
eloc 87
nc 3960
nop 3
dl 0
loc 108
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * TBitHelper class file
4
 *
5
 * @author Brad Anderson <[email protected]>
6
 * @link https://github.com/pradosoft/prado
7
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
8
 */
9
10
namespace Prado\Util\Helpers;
11
12
use Prado\Exceptions\TInvalidDataValueException;
13
14
/**
15
 * TBitHelper class.
16
 *
17
 * This class contains static functions for bit-wise and byte operations, like color
18
 * bit shifting, unsigned right bit shift, mirroring the order of bits, flipping
19
 * endian, and formatting floats into and from smaller float representations like
20
 * half floats (Fp16, Bf16) and mini floats (Fp8).  It also can check for negative
21
 * floats including negative zero.
22
 *
23
 * Shifting bits for color accuracy requires repeating the bits rather than
24
 * just adding extra 0/1 bits.  {@link colorBitShift} properly adds and removes bits
25
 * to an integer color value by replicating the bits for new bits.
26
 *
27
 * There are specific floating point conversion methods for converting float to:
28
 *   - Fp16 with {@link floatToFp16} and back with {@link fp16ToFloat}.
29
 *   - Bf16 with {@link floatToBf16} and back with {@link bf16ToFloat}.
30
 *   - Fp8-e5m2 with {@link floatToFp8Range} and back with {@link fp8RangeToFloat}.
31
 *   - Fp8-e4m3 with {@link floatToFp8Precision} and back with {@link fp8PrecisionToFloat}.
32
 * These functions use the general conversion functions {@link floatToFpXX} and
33
 * {@link fpXXToFloat} where the number of bits for the exponent and mantissa are
34
 * parameters. For example, 24 bit floats or 14 bit floats can be created.
35
 *
36
 * {@link mirrorBits} can mirror arbitrary runs of bits in an integer.  There is
37
 * quick mirroring for specific exponents of two: {@link mirrorByte} for 8 bits,
38
 * {@link mirrorShort} for 16 bits, {@link mirrorLong} for 32 bits, and, on 64 bit
39
 * instances of PHP, {@link mirrorLongLong} for 64 bits.
40
 *
41
 * There are endian byte reversal functions: {@link flipEndianShort}, {@link flipEndianLong},
42
 * and, on 64 bit instances of PHP, {@link flipEndianLongLong}.
43
 *
44
 * {@link bitCount} calculates the number of bits required to represent a specific
45
 * number. 255 return 8 bits, 256 returns 9 bits.
46
 *
47
 * {@link isNegativeFloat} is used to determine if a float has the negative bit
48
 * set.  It will return true on any negative float number, including negative zero.
49
 * {@link isNegativeZero} can check if a float is a negative zero.  PHP cannot normally
50
 * check for negative zero float and requires these special functions to so.
51
 *
52
 * The Levels and Masks are for O(1) time bit reversals of 8, 16, 32, and 64 bit integers.
53
 * The TBitHelper class automatically adjusts itself for 32 or 64 bit PHP environments.
54
 *
55
 * When quickly mirroring bits or switching endian, the high bits are also converted
56
 * like the low bits.  E.g. When mirroring a Byte, all bytes in the integer are
57
 * individually mirrored in place. When converting a Short, each short in the integer
58
 * will be converted in place. In the instance of a Long, for 64 bit systems will
59
 * convert both Longs -in place- in its LongLong (64 bit) unit integer type.
60
 * Converting LongLong is only supported in 64 bit PHP environments.
61
 *
62
 * @author Brad Anderson <[email protected]>
63
 * @since 4.2.3
64
 */
65
class TBitHelper
66
{
67
	// Defined constants for 32 bit computation
68
	public const PHP_INT32_MIN = -2147483648;	// 0x80000000
69
	public const PHP_INT32_MAX = 2147483647;	// 0x7FFFFFFF
70
	// on 32 bit systems the PHP_INT64_UMAX is a float and not a integer.
71
	public const PHP_INT32_UMAX = 4294967295;	// 0xFFFFFFFF (unsigned)
72
	public const PHP_INT32_MASK = (PHP_INT_SIZE > 4) ? self::PHP_INT32_UMAX : -1;
73
74
	// Defined constants for 64 bit computation
75
	//   on 32 bit systems these values are only approximate floats and not integers.
76
	public const PHP_INT64_MIN = -9223372036854775808;	// 0x80000000_00000000
77
	public const PHP_INT64_MAX = 9223372036854775807;	// 0x7FFFFFFF_FFFFFFFF
78
	//PHP_INT64_UMAX is a float that only approximates the maximum, unless using 16 byte int
79
	public const PHP_INT64_UMAX = 18446744073709551615;	// 0xFFFFFFFF_FFFFFFFF (unsigned)
80
	public const PHP_INT64_MASK = -1; // Assuming 64 bit is validated.
81
82
	public const Level1 = (PHP_INT_SIZE >= 8) ? 0x5555555555555555 : 0x55555555;
83
	public const NLevel1 = ~self::Level1;
84
	public const Mask1 = (PHP_INT_SIZE >= 8) ? 0x7FFFFFFFFFFFFFFF : 0x7FFFFFFF;
85
	public const Level2 = (PHP_INT_SIZE >= 8) ? 0x3333333333333333 : 0x33333333;
86
	public const NLevel2 = ~self::Level2;
87
	public const Mask2 = self::Mask1 >> 1;
88
	public const Level3 = (PHP_INT_SIZE >= 8) ? 0x0F0F0F0F0F0F0F0F : 0x0F0F0F0F;
89
	public const NLevel3 = ~self::Level3;
90
	public const Mask3 = self::Mask1 >> 3;
91
	public const Level4 = (PHP_INT_SIZE >= 8) ? 0x00FF00FF00FF00FF : 0x00FF00FF;
92
	public const NLevel4 = ~self::Level4;
93
	public const Mask4 = self::Mask1 >> 7;
94
	public const Level5 = (PHP_INT_SIZE >= 8) ? 0x0000FFFF0000FFFF : 0x0000FFFF;
95
	public const NLevel5 = ~self::Level5;
96
	public const Mask5 = self::Mask1 >> 15;
97
	public const Level6 = (PHP_INT_SIZE >= 8) ? 0x00000000FFFFFFFF : -1;
98
	public const NLevel6 = ~self::Level6;
99
	public const Mask6 = self::Mask1 >> 31;
100
101
	/**
102
	 * Motorola is Big Endian with the Most Significant Byte first whereas Intel uses
103
	 * Little Endian with the Least Significant Byte first.  This mainly only affects
104
	 * the binary reading and writing of data types that are 2 bytes or larger.
105
	 * @return bool Is the PHP environment in Big Endian Motorola Byte format.
106
	 */
107
	public static function isSystemBigEndian(): bool
108
	{
109
		static $bigEndian = null;
110
		if ($bigEndian === null) {
111
			$bigEndian = unpack('S', "\x00\x01")[1] === 1;
112
		}
113
		return $bigEndian;
114
	}
115
116
	/**
117
	 * @return bool Is the PHP environment 64 bit and supports the 64 bit LongLong type.
118
	 */
119
	public static function hasLongLong(): bool
120
	{
121
		return PHP_INT_SIZE >= 8;
122
	}
123
124
	/**
125
	 * This is a CRC32 replacement multi-tool.  This acts exactly as crc32 with the
126
	 * added functionality that it accepts file paths (when $crc = true) which computes
127
	 * the CRC32 of the file.  This also accepts a $crc computed from existing data and
128
	 * continues to update the $crc with new data form $string as if $string were appended.
129
	 *
130
	 * If an array is passed in $string, [0] is the string data, filepath, or stream
131
	 * resource, element [1] is the size to read, and element [2] is the startOffset.
132
	 * If an array is passed, $crc = true still means that the $string is a FilePath.
133
	 * If the $string is a stream-resource, it reads until fgetc returns false or '',
134
	 * or size is hit.
135
	 *
136
	 * If using this on a file (with $crc = true) then $crc2 can be used for the existing
137
	 * crc32 for continued computation.
138
	 *
139
	 * A continued CRC32 can also be generated with HashContext using {@link hash_init},
140
	 * {@link hash_update}, and {@link hash_update_stream}, and {@link hash_final}.
141
	 * @param mixed $string String of Data, File Path, Stream Resource, or array.
142
	 *   An Array format is [0] => String Data, File Path, or Stream Resource, [1] is
143
	 *   the total size to read, and [2] is the startOffset.
144
	 * @param bool|int $crc The running CRC32 to continue calculating.  When true,
145
	 *   this expects $string to be a File Path rather than data.  Default 0 for normal
146
	 *   crc32 function without any prior running data.
147
	 * @param ?int $crc2 The existing CRC to update when specifying $string as a file
148
	 *   (with $crc = true).  Default null for new initial $crc for a file.
149
	 */
150
	public static function crc32(mixed $string, bool|int $crc = 0, ?int $crc2 = null): false|int
151
	{
152
		static $crc_table = [
153
			0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
154
			0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
155
			0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
156
			0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
157
			0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
158
			0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
159
			0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
160
			0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
161
			0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
162
			0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
163
			0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
164
			0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
165
			0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
166
			0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
167
			0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
168
			0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
169
			0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
170
			0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
171
			0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
172
			0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
173
			0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
174
			0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
175
			0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
176
			0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
177
			0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
178
			0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
179
			0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
180
			0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
181
			0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
182
			0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
183
			0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
184
			0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
185
		];
186
		$length = null;
187
		$startOffset = 0;
188
		$close = false;
189
		if (is_array($string)) {
190
			$startOffset = $string[2] ?? $string['offset'] ?? 0;
191
			$length = $string[1] ?? $string['length'] ?? null;
192
			$string = $string[0] ?? $string['source'] ?? null;
193
		}
194
		if ($crc === false) {
195
			$crc = $crc2 === null ? 0 : $crc2;
196
		}
197
		if (is_string($string)) {
198
			if (is_int($crc)) {
199
				if ($length !== null || $startOffset) {
200
					$string = substr($string, $startOffset, $length);
201
				}
202
				if ($crc === 0) {
203
					return crc32($string);
204
				}
205
				$crc ^= 0xFFFFFFFF;
206
				$length = strlen($string);
207
				for ($i = 0; $i < $length; $i++) {
208
					$crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_table[($crc & 0xFF) ^ ord($string[$i])];
209
				}
210
				$crc ^= 0xFFFFFFFF;
211
				return $crc;
212
			} elseif (realpath($string) || preg_match('/^[-+\.\w\d]{1,20}\:\/\//i', $string)) {
213
				if ($length === null && !$startOffset && !$crc2) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $crc2 of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
214
					$hash = hash_file('crc32b', $string, true);
215
					$value = unpack('N', $hash)[1];
216
					if(PHP_INT_SIZE === 4 && $value > self::PHP_INT32_MAX) {
217
						$value = (int) ($value - self::PHP_INT32_UMAX - 1);
218
					}
219
					return $value;
220
				}
221
				$string = fopen($string, 'rb');
222
				if (!$string) {
0 ignored issues
show
introduced by
$string is of type resource, thus it always evaluated to false.
Loading history...
223
					return false;
224
				}
225
				$close = true;
226
			}
227
		}
228
		if (is_resource($string) && get_resource_type($string) === 'stream') {
229
			if ($crc === true) {
230
				$crc = $crc2 === null ? 0 : $crc2;
231
			}
232
			if ($startOffset) {
233
				$meta = stream_get_meta_data($string);
234
				if ($meta['seekable']) {
235
					fseek($string, $startOffset);
236
				} else {
237
					fread($string, $startOffset);
238
				}
239
			}
240
			$crc ^= 0xFFFFFFFF;
241
			while($length === null || $length > 0) {
242
				$d = fgetc($string);
243
				if ($d === false || strlen($d) === 0) {
244
					break;
245
				}
246
				$crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_table[($crc & 0xFF) ^ ord($d)];
247
				if ($length !== null) {
248
					$length--;
249
				}
250
			}
251
			$crc ^= 0xFFFFFFFF;
252
			if ($close) {
0 ignored issues
show
introduced by
The condition $close is always false.
Loading history...
253
				fclose($string);
254
			}
255
			return $length === null || $length === 0 ? $crc : false;
256
		}
257
		return false;
258
	}
259
260
	/**
261
	 * This returns true with all negative floats, including -0.0.  Normally "$float < 0"
262
	 * will not include -0.0, where this function does include -0.0.
263
	 * @param float $value The float to check for being negative.
264
	 * @return bool Is a negative float.
265
	 */
266
	public static function isNegativeFloat(float $value): bool
267
	{
268
		return $value < 0 || $value === -0.0 && (ord(pack('G', $value)) & 0x80) !== 0;
269
	}
270
271
	/**
272
	 * This returns true with negative zero (-0.0).  Checking for negative zero floats
273
	 * requires this special function because PHP cannot be directly check for negative
274
	 * zero due to '-0.0 === 0.0'.
275
	 * @param float $value The float to check for being negative.
276
	 * @return bool Is a negative zero float.
277
	 */
278
	public static function isNegativeZero(float $value): bool
279
	{
280
		return $value === -0.0 && (ord(pack('G', $value)) & 0x80) !== 0;
281
	}
282
283
	/**
284
	 * Encodes a PHP float into an N-bit floating point number (in an integer) representation.
285
	 * This function can be configured with arbitrary number of Exponent Bits, Mantissa Bits,
286
	 * Exponent Bias, and IEEE Conformance (for subnormal numbers, INF, -INF, and NAN).
287
	 * The total number of floating point bits to be parsed is "$exponentBits + $mantissaBits + 1".
288
	 *
289
	 * With default parameter values, this functions as floatToFp16.
290
	 * @param float $value The PHP float to encode.
291
	 * @param int $exponentBits The number of bits used for the exponent, default: null for 5.
292
	 * @param int $mantissaBits The number of bits used for the mantissa, default: null for 10.
293
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
294
	 *    half the maximum exponent value.  Default: null.
295
	 * @param bool $IEEEConformance Whether to follow the IEEE 754 standard for special values
296
	 *    (NaN, INF, -INF, and subnormal). Default true
297
	 * @throws TInvalidDataValueException on bad floating point configuration values.
298
	 * @return int The a short form float representation of the float $value.
299
	 */
300
	public static function floatToFpXX(float $value, ?int $exponentBits = null, ?int $mantissaBits = null, ?int $exponentBias = null, bool $IEEEConformance = true): int
301
	{
302
		$exponentBits = ($exponentBits === null) ? 5 : $exponentBits;
303
		$mantissaBits = ($mantissaBits === null) ? 10 : $mantissaBits;
304
		$exponentMaxValue = ~(-1 << $exponentBits);
305
		$exponentBias = ($exponentBias === null) ? $exponentMaxValue >> 1 : $exponentBias;
306
		if ($exponentBits <= 0 || $mantissaBits <= 0 || ($exponentBits + $mantissaBits + 1) > PHP_INT_SIZE * 8 || $exponentBias < 0 || $exponentBias > $exponentMaxValue) {
307
			throw new TInvalidDataValueException('bithelper_bad_fp_format', $exponentBits, $mantissaBits, $exponentBias, PHP_INT_SIZE * 8);
308
		}
309
		$sign = self::isNegativeFloat($value) ? 1 : 0;
310
		$value = abs($value);
311
		$exponent = 0;
312
		$mantissa = 0;
313
314
		if ($IEEEConformance && is_nan($value)) {
315
			$exponent = $exponentMaxValue;
316
			$mantissa = 1 << ($mantissaBits - 1);
317
		} elseif ($IEEEConformance && (is_infinite($value) || $value >= pow(2, ($exponentMaxValue - 1) - $exponentBias) * (1 << $mantissaBits))) {
318
			$exponent = $exponentMaxValue;
319
		} elseif ($value == 0) {
320
			$mantissa = 0;
321
		} else {
322
			$exponent = floor(log($value, 2)) + $exponentBias;
323
			if ($exponent <= 0) {
324
				$mantissa = round($value / pow(2, 1 - $exponentBias - $mantissaBits));
325
				$exponent = 0;
326
			} elseif ($exponent >= $exponentMaxValue) {
327
				$exponent = $exponentMaxValue;
328
				$mantissa = 0;
329
			} else {
330
				$totalMantissaValues = (1 << $mantissaBits);
331
				$mantissa = round(($value / pow(2, $exponent - $exponentBias) - 1.0) * $totalMantissaValues);
332
				if ($mantissa === $totalMantissaValues) {
333
					$exponent++;
334
					$mantissa = 0;
335
				}
336
			}
337
		}
338
		$fpXX = ((($sign << $exponentBits) | $exponent) << $mantissaBits) | $mantissa;
339
		return $fpXX;
340
	}
341
342
	/**
343
	 * This encodes a PHP float into a Fp16 (1 bit sign, 5 bits exponent, 10 bits mantissa) float.
344
	 * @param float $value The float to encode.
345
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
346
	 *    half the maximum exponent value.  Default: null.
347
	 * @return int The encoded 2 byte Fp16 float.
348
	 */
349
	public static function floatToFp16(float $value, ?int $exponentBias = null): int
350
	{
351
		return self::floatToFpXX($value, 5, 10, $exponentBias);
352
	}
353
354
	/**
355
	 * This encodes a PHP float into a Bf16 (1 bit sign, 8 bits exponent, 7 bits mantissa)
356
	 * float.  This preserves the range of typical 4 byte floats but drops 2 bytes of
357
	 * precision from 23 bits to 7 bits.
358
	 * @param float $value The float to encode.
359
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
360
	 *    half the maximum exponent value.  Default: null.
361
	 * @return int The encoded 2 byte Bf16 float.
362
	 */
363
	public static function floatToBf16(float $value, ?int $exponentBias = null): int
364
	{
365
		return self::floatToFpXX($value, 8, 7, $exponentBias);
366
	}
367
368
	/**
369
	 * This encodes a PHP float into an FP8 (1 bit sign, 5 bits exponent, 2 bits mantissa) float.
370
	 * The FP8 E5M2 format is for lower precision and higher range.
371
	 * @param float $value The float to encode.
372
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
373
	 *    half the maximum exponent value.  Default: null.
374
	 * @return int The encoded 1 byte FP8-E5M2 float.
375
	 */
376
	public static function floatToFp8Range(float $value, ?int $exponentBias = null): int
377
	{
378
		return self::floatToFpXX($value, 5, 2, $exponentBias);
379
	}
380
381
	/**
382
	 * This encodes a PHP float into an FP8 (1 bit sign, 4 bits exponent, 3 bits mantissa) float.
383
	 * The FP8 E4M3 format is for higher precision and lower range.
384
	 * @param float $value The float to encode.
385
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
386
	 *    half the maximum exponent value.  Default: null.
387
	 * @return int The encoded 1 byte FP8-E4M3 float.
388
	 */
389
	public static function floatToFp8Precision(float $value, ?int $exponentBias = null): int
390
	{
391
		return self::floatToFpXX($value, 4, 3, $exponentBias);
392
	}
393
394
	/**
395
	 * Decodes an N-bit floating point encoded as an integer to a PHP floating-point number.
396
	 * This function can be configured with arbitrary number of Exponent Bits, Mantissa Bits,
397
	 * Exponent Bias, and IEEE Conformance (for subnormal numbers, INF, -INF, and NAN).
398
	 * The total number of floating point bits to be parsed is "$exponentBits + $mantissaBits + 1".
399
	 *
400
	 * With default parameter values, this functions as fp16ToFloat.
401
	 * @param int $fpXX The encoded N-bit floating point number.
402
	 * @param int $exponentBits The number of bits used for the exponent, default: null for 5.
403
	 * @param int $mantissaBits The number of bits used for the mantissa, default: null for 10.
404
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
405
	 *    half the maximum exponent value.  Default: null.
406
	 * @param bool $IEEEConformance Whether to follow the IEEE 754 standard for special values
407
	 *    (NaN, INF, -INF, and subnormal). Default true
408
	 * @throws TInvalidDataValueException on bad floating point configuration values.
409
	 * @return float The PHP float of the encoded $fpXX float.
410
	 */
411
	public static function fpXXToFloat(int $fpXX, ?int $exponentBits = null, ?int $mantissaBits = null, ?int $exponentBias = null, bool $IEEEConformance = true): float
412
	{
413
		$exponentBits = ($exponentBits === null) ? 5 : $exponentBits;
414
		$mantissaBits = ($mantissaBits === null) ? 10 : $mantissaBits;
415
		$exponentMaxValue = ~(-1 << $exponentBits);
416
		if ($exponentBits <= 0 || $mantissaBits <= 0 || ($exponentBits + $mantissaBits + 1) > PHP_INT_SIZE * 8 ||
417
			($exponentBias !== null && ($exponentBias < 0 || $exponentBias > $exponentMaxValue))) {
418
			throw new TInvalidDataValueException('bithelper_bad_fp_format', $exponentBits, $mantissaBits, $exponentBias, PHP_INT_SIZE * 8);
419
		}
420
		$exponentBias = ($exponentBias === null) ? $exponentMaxValue >> 1 : $exponentBias;
421
		$sign = ($fpXX >> ($exponentBits + $mantissaBits)) & 0x1;
422
		$exponent = ($fpXX >> $mantissaBits) & $exponentMaxValue;
423
		$mantissa = $fpXX & ~(-1 << $mantissaBits);
424
		if ($IEEEConformance && $exponent == 0) { // subnormal numbers.
425
			$value = $mantissa * pow(2, 1 - $exponentBias - $mantissaBits);
426
		} elseif ($IEEEConformance && $exponent == $exponentMaxValue) {
427
			$value = ($mantissa == 0) ? INF : NAN;
428
		} else {
429
			$value = pow(2, $exponent - $exponentBias) * (1.0 + ($mantissa / (1 << $mantissaBits)));
430
		}
431
		if ($sign) {
432
			$value = -$value;
433
		}
434
		return $value;
435
	}
436
437
	/**
438
	 * This decodes a Fp16 (5 bits exponent, 10 bits mantissa) encoded float into a PHP Float.
439
	 * @param int $fp16 the Fp16 encoded float.
440
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
441
	 *    half the maximum exponent value.  Default: null.
442
	 * @return float The Fp16 float decoded as a PHP float.
443
	 */
444
	public static function fp16ToFloat(int $fp16, ?int $exponentBias = null): float
445
	{
446
		return self::fpXXToFloat($fp16, 5, 10, $exponentBias);
447
	}
448
449
	/**
450
	 * This decodes a Bf16 (8 bits exponent, 7 bits mantissa) encoded float into a PHP
451
	 * Float.
452
	 * @param int $bf16 the BF16 encoded float.
453
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
454
	 *    half the maximum exponent value.  Default: null.
455
	 * @return float The Bf16 float decoded as a PHP float.
456
	 */
457
	public static function bf16ToFloat(int $bf16, ?int $exponentBias = null): float
458
	{
459
		return self::fpXXToFloat($bf16, 8, 7, $exponentBias);
460
	}
461
462
	/**
463
	 * This decodes a FP8 (5 bits exponent, 2 bits mantissa) encoded float into a PHP Float.
464
	 * @param int $fp8 the FP8-E5M2 encoded float.
465
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
466
	 *    half the maximum exponent value.  Default: null.
467
	 * @return float The FP8-E5M2 float decoded as a PHP float.
468
	 */
469
	public static function fp8RangeToFloat(int $fp8, ?int $exponentBias = null): float
470
	{
471
		return self::fpXXToFloat($fp8, 5, 2, $exponentBias);
472
	}
473
474
	/**
475
	 * This decodes a FP8 (4 bits exponent, 3 bits mantissa) encoded float into a PHP Float.
476
	 * @param int $fp8 the FP8-E4M3 encoded float.
477
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
478
	 *    half the maximum exponent value.  Default: null.
479
	 * @return float The FP8-E4M3 float decoded as a PHP float.
480
	 */
481
	public static function fp8PrecisionToFloat(int $fp8, ?int $exponentBias = null): float
482
	{
483
		return self::fpXXToFloat($fp8, 4, 3, $exponentBias);
484
	}
485
486
	/**
487
	 * This calculates the number of bits required to represent a given number.
488
	 * eg. If there are 256 colors, then the maximum representable number in 8 bits
489
	 * is 255.  A $value of 255 returns 8 bits, and 256 returns 9 bits, to represent
490
	 * the number.
491
	 * @param int $value The number to calculate the bits required to represent it.
492
	 * @return int The number of bits required to represent $n
493
	 */
494
	public static function bitCount(int $value): int
495
	{
496
		if ($value === 0) {
497
			return 0;
498
		} elseif ($value < 0) {	// Negative numbers need one more bit.
499
			$value = (-$value) << 1;
500
		}
501
		if ($value < 0) {
502
			return PHP_INT_SIZE * 8;
503
		}
504
		return (int) ceil(log($value + 1, 2));
505
	}
506
507
	/**
508
	 * This method shifts color bits.  When removing bits, they are simply dropped.
509
	 * When adding bits, it replicates the existing bits for new bits to create the
510
	 * most accurate higher bit representation of the color.
511
	 * @param int $value The color value to expand or contract bits.
512
	 * @param int $inBits The number of bits of the input value.
513
	 * @param int $outBits The number of bits of the output value.
514
	 * @return int The $value shifted to $outBits in size.
515
	 * @throw TInvalidDataValueException when the $inBits or $outBits are less than
516
	 *   1 or greater than the Max Int Size for this PHP implementation.
517
	 */
518
	public static function colorBitShift(int $value, int $inBits, int $outBits): int
519
	{
520
		if ($inBits < 1 || $inBits > PHP_INT_SIZE * 8) {
521
			throw new TInvalidDataValueException("bithelper_invalid_color_in", $inBits);
522
		}
523
		if ($outBits < 1 || $outBits > PHP_INT_SIZE * 8) {
524
			throw new TInvalidDataValueException("bithelper_invalid_color_out", $outBits);
525
		}
526
		$dif = $outBits - $inBits;
527
		if ($dif > 0) {
528
			$return = $value;
529
			do {
530
				$dd = min($inBits, $dif);
531
				$return = ($return << $dd) | ($value >> ($inBits - $dd));
532
				$dif -= $dd;
533
			} while ($dif > 0);
534
			return $return;
535
		} elseif ($dif < 0) {
536
			$dif = -$dif;
537
			return ($value >> $dif) & (PHP_INT_MAX >> ($dif - 1));
538
		}
539
		return $value;
540
	}
541
542
	/**
543
	 * This does a right bit shift but the signed bit is not replicated in the high
544
	 * bit (with a bit-and).
545
	 * In normal PHP right bit shift, the signed bit is what make up any new bit in
546
	 * the shift.
547
	 * @param int $value The integer to bit shift.
548
	 * @param int $bits How much to shift the bits right.  Positive is right shift,
549
	 *   Negative is left shift.
550
	 * @return int The shifted integer without the high bit repeating.
551
	 */
552
	public static function unsignedShift(int $value, int $bits): int
553
	{
554
		if ($bits > 0) {
555
			return ($value >> $bits) & (PHP_INT_MAX >> ($bits - 1));
556
		} elseif ($bits < 0) {
557
			return $value << -$bits;
558
		} else {
559
			return $value;
560
		}
561
	}
562
563
	/**
564
	 * This mirrors $nbit bits from $value. For example, 0b100 becomes 0b001 @ $nbit = 3
565
	 * and 0x0100 become 0x0010 @ $nbit = 4.
566
	 * @param int $value The bits to reverse.
567
	 * @param int $nbit The number of bits to reverse.
568
	 * @throws TInvalidDataValueException when $nbits is over the maximum size of a PHP int.
569
	 * @return int reversed bits of $value.
570
	 */
571
	public static function mirrorBits(int $value, int $nbit): int
572
	{
573
		if ($nbit > PHP_INT_SIZE * 8) {
574
			throw new TInvalidDataValueException('bithelper_bad_mirror_bits', $nbit, PHP_INT_SIZE * 8);
575
		}
576
		for ($i = 0, $result = 0; $i < $nbit; $i++) {
577
			$result <<= 1;
578
			$result |= $value & 1;
579
			$value >>= 1;
580
		}
581
		return $result;
582
	}
583
584
	/**
585
	 * This quickly mirrors the 8 bits in each byte of $n.
586
	 * @param int $n The integer to mirror the bits of each byte.
587
	 * @return int reversed 8 bits of $n.
588
	 */
589
	public static function mirrorByte(int $n): int
590
	{
591
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
592
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
593
		return ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
594
	}
595
596
	/**
597
	 * This quickly mirrors the 16 bits in each [2 byte] short of $n.
598
	 * @param int $n The integer to mirror the bits of each short.
599
	 * @return int reversed 16 bits of $n.
600
	 */
601
	public static function mirrorShort(int $n): int
602
	{
603
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
604
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
605
		$n = ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
606
		return ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
607
608
	}
609
610
	/**
611
	 * This quickly mirrors the 32 bits in each [4 byte] long of $n.
612
	 * @param int $n The integer to mirror the bits of each long.
613
	 * @return int reversed 32 bits of $n.
614
	 */
615
	public static function mirrorLong(int $n): int
616
	{
617
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
618
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
619
		$n = ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
620
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
621
		return ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
622
	}
623
624
	/**
625
	 * This quickly mirrors the 64 bits of $n.  This only works with 64 bit PHP systems.
626
	 * For speed, there is no check to validate that the system is 64 bit PHP.  You
627
	 * must do the validation if/when needed with method {@link hasLongLong}.
628
	 * @param int $n The 8 byte integer to mirror the bits of.
629
	 * @return int reversed 64 bits of $n.
630
	 */
631
	public static function mirrorLongLong(int $n): int
632
	{
633
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
634
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
635
		$n = ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
636
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
637
		$n = ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
638
		return ((($n & self::NLevel6) >> 32) & self::Mask6) | (($n & self::Level6) << 32);
639
	}
640
641
	/**
642
	 * This quickly flips the endian in each [2 byte] short of $n.
643
	 * @param int $n The 2 byte short to reverse the endian.
644
	 * @return int reversed endian of $n.
645
	 */
646
	public static function flipEndianShort(int $n): int
647
	{
648
		return ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
649
	}
650
651
	/**
652
	 * This quickly flips the endian in each [4 byte] long of $n.
653
	 * @param int $n The 4 byte long to reverse the endian.
654
	 * @return int The reversed endian of $n.
655
	 */
656
	public static function flipEndianLong(int $n): int
657
	{
658
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
659
		return ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
660
	}
661
662
	/**
663
	 * This quickly fligs the  endian of an 8 byte integer.  This only works with 64
664
	 * bit PHP systems. 32 bit systems will treat the bit field as floats and invariably
665
	 * fail.
666
	 *
667
	 * For speed, there is no check to validate that the system is 64 bit PHP.  You
668
	 * must do the validation if/when needed with method {@link hasLongLong}.
669
	 * @param int $n The 8 byte long long to reverse the endian.
670
	 * @return int reversed 8 bytes endian of $n.
671
	 */
672
	public static function flipEndianLongLong(int $n): int
673
	{
674
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
675
		$n = ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
676
		return ((($n & self::NLevel6) >> 32) & self::Mask6) | (($n & self::Level6) << 32);
677
	}
678
}
679