Reading the decimal value of little endian in PHP - php

I'm reading 6 bytes in little endian from a binary file using
$data = fread($fp, 6);
unpack("V", $data);
The result is 1152664389 and in HEX it is 0x44B44345
Now this result is in little endian and has a decimal number. In Delphi, I was able to get the decimal number using this function:
var
myint:integer;
s:single;
begin
myint:= 1152664389; // same as myint:= $44B44345;
s:= PSingle(#myint)^;
and output of s is 1442.102.. This is exactly the number im looking for...
I searched for a way to do it in PHP but I just got lost.
Please help,
Thanks

Okay, I was a bit confused. I wanted to actually retrieve a little endian number from a binary file then convert it to float.
So my steps were:
1) Read bytes and unpack using $number = unpack("V", $data);
2) Convert $number to decimal using dechex.
3) Then use the following function to convert hex to float:
function hexTo32Float($strHex) {
$v = hexdec($strHex);
$x = ($v & ((1 << 23) - 1)) + (1 << 23) * ($v >> 31 | 1);
$exp = ($v >> 23 & 0xFF) - 127;
return $x * pow(2, $exp - 23);
}
Thanks again.

Related

Truncate 64Bit Integer to 32Bit and simulate value

I'm trying to write an algorithm to convert a 64Bit integer to 32Bit via truncation, and return a value accurately representing a 32Bit value. The obvious problem is that integers in PHP are only 64Bit (barring 32Bit systems).
I first tried the following:
$x = $v & 0xFFFFFFFF
So if $v = PHP_INT_MAX, $x is 4294967295, when really I want -1. I know the reason for this is that when represented as a 64Bit integer, PHP prepends the zeros to the last 32 bits, and thats why I get a positive number.
My solution so far is this:
function convert64BitTo32Bit(int $v): int
{
$v &= 0xFFFFFFFF;
if ($v & 0x80000000) {
return $v | 0xFFFFFFFF00000000;
}
return $v;
}
And I'm pretty sure its right. The problem I have with this is that it requires that if statement to inspect whether the truncated number is negative. I was really hoping for a bitwise only solution. It may not be possible, but I thought I'd ask.
EDIT:
The problem can be simplified to just part of the solution. i.e. if the first bit is 1, make all bits 1, if first bit is 0 make all bits 0.
# example assumes input is the LSBs of an 8Bit integer.
# scale for 64Bit and the same solution should work.
op(1010) = 1111
op(0101) = 0000
op(0000) = 0000
op(1000) = 1111
op(0001) = 0000
I would be able to use the LSBs to derive this value, then mask it onto the MSBs of the 64Bit integer. This is what I'm trying to figure out now, though I'm trying to avoid creating a monster equation.
There's probably a more elegant/efficient way using bitwise operations, but you can also force the conversion with pack() and unpack(). Eg: Pack as unsigned, unpack as signed.
function overflow32($in) {
return unpack('l', pack('i', $in & 0xFFFFFFFF))[1];
}
var_dump( overflow32(pow(2,33)-1) ); // int(-1)
I'm curious what you're applying this to, because I had to break a hash function with this same thing some years ago when I moved an app from a 32 bit machine to a 64 bit machine, but I can't remember what it was.
Edit: Wow for some reason I remembered Two's Complement being hard. Literally just invert and add one.
function overflow32($in) {
if( $in & 0x80000000 ) {
return ( ( ~$in & 0xFFFFFFFF) + 1 ) * -1;
} else {
return $in & 0xFFFFFFFF;
}
}
var_dump( kludge32(pow(2,33)-1) );
Edit 2: I saw that you want to extend this to arbitrary bit lengths, in which case you just need to calculate the masks instead of explicitly setting them:
function overflow_bits($in, $bits=32) {
$sign_mask = 1 << $bits-1;
$clamp_mask = ($sign_mask << 1) - 1;
var_dump(
decbin($in),
decbin($sign_mask),
decbin($clamp_mask)
);
if( $in & $sign_mask ) {
return ( ( ~$in & $clamp_mask) + 1 ) * -1;
} else {
return $in & $clamp_mask;
}
}
var_dump(
overflow_bits(pow(2, 31), 32),
overflow_bits(pow(2, 15), 16),
overflow_bits(pow(2, 7), 8)
);
I left in the debug var_dump()s for output flavor:
string(32) "10000000000000000000000000000000"
string(32) "10000000000000000000000000000000"
string(32) "11111111111111111111111111111111"
string(16) "1000000000000000"
string(16) "1000000000000000"
string(16) "1111111111111111"
string(8) "10000000"
string(8) "10000000"
string(8) "11111111"
int(-2147483648)
int(-32768)
int(-128)
I think I've cracked it:
function overflow32Bit(int $x): int
{
return ($x & 0xFFFFFFFF) | ((($x & 0xFFFFFFFF) >> 31) * ((2 ** 32) - 1) << 32);
}
var_dump(overflow32Bit(PHP_INT_MAX)); // -1 correct
var_dump(overflow32Bit(PHP_INT_MAX - 1)); // -2 correct
var_dump(overflow32Bit(PHP_INT_MIN)); // 0 correct
var_dump(overflow32Bit((2 ** 31) - 1)); // 2147483647 correct
var_dump(overflow32Bit((2 ** 31))); // -2147483647 correct
var_dump(overflow32Bit(0xFFFFFFFF)); // -1 correct
var_dump(overflow32Bit(0x7FFFFFFF)); // 2147483647 correct
The solution was actually staring me in the face. Get the value of the first bit, then multiply it by the max value of unsigned 32bit integer.
If someone can come up with a better or shorter solution, I'll also accept that.
PS. This is only for 32Bit, i also intend to use this proof for 16Bit and 8Bit.
Expanded, $x is input, $z is output.
$x = PHP_INT_MAX;
$y = (2 ** 32) - 1;
$z = ($x & $y) | ((($x & $y) >> 31) * ($y << 32));
var_dump($z);

Convert double to Pascal 6-byte (48 bits) real format

I need to do some work on data contained in legacy files. For this purpose, I need to read and write Turbo Pascal's 6-byte (48 bit) floating point numbers, from PHP. The Turbo Pascal data type is commonly known as real48 (specs).
I have the following php code to read the format:
/**
* Convert Turbo Pascal 48-bit (6 byte) real to a PHP float
* #param binary 48-bit real (in binary) to convert
* #return float number
*/
function real48ToDouble($real48) {
$byteArray = array_values( unpack('C*', $real48) );
if ($byteArray[0] == 0) {
return 0; // Zero exponent = 0
}
$exponent = $byteArray[0] - 129;
$mantissa = 0;
for ($b = 1; $b <= 4; $b++) {
$mantissa += $byteArray[$b];
$mantissa /= 256;
}
$mantissa += ($byteArray[5] & 127);
$mantissa /= 128;
$mantissa += 1;
if ($byteArray[5] & 128) { // Sign bit check
$mantissa = -$mantissa;
}
return $mantissa * pow(2, $exponent);
}
(adapted from)
Now I need to do the reverse: write the data type.
Note:
I'm aware of the answer to the question Convert C# double to Delphi Real48, but it seems awfully hacky and I would think a much cleaner solution is possible. AND my machine does not natively support 64-bits.
On a second look, the method posted in the answer to Convert C# double to Delphi Real48 cleaned up pretty nicely.
For future reference:
/**
* Convert a PHP number [Int|Float] to a Turbo Pascal 48-bit (6 byte) real byte representation
* #param float number to convert
* #return binary 48-bit real
*/
function doubleToReal48($double) {
$byteArray = array_values( unpack('C*', pack('d', $double)) ); // 64 bit double as array of integers
$real48 = array(0, 0, 0, 0, 0, 0);
// Copy the negative flag
$real48[5] |= ($byteArray[7] & 128);
// Get the exponent
$n = ($byteArray[7] & 127) << 4;
$n |= ($byteArray[6] & 240) >> 4;
if ($n == 0) { // Zero exponent = 0
return pack('c6', $real48[0], $real48[1], $real48[2], $real48[3], $real48[4], $real48[5]);
}
$real48[0] = $n - 1023 + 129;
// Copy the Mantissa
$real48[5] |= (($byteArray[6] & 15) << 3); // Get the last 4 bits
$real48[5] |= (($byteArray[5] & 224) >> 5); // Get the first 3 bits
for ($b = 4; $b >= 1; $b--) {
$real48[$b] = (($byteArray[$b+1] & 31) << 3); // Get the last 5 bits
$real48[$b] |= (($byteArray[$b] & 224) >> 5); // Get the first 3 bits
}
return pack('c6', $real48[0], $real48[1], $real48[2], $real48[3], $real48[4], $real48[5]);
}

PHP pack() format for signed 32 int - big endian

I am creating and then writing data to a file (a new 'ESRI Shape file') using PHP, fopen, fseek, pack etc. The file spec is here http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf.
The file spec states that the data written needs to be in a combination of the following:
Integer: Signed 32-bit integer (4 bytes) - Big Endian
Integer: Signed 32-bit integer (4 bytes) - Little Endian
Double: Signed 64-bit IEEE double-precision floating point number (8 bytes) - Little Endian
I cant seem to find a pack() format that allows for these formats. I don't want to use a machine dependent format as this code may be running on a variety of platforms.
Can anyone advise on what format (or combination of formats) I need to use for these 3 formats?
Many thanks,
Steve
You could check the endianness of the machine running the code and reverse the bytes manually as necessary. The code below should work, but you will only be able to convert one int or float at a time.
define('BIG_ENDIAN', pack('L', 1) === pack('N', 1));
function pack_int32s_be($n) {
if (BIG_ENDIAN) {
return pack('l', $n); // that's a lower case L
}
return strrev(pack('l', $n));
}
function pack_int32s_le($n) {
if (BIG_ENDIAN) {
return strrev(pack('l', $n));
}
return pack('l', $n); // that's a lower case L
}
function pack_double_be($n) {
if (BIG_ENDIAN) {
return pack('d', $n);
}
return strrev(pack('d', $n));
}
function pack_double_le($n) {
if (BIG_ENDIAN) {
return strrev(pack('d', $n));
}
return pack('d', $n);
}
If PHP doesn't support it, you could implement your own.
function pack_int32be($i) {
if ($i < -2147483648 || $i > 2147483647) {
die("Out of bounds");
}
return pack('C4',
($i >> 24) & 0xFF,
($i >> 16) & 0xFF,
($i >> 8) & 0xFF,
($i >> 0) & 0xFF
);
}
function pack_int32le($i) {
if ($i < -2147483648 || $i > 2147483647) {
die("Out of bounds");
}
return pack('C4',
($i >> 0) & 0xFF,
($i >> 8) & 0xFF,
($i >> 16) & 0xFF,
($i >> 24) & 0xFF
);
}
The double-precision LE is much harder. Supporting quad-precision system would involve packing the number using d, converting it to a binary string, splitting the binary into fields, truncating the fields to the right size if they're too large, concatenating the fields, then converting from binary to bytes.

PHP pack/unpack float in big endian byte order

How can I pack/unpack floats in big endian byte order with php?
I got this far with an unpack function, but I'm not sure if this would even work.
function unpackFloat ($float) {
$n = unpack ('Nn');
$n = $n['n'];
$sign = ($n >> 31);
$exponent = ($n >> 23) & 0xFF;
$fraction = $n & 0x7FFFFF;
}
After thinking about it for a while I found a pretty easy solution, to use the opposite byte order from the one pack('f') uses.
unpack
unpack('fdat', strrev(substr($data, 0, 4)));
pack
strrev(pack('f', $data));
PHP 7.2 introduced the option to pack floating point numbers with big endian byte order directly:
// float
$bytes = pack('G', 3.1415);
// double precision float
$bytes = pack('E', 3.1415);
https://www.php.net/manual/en/function.pack.php

Converting random byte (0-255) to a float in PHP?

I am reading random bytes from /dev/urandom, and I want to make a random float out of it/them. How would I do this? I can convert the byte to a number with ord(), and it's between 0 and 255.
It's obvious that 255 is 1.0, 127 is 0.5 and 0 is 0.0, but how do I calculate the rest? Also, I think one byte is not enough to give lots of precision? How many bytes should I use for this?
Try the simple linear relationship
$f = $r / 255.0;
where $r is the random byte, and $f is the random float.
This way, when $r=255, $f is 1.0 and when $r=127, $f is 0.498
to get 0.5 for r=127 would require a different relationship.
Umm, do you just want a random sequence of numbers? If so, just use http://php.net/mt_rand
$int = mt_rand(0, 1000);
$float = mt_rand(0, 1000) / 1000.0;
$float_more_precise = mt_rand(0, 100000) / 100000.0;
Since you want 127 to be 0.5, I imagine you want
$float = round($byte/255,1)
If you want the full precision float can offer, then you'll need 24 bits or three bytes:
$float = ($b1 | ($b2 << 8) | ($b3 << 16)) / (float)(1 << 24);
However, PHP's documentation is a little fuzzy on the details so if you need a double which is 64 bits long instead of float's 32 bits, then you'll need 53 bits.

Categories