TBitHelper   F
last analyzed

Complexity

Total Complexity 108

Size/Duplication

Total Lines 612
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 242
dl 0
loc 612
rs 2
c 0
b 0
f 0
wmc 108

26 Methods

Rating   Name   Duplication   Size   Complexity  
A hasLongLong() 0 3 1
A fp8RangeToFloat() 0 3 1
D floatToFpXX() 0 40 19
B colorBitShift() 0 22 8
A unsignedShift() 0 8 3
A floatToFp8Precision() 0 3 1
A mirrorShort() 0 6 1
F crc32() 0 108 32
A mirrorByte() 0 5 1
A mirrorBits() 0 11 3
A mirrorLongLong() 0 8 1
A mirrorLong() 0 7 1
A flipEndianLong() 0 4 1
A bf16ToFloat() 0 3 1
A isNegativeZero() 0 3 2
A bitCount() 0 11 4
A isSystemBigEndian() 0 7 2
A fp8PrecisionToFloat() 0 3 1
A isNegativeFloat() 0 3 3
C fpXXToFloat() 0 24 16
A flipEndianLongLong() 0 5 1
A floatToFp16() 0 3 1
A flipEndianShort() 0 3 1
A floatToFp8Range() 0 3 1
A floatToBf16() 0 3 1
A fp16ToFloat() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like TBitHelper often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TBitHelper, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * TBitHelper class file
5
 *
6
 * @author Brad Anderson <[email protected]>
7
 * @link https://github.com/pradosoft/prado
8
 * @license https://github.com/pradosoft/prado/blob/master/LICENSE
9
 */
10
11
namespace Prado\Util\Helpers;
12
13
use Prado\Exceptions\TInvalidDataValueException;
14
15
/**
16
 * TBitHelper class.
17
 *
18
 * This class contains static functions for bit-wise and byte operations, like color
19
 * bit shifting, unsigned right bit shift, mirroring the order of bits, flipping
20
 * endian, and formatting floats into and from smaller float representations like
21
 * half floats (Fp16, Bf16) and mini floats (Fp8).  It also can check for negative
22
 * floats including negative zero.
23
 *
24
 * Shifting bits for color accuracy requires repeating the bits rather than
25
 * just adding extra 0/1 bits.  {@see colorBitShift} properly adds and removes bits
26
 * to an integer color value by replicating the bits for new bits.
27
 *
28
 * There are specific floating point conversion methods for converting float to:
29
 *   - Fp16 with {@see floatToFp16} and back with {@see fp16ToFloat}.
30
 *   - Bf16 with {@see floatToBf16} and back with {@see bf16ToFloat}.
31
 *   - Fp8-e5m2 with {@see floatToFp8Range} and back with {@see fp8RangeToFloat}.
32
 *   - Fp8-e4m3 with {@see floatToFp8Precision} and back with {@see fp8PrecisionToFloat}.
33
 * These functions use the general conversion functions {@see floatToFpXX} and
34
 * {@see fpXXToFloat} where the number of bits for the exponent and mantissa are
35
 * parameters. For example, 24 bit floats or 14 bit floats can be created.
36
 *
37
 * {@see mirrorBits} can mirror arbitrary runs of bits in an integer.  There is
38
 * quick mirroring for specific exponents of two: {@see mirrorByte} for 8 bits,
39
 * {@see mirrorShort} for 16 bits, {@see mirrorLong} for 32 bits, and, on 64 bit
40
 * instances of PHP, {@see mirrorLongLong} for 64 bits.
41
 *
42
 * There are endian byte reversal functions: {@see flipEndianShort}, {@see flipEndianLong},
43
 * and, on 64 bit instances of PHP, {@see flipEndianLongLong}.
44
 *
45
 * {@see bitCount} calculates the number of bits required to represent a specific
46
 * number. 255 return 8 bits, 256 returns 9 bits.
47
 *
48
 * {@see isNegativeFloat} is used to determine if a float has the negative bit
49
 * set.  It will return true on any negative float number, including negative zero.
50
 * {@see isNegativeZero} can check if a float is a negative zero.  PHP cannot normally
51
 * check for negative zero float and requires these special functions to so.
52
 *
53
 * The Levels and Masks are for O(1) time bit reversals of 8, 16, 32, and 64 bit integers.
54
 * The TBitHelper class automatically adjusts itself for 32 or 64 bit PHP environments.
55
 *
56
 * When quickly mirroring bits or switching endian, the high bits are also converted
57
 * like the low bits.  E.g. When mirroring a Byte, all bytes in the integer are
58
 * individually mirrored in place. When converting a Short, each short in the integer
59
 * will be converted in place. In the instance of a Long, for 64 bit systems will
60
 * convert both Longs -in place- in its LongLong (64 bit) unit integer type.
61
 * Converting LongLong is only supported in 64 bit PHP environments.
62
 *
63
 * @author Brad Anderson <[email protected]>
64
 * @since 4.3.0
65
 */
66
class TBitHelper
67
{
68
	// Defined constants for 32 bit computation
69
	public const PHP_INT32_MIN = -2147483648;	// 0x80000000
70
	public const PHP_INT32_MAX = 2147483647;	// 0x7FFFFFFF
71
	// on 32 bit systems the PHP_INT64_UMAX is a float and not a integer.
72
	public const PHP_INT32_UMAX = 4294967295;	// 0xFFFFFFFF (unsigned)
73
	public const PHP_INT32_MASK = (PHP_INT_SIZE > 4) ? self::PHP_INT32_UMAX : -1;
74
75
	// Defined constants for 64 bit computation
76
	//   on 32 bit systems these values are only approximate floats and not integers.
77
	public const PHP_INT64_MIN = -9223372036854775808;	// 0x80000000_00000000
78
	public const PHP_INT64_MAX = 9223372036854775807;	// 0x7FFFFFFF_FFFFFFFF
79
	//PHP_INT64_UMAX is a float that only approximates the maximum, unless using 16 byte int
80
	public const PHP_INT64_UMAX = 18446744073709551615;	// 0xFFFFFFFF_FFFFFFFF (unsigned)
81
	public const PHP_INT64_MASK = -1; // Assuming 64 bit is validated.
82
83
	public const Level1 = (PHP_INT_SIZE >= 8) ? 0x5555555555555555 : 0x55555555;
84
	public const NLevel1 = ~self::Level1;
85
	public const Mask1 = (PHP_INT_SIZE >= 8) ? 0x7FFFFFFFFFFFFFFF : 0x7FFFFFFF;
86
	public const Level2 = (PHP_INT_SIZE >= 8) ? 0x3333333333333333 : 0x33333333;
87
	public const NLevel2 = ~self::Level2;
88
	public const Mask2 = self::Mask1 >> 1;
89
	public const Level3 = (PHP_INT_SIZE >= 8) ? 0x0F0F0F0F0F0F0F0F : 0x0F0F0F0F;
90
	public const NLevel3 = ~self::Level3;
91
	public const Mask3 = self::Mask1 >> 3;
92
	public const Level4 = (PHP_INT_SIZE >= 8) ? 0x00FF00FF00FF00FF : 0x00FF00FF;
93
	public const NLevel4 = ~self::Level4;
94
	public const Mask4 = self::Mask1 >> 7;
95
	public const Level5 = (PHP_INT_SIZE >= 8) ? 0x0000FFFF0000FFFF : 0x0000FFFF;
96
	public const NLevel5 = ~self::Level5;
97
	public const Mask5 = self::Mask1 >> 15;
98
	public const Level6 = (PHP_INT_SIZE >= 8) ? 0x00000000FFFFFFFF : -1;
99
	public const NLevel6 = ~self::Level6;
100
	public const Mask6 = self::Mask1 >> 31;
101
102
	/**
103
	 * Motorola is Big Endian with the Most Significant Byte first whereas Intel uses
104
	 * Little Endian with the Least Significant Byte first.  This mainly only affects
105
	 * the binary reading and writing of data types that are 2 bytes or larger.
106
	 * @return bool Is the PHP environment in Big Endian Motorola Byte format.
107
	 */
108
	public static function isSystemBigEndian(): bool
109
	{
110
		static $bigEndian = null;
111
		if ($bigEndian === null) {
112
			$bigEndian = unpack('S', "\x00\x01")[1] === 1;
113
		}
114
		return $bigEndian;
115
	}
116
117
	/**
118
	 * @return bool Is the PHP environment 64 bit and supports the 64 bit LongLong type.
119
	 */
120
	public static function hasLongLong(): bool
121
	{
122
		return PHP_INT_SIZE >= 8;
123
	}
124
125
	/**
126
	 * This is a CRC32 replacement multi-tool.  This acts exactly as crc32 with the
127
	 * added functionality that it accepts file paths (when $crc = true) which computes
128
	 * the CRC32 of the file.  This also accepts a $crc computed from existing data and
129
	 * continues to update the $crc with new data form $string as if $string were appended.
130
	 *
131
	 * If an array is passed in $string, [0] is the string data, filepath, or stream
132
	 * resource, element [1] is the size to read, and element [2] is the startOffset.
133
	 * If an array is passed, $crc = true still means that the $string is a FilePath.
134
	 * If the $string is a stream-resource, it reads until fgetc returns false or '',
135
	 * or size is hit.
136
	 *
137
	 * If using this on a file (with $crc = true) then $crc2 can be used for the existing
138
	 * crc32 for continued computation.
139
	 *
140
	 * A continued CRC32 can also be generated with HashContext using {@link hash_init},
141
	 * {@link hash_update}, and {@link hash_update_stream}, and {@link hash_final}.
142
	 * @param mixed $string String of Data, File Path, Stream Resource, or array.
143
	 *   An Array format is [0] => String Data, File Path, or Stream Resource, [1] is
144
	 *   the total size to read, and [2] is the startOffset.
145
	 * @param bool|int $crc The running CRC32 to continue calculating.  When true,
146
	 *   this expects $string to be a File Path rather than data.  Default 0 for normal
147
	 *   crc32 function without any prior running data.
148
	 * @param ?int $crc2 The existing CRC to update when specifying $string as a file
149
	 *   (with $crc = true).  Default null for new initial $crc for a file.
150
	 */
151
	public static function crc32(mixed $string, bool|int $crc = 0, ?int $crc2 = null): false|int
152
	{
153
		static $crc_table = [
154
			0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
155
			0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
156
			0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
157
			0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
158
			0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
159
			0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
160
			0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
161
			0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
162
			0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
163
			0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
164
			0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
165
			0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
166
			0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
167
			0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
168
			0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
169
			0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
170
			0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
171
			0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
172
			0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
173
			0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
174
			0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
175
			0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
176
			0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
177
			0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
178
			0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
179
			0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
180
			0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
181
			0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
182
			0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
183
			0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
184
			0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
185
			0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D,
186
		];
187
		$length = null;
188
		$startOffset = 0;
189
		$close = false;
190
		if (is_array($string)) {
191
			$startOffset = $string[2] ?? $string['offset'] ?? 0;
192
			$length = $string[1] ?? $string['length'] ?? null;
193
			$string = $string[0] ?? $string['source'] ?? null;
194
		}
195
		if ($crc === false) {
196
			$crc = $crc2 === null ? 0 : $crc2;
197
		}
198
		if (is_string($string)) {
199
			if (is_int($crc)) {
200
				if ($length !== null || $startOffset) {
201
					$string = substr($string, $startOffset, $length);
202
				}
203
				if ($crc === 0) {
204
					return crc32($string);
205
				}
206
				$crc ^= 0xFFFFFFFF;
207
				$length = strlen($string);
208
				for ($i = 0; $i < $length; $i++) {
209
					$crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_table[($crc & 0xFF) ^ ord($string[$i])];
210
				}
211
				$crc ^= 0xFFFFFFFF;
212
				return $crc;
213
			} elseif (realpath($string) || preg_match('/^[-+\.\w\d]{1,20}\:\/\//i', $string)) {
214
				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...
215
					$hash = hash_file('crc32b', $string, true);
216
					$value = unpack('N', $hash)[1];
217
					if (PHP_INT_SIZE === 4 && $value > self::PHP_INT32_MAX) {
218
						$value = (int) ($value - self::PHP_INT32_UMAX - 1);
219
					}
220
					return $value;
221
				}
222
				$string = fopen($string, 'rb');
223
				if (!$string) {
0 ignored issues
show
introduced by
$string is of type resource, thus it always evaluated to false.
Loading history...
224
					return false;
225
				}
226
				$close = true;
227
			}
228
		}
229
		if (is_resource($string) && get_resource_type($string) === 'stream') {
230
			if ($crc === true) {
231
				$crc = $crc2 === null ? 0 : $crc2;
232
			}
233
			if ($startOffset) {
234
				$meta = stream_get_meta_data($string);
235
				if ($meta['seekable']) {
236
					fseek($string, $startOffset);
237
				} else {
238
					fread($string, $startOffset);
239
				}
240
			}
241
			$crc ^= 0xFFFFFFFF;
242
			while ($length === null || $length > 0) {
243
				$d = fgetc($string);
244
				if ($d === false || strlen($d) === 0) {
245
					break;
246
				}
247
				$crc = (($crc >> 8) & 0x00FFFFFF) ^ $crc_table[($crc & 0xFF) ^ ord($d)];
248
				if ($length !== null) {
249
					$length--;
250
				}
251
			}
252
			$crc ^= 0xFFFFFFFF;
253
			if ($close) {
0 ignored issues
show
introduced by
The condition $close is always false.
Loading history...
254
				fclose($string);
255
			}
256
			return $length === null || $length === 0 ? $crc : false;
257
		}
258
		return false;
259
	}
260
261
	/**
262
	 * This returns true with all negative floats, including -0.0.  Normally "$float < 0"
263
	 * will not include -0.0, where this function does include -0.0.
264
	 * @param float $value The float to check for being negative.
265
	 * @return bool Is a negative float.
266
	 */
267
	public static function isNegativeFloat(float $value): bool
268
	{
269
		return $value < 0 || $value === -0.0 && (ord(pack('G', $value)) & 0x80) !== 0;
270
	}
271
272
	/**
273
	 * This returns true with negative zero (-0.0).  Checking for negative zero floats
274
	 * requires this special function because PHP cannot be directly check for negative
275
	 * zero due to '-0.0 === 0.0'.
276
	 * @param float $value The float to check for being negative.
277
	 * @return bool Is a negative zero float.
278
	 */
279
	public static function isNegativeZero(float $value): bool
280
	{
281
		return $value === -0.0 && (ord(pack('G', $value)) & 0x80) !== 0;
282
	}
283
284
	/**
285
	 * Encodes a PHP float into an N-bit floating point number (in an integer) representation.
286
	 * This function can be configured with arbitrary number of Exponent Bits, Mantissa Bits,
287
	 * Exponent Bias, and IEEE Conformance (for subnormal numbers, INF, -INF, and NAN).
288
	 * The total number of floating point bits to be parsed is "$exponentBits + $mantissaBits + 1".
289
	 *
290
	 * With default parameter values, this functions as floatToFp16.
291
	 * @param float $value The PHP float to encode.
292
	 * @param int $exponentBits The number of bits used for the exponent, default: null for 5.
293
	 * @param int $mantissaBits The number of bits used for the mantissa, default: null for 10.
294
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
295
	 *    half the maximum exponent value.  Default: null.
296
	 * @param bool $IEEEConformance Whether to follow the IEEE 754 standard for special values
297
	 *    (NaN, INF, -INF, and subnormal). Default true
298
	 * @throws TInvalidDataValueException on bad floating point configuration values.
299
	 * @return int The a short form float representation of the float $value.
300
	 */
301
	public static function floatToFpXX(float $value, ?int $exponentBits = null, ?int $mantissaBits = null, ?int $exponentBias = null, bool $IEEEConformance = true): int
302
	{
303
		$exponentBits = ($exponentBits === null) ? 5 : $exponentBits;
304
		$mantissaBits = ($mantissaBits === null) ? 10 : $mantissaBits;
305
		$exponentMaxValue = ~(-1 << $exponentBits);
306
		$exponentBias = ($exponentBias === null) ? $exponentMaxValue >> 1 : $exponentBias;
307
		if ($exponentBits <= 0 || $mantissaBits <= 0 || ($exponentBits + $mantissaBits + 1) > PHP_INT_SIZE * 8 || $exponentBias < 0 || $exponentBias > $exponentMaxValue) {
308
			throw new TInvalidDataValueException('bithelper_bad_fp_format', $exponentBits, $mantissaBits, $exponentBias, PHP_INT_SIZE * 8);
309
		}
310
		$sign = self::isNegativeFloat($value) ? 1 : 0;
311
		$value = abs($value);
312
		$exponent = 0;
313
		$mantissa = 0;
314
315
		if ($IEEEConformance && is_nan($value)) {
316
			$exponent = $exponentMaxValue;
317
			$mantissa = 1 << ($mantissaBits - 1);
318
		} elseif ($IEEEConformance && (is_infinite($value) || $value >= pow(2, ($exponentMaxValue - 1) - $exponentBias) * (1 << $mantissaBits))) {
319
			$exponent = $exponentMaxValue;
320
		} elseif ($value == 0) {
321
			$mantissa = 0;
322
		} else {
323
			$exponent = floor(log($value, 2)) + $exponentBias;
324
			if ($exponent <= 0) {
325
				$mantissa = round($value / pow(2, 1 - $exponentBias - $mantissaBits));
326
				$exponent = 0;
327
			} elseif ($exponent >= $exponentMaxValue) {
328
				$exponent = $exponentMaxValue;
329
				$mantissa = 0;
330
			} else {
331
				$totalMantissaValues = (1 << $mantissaBits);
332
				$mantissa = round(($value / pow(2, $exponent - $exponentBias) - 1.0) * $totalMantissaValues);
333
				if ($mantissa === $totalMantissaValues) {
334
					$exponent++;
335
					$mantissa = 0;
336
				}
337
			}
338
		}
339
		$fpXX = ((($sign << $exponentBits) | $exponent) << $mantissaBits) | $mantissa;
340
		return $fpXX;
341
	}
342
343
	/**
344
	 * This encodes a PHP float into a Fp16 (1 bit sign, 5 bits exponent, 10 bits mantissa) float.
345
	 * @param float $value The float to encode.
346
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
347
	 *    half the maximum exponent value.  Default: null.
348
	 * @return int The encoded 2 byte Fp16 float.
349
	 */
350
	public static function floatToFp16(float $value, ?int $exponentBias = null): int
351
	{
352
		return self::floatToFpXX($value, 5, 10, $exponentBias);
353
	}
354
355
	/**
356
	 * This encodes a PHP float into a Bf16 (1 bit sign, 8 bits exponent, 7 bits mantissa)
357
	 * float.  This preserves the range of typical 4 byte floats but drops 2 bytes of
358
	 * precision from 23 bits to 7 bits.
359
	 * @param float $value The float to encode.
360
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
361
	 *    half the maximum exponent value.  Default: null.
362
	 * @return int The encoded 2 byte Bf16 float.
363
	 */
364
	public static function floatToBf16(float $value, ?int $exponentBias = null): int
365
	{
366
		return self::floatToFpXX($value, 8, 7, $exponentBias);
367
	}
368
369
	/**
370
	 * This encodes a PHP float into an FP8 (1 bit sign, 5 bits exponent, 2 bits mantissa) float.
371
	 * The FP8 E5M2 format is for lower precision and higher range.
372
	 * @param float $value The float to encode.
373
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
374
	 *    half the maximum exponent value.  Default: null.
375
	 * @return int The encoded 1 byte FP8-E5M2 float.
376
	 */
377
	public static function floatToFp8Range(float $value, ?int $exponentBias = null): int
378
	{
379
		return self::floatToFpXX($value, 5, 2, $exponentBias);
380
	}
381
382
	/**
383
	 * This encodes a PHP float into an FP8 (1 bit sign, 4 bits exponent, 3 bits mantissa) float.
384
	 * The FP8 E4M3 format is for higher precision and lower range.
385
	 * @param float $value The float to encode.
386
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
387
	 *    half the maximum exponent value.  Default: null.
388
	 * @return int The encoded 1 byte FP8-E4M3 float.
389
	 */
390
	public static function floatToFp8Precision(float $value, ?int $exponentBias = null): int
391
	{
392
		return self::floatToFpXX($value, 4, 3, $exponentBias);
393
	}
394
395
	/**
396
	 * Decodes an N-bit floating point encoded as an integer to a PHP floating-point number.
397
	 * This function can be configured with arbitrary number of Exponent Bits, Mantissa Bits,
398
	 * Exponent Bias, and IEEE Conformance (for subnormal numbers, INF, -INF, and NAN).
399
	 * The total number of floating point bits to be parsed is "$exponentBits + $mantissaBits + 1".
400
	 *
401
	 * With default parameter values, this functions as fp16ToFloat.
402
	 * @param int $fpXX The encoded N-bit floating point number.
403
	 * @param int $exponentBits The number of bits used for the exponent, default: null for 5.
404
	 * @param int $mantissaBits The number of bits used for the mantissa, default: null for 10.
405
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
406
	 *    half the maximum exponent value.  Default: null.
407
	 * @param bool $IEEEConformance Whether to follow the IEEE 754 standard for special values
408
	 *    (NaN, INF, -INF, and subnormal). Default true
409
	 * @throws TInvalidDataValueException on bad floating point configuration values.
410
	 * @return float The PHP float of the encoded $fpXX float.
411
	 */
412
	public static function fpXXToFloat(int $fpXX, ?int $exponentBits = null, ?int $mantissaBits = null, ?int $exponentBias = null, bool $IEEEConformance = true): float
413
	{
414
		$exponentBits = ($exponentBits === null) ? 5 : $exponentBits;
415
		$mantissaBits = ($mantissaBits === null) ? 10 : $mantissaBits;
416
		$exponentMaxValue = ~(-1 << $exponentBits);
417
		if ($exponentBits <= 0 || $mantissaBits <= 0 || ($exponentBits + $mantissaBits + 1) > PHP_INT_SIZE * 8 ||
418
			($exponentBias !== null && ($exponentBias < 0 || $exponentBias > $exponentMaxValue))) {
419
			throw new TInvalidDataValueException('bithelper_bad_fp_format', $exponentBits, $mantissaBits, $exponentBias, PHP_INT_SIZE * 8);
420
		}
421
		$exponentBias = ($exponentBias === null) ? $exponentMaxValue >> 1 : $exponentBias;
422
		$sign = ($fpXX >> ($exponentBits + $mantissaBits)) & 0x1;
423
		$exponent = ($fpXX >> $mantissaBits) & $exponentMaxValue;
424
		$mantissa = $fpXX & ~(-1 << $mantissaBits);
425
		if ($IEEEConformance && $exponent == 0) { // subnormal numbers.
426
			$value = $mantissa * pow(2, 1 - $exponentBias - $mantissaBits);
427
		} elseif ($IEEEConformance && $exponent == $exponentMaxValue) {
428
			$value = ($mantissa == 0) ? INF : NAN;
429
		} else {
430
			$value = pow(2, $exponent - $exponentBias) * (1.0 + ($mantissa / (1 << $mantissaBits)));
431
		}
432
		if ($sign) {
433
			$value = -$value;
434
		}
435
		return $value;
436
	}
437
438
	/**
439
	 * This decodes a Fp16 (5 bits exponent, 10 bits mantissa) encoded float into a PHP Float.
440
	 * @param int $fp16 the Fp16 encoded float.
441
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
442
	 *    half the maximum exponent value.  Default: null.
443
	 * @return float The Fp16 float decoded as a PHP float.
444
	 */
445
	public static function fp16ToFloat(int $fp16, ?int $exponentBias = null): float
446
	{
447
		return self::fpXXToFloat($fp16, 5, 10, $exponentBias);
448
	}
449
450
	/**
451
	 * This decodes a Bf16 (8 bits exponent, 7 bits mantissa) encoded float into a PHP
452
	 * Float.
453
	 * @param int $bf16 the BF16 encoded float.
454
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
455
	 *    half the maximum exponent value.  Default: null.
456
	 * @return float The Bf16 float decoded as a PHP float.
457
	 */
458
	public static function bf16ToFloat(int $bf16, ?int $exponentBias = null): float
459
	{
460
		return self::fpXXToFloat($bf16, 8, 7, $exponentBias);
461
	}
462
463
	/**
464
	 * This decodes a FP8 (5 bits exponent, 2 bits mantissa) encoded float into a PHP Float.
465
	 * @param int $fp8 the FP8-E5M2 encoded float.
466
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
467
	 *    half the maximum exponent value.  Default: null.
468
	 * @return float The FP8-E5M2 float decoded as a PHP float.
469
	 */
470
	public static function fp8RangeToFloat(int $fp8, ?int $exponentBias = null): float
471
	{
472
		return self::fpXXToFloat($fp8, 5, 2, $exponentBias);
473
	}
474
475
	/**
476
	 * This decodes a FP8 (4 bits exponent, 3 bits mantissa) encoded float into a PHP Float.
477
	 * @param int $fp8 the FP8-E4M3 encoded float.
478
	 * @param null|int $exponentBias The bias to apply to the exponent. If null, it defaults to
479
	 *    half the maximum exponent value.  Default: null.
480
	 * @return float The FP8-E4M3 float decoded as a PHP float.
481
	 */
482
	public static function fp8PrecisionToFloat(int $fp8, ?int $exponentBias = null): float
483
	{
484
		return self::fpXXToFloat($fp8, 4, 3, $exponentBias);
485
	}
486
487
	/**
488
	 * This calculates the number of bits required to represent a given number.
489
	 * eg. If there are 256 colors, then the maximum representable number in 8 bits
490
	 * is 255.  A $value of 255 returns 8 bits, and 256 returns 9 bits, to represent
491
	 * the number.
492
	 * @param int $value The number to calculate the bits required to represent it.
493
	 * @return int The number of bits required to represent $n
494
	 */
495
	public static function bitCount(int $value): int
496
	{
497
		if ($value === 0) {
498
			return 0;
499
		} elseif ($value < 0) {	// Negative numbers need one more bit.
500
			$value = (-$value) << 1;
501
		}
502
		if ($value < 0) {
503
			return PHP_INT_SIZE * 8;
504
		}
505
		return (int) ceil(log($value + 1, 2));
506
	}
507
508
	/**
509
	 * This method shifts color bits.  When removing bits, they are simply dropped.
510
	 * When adding bits, it replicates the existing bits for new bits to create the
511
	 * most accurate higher bit representation of the color.
512
	 * @param int $value The color value to expand or contract bits.
513
	 * @param int $inBits The number of bits of the input value.
514
	 * @param int $outBits The number of bits of the output value.
515
	 * @return int The $value shifted to $outBits in size.
516
	 * @throw TInvalidDataValueException when the $inBits or $outBits are less than
517
	 *   1 or greater than the Max Int Size for this PHP implementation.
518
	 */
519
	public static function colorBitShift(int $value, int $inBits, int $outBits): int
520
	{
521
		if ($inBits < 1 || $inBits > PHP_INT_SIZE * 8) {
522
			throw new TInvalidDataValueException("bithelper_invalid_color_in", $inBits);
523
		}
524
		if ($outBits < 1 || $outBits > PHP_INT_SIZE * 8) {
525
			throw new TInvalidDataValueException("bithelper_invalid_color_out", $outBits);
526
		}
527
		$dif = $outBits - $inBits;
528
		if ($dif > 0) {
529
			$return = $value;
530
			do {
531
				$dd = min($inBits, $dif);
532
				$return = ($return << $dd) | ($value >> ($inBits - $dd));
533
				$dif -= $dd;
534
			} while ($dif > 0);
535
			return $return;
536
		} elseif ($dif < 0) {
537
			$dif = -$dif;
538
			return ($value >> $dif) & (PHP_INT_MAX >> ($dif - 1));
539
		}
540
		return $value;
541
	}
542
543
	/**
544
	 * This does a right bit shift but the signed bit is not replicated in the high
545
	 * bit (with a bit-and).
546
	 * In normal PHP right bit shift, the signed bit is what make up any new bit in
547
	 * the shift.
548
	 * @param int $value The integer to bit shift.
549
	 * @param int $bits How much to shift the bits right.  Positive is right shift,
550
	 *   Negative is left shift.
551
	 * @return int The shifted integer without the high bit repeating.
552
	 */
553
	public static function unsignedShift(int $value, int $bits): int
554
	{
555
		if ($bits > 0) {
556
			return ($value >> $bits) & (PHP_INT_MAX >> ($bits - 1));
557
		} elseif ($bits < 0) {
558
			return $value << -$bits;
559
		} else {
560
			return $value;
561
		}
562
	}
563
564
	/**
565
	 * This mirrors $nbit bits from $value. For example, 0b100 becomes 0b001 @ $nbit = 3
566
	 * and 0x0100 become 0x0010 @ $nbit = 4.
567
	 * @param int $value The bits to reverse.
568
	 * @param int $nbit The number of bits to reverse.
569
	 * @throws TInvalidDataValueException when $nbits is over the maximum size of a PHP int.
570
	 * @return int reversed bits of $value.
571
	 */
572
	public static function mirrorBits(int $value, int $nbit): int
573
	{
574
		if ($nbit > PHP_INT_SIZE * 8) {
575
			throw new TInvalidDataValueException('bithelper_bad_mirror_bits', $nbit, PHP_INT_SIZE * 8);
576
		}
577
		for ($i = 0, $result = 0; $i < $nbit; $i++) {
578
			$result <<= 1;
579
			$result |= $value & 1;
580
			$value >>= 1;
581
		}
582
		return $result;
583
	}
584
585
	/**
586
	 * This quickly mirrors the 8 bits in each byte of $n.
587
	 * @param int $n The integer to mirror the bits of each byte.
588
	 * @return int reversed 8 bits of $n.
589
	 */
590
	public static function mirrorByte(int $n): int
591
	{
592
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
593
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
594
		return ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
595
	}
596
597
	/**
598
	 * This quickly mirrors the 16 bits in each [2 byte] short of $n.
599
	 * @param int $n The integer to mirror the bits of each short.
600
	 * @return int reversed 16 bits of $n.
601
	 */
602
	public static function mirrorShort(int $n): int
603
	{
604
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
605
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
606
		$n = ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
607
		return ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
608
609
	}
610
611
	/**
612
	 * This quickly mirrors the 32 bits in each [4 byte] long of $n.
613
	 * @param int $n The integer to mirror the bits of each long.
614
	 * @return int reversed 32 bits of $n.
615
	 */
616
	public static function mirrorLong(int $n): int
617
	{
618
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
619
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
620
		$n = ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
621
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
622
		return ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
623
	}
624
625
	/**
626
	 * This quickly mirrors the 64 bits of $n.  This only works with 64 bit PHP systems.
627
	 * For speed, there is no check to validate that the system is 64 bit PHP.  You
628
	 * must do the validation if/when needed with method {@see hasLongLong}.
629
	 * @param int $n The 8 byte integer to mirror the bits of.
630
	 * @return int reversed 64 bits of $n.
631
	 */
632
	public static function mirrorLongLong(int $n): int
633
	{
634
		$n = ((($n & self::NLevel1) >> 1) & self::Mask1) | (($n & self::Level1) << 1);
635
		$n = ((($n & self::NLevel2) >> 2) & self::Mask2) | (($n & self::Level2) << 2);
636
		$n = ((($n & self::NLevel3) >> 4) & self::Mask3) | (($n & self::Level3) << 4);
637
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
638
		$n = ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
639
		return ((($n & self::NLevel6) >> 32) & self::Mask6) | (($n & self::Level6) << 32);
640
	}
641
642
	/**
643
	 * This quickly flips the endian in each [2 byte] short of $n.
644
	 * @param int $n The 2 byte short to reverse the endian.
645
	 * @return int reversed endian of $n.
646
	 */
647
	public static function flipEndianShort(int $n): int
648
	{
649
		return ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
650
	}
651
652
	/**
653
	 * This quickly flips the endian in each [4 byte] long of $n.
654
	 * @param int $n The 4 byte long to reverse the endian.
655
	 * @return int The reversed endian of $n.
656
	 */
657
	public static function flipEndianLong(int $n): int
658
	{
659
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
660
		return ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
661
	}
662
663
	/**
664
	 * This quickly fligs the  endian of an 8 byte integer.  This only works with 64
665
	 * bit PHP systems. 32 bit systems will treat the bit field as floats and invariably
666
	 * fail.
667
	 *
668
	 * For speed, there is no check to validate that the system is 64 bit PHP.  You
669
	 * must do the validation if/when needed with method {@see hasLongLong}.
670
	 * @param int $n The 8 byte long long to reverse the endian.
671
	 * @return int reversed 8 bytes endian of $n.
672
	 */
673
	public static function flipEndianLongLong(int $n): int
674
	{
675
		$n = ((($n & self::NLevel4) >> 8) & self::Mask4) | (($n & self::Level4) << 8);
676
		$n = ((($n & self::NLevel5) >> 16) & self::Mask5) | (($n & self::Level5) << 16);
677
		return ((($n & self::NLevel6) >> 32) & self::Mask6) | (($n & self::Level6) << 32);
678
	}
679
}
680