Saturday 9 February 2013

PHP Convert HEX <- 2 -> Floating point (Removed from PHP.net)

This article was removed from PHP.net notes because it was to big.. hell.. it's just two 30 line fucntions! 

Hey PHP.net guys! If you would've compiled this function into php there would'nt be a need for me or anyother looking for this to have it as a note =)

There are many scripts/functions to convert a Hex string to a Floating point number, but i could'nt find any usable function to convert a float2hex (float number to hexadecimal) .. so i went on an made one! .. to my surprise.. it didn't matched the other existing functions results exactly due to different methods used on each .. so i went on an created a complementary function to have both results match float2hex32n<->hex2float32n.


<?php/** Complementary functions to CONVERT PHP FLOATING POINT NUMBERS or DECIMALS
* (IEEE 754 single-precision 32 bit) TO HEXADECIMAL AND BACK.
*

* @created on 28/Jan/2010 / time spent: 2hours approx.
* @special thanks to Thomas Finley's article "Floating Point"
*
http://tfinley.net/notes/cps104/floating.html
*
* These functions allow to convert any php floating point numbers with the
* notation 1.234 (or fixed point numbers) into their corresponding 8 digits
* hexadecimal notation. (i.e.- 1557897.40 -> 49BE2C4B <- 1557897.40) by
* disregarding their implicit format and treating them at will as either
* integer, decimals, binary numbers, hexadecimal values, or plain text.
*
**/

/** FLOAT2HEX32n
* (Convert php float numbers or decimals (single-precision 32bits) to 8 digit Hexadecimal)
* Accepts only fixed point notation decimal numbers or fractions on a string (i.e.- "1.23456")
* @usage:
* float2hex32n("-1557897.13"); returns "c9be2c49"
**/
function float2hex32n($number) {
//Convert non-decimal to decimal.
$number=number_format($number,23,'.',''
);
//Get the integer portion of $number
if ($number>0
) {
$intnumber=substr($number,0,strpos($number,"."
));
} else {
//Check whether is a negative number to remove - sign.
$intnumber=substr($number,1,strpos($number,"."
));
}
//Convert integer to binary
$binint=decbin($intnumber
);
//Get the decimal fraction of the number
$pointnumber=substr($number,strpos($number,"."
));
//Add a 0 to treat as single decimal fraction
$pointnumber="0".$pointnumber
;
//Convert decimal values to base 2
$tmppoint=number_format($pointnumber*2,23,'.',''
);
for (
$i=0; $i<23; $i
++) {
$binpoint.=substr((string)$tmppoint,0,1
);
$tmppoint=substr((string)$tmppoint,1
);
$tmppoint=number_format($tmppoint*2,23,'.',''
);
}
//Gather both values to get base 2 binary fraction
$scibin=$binint.".".$binpoint
;
//Find fraction separator "." position
$exp=strpos($scibin,"."
);
//Transform to scientific notation (1.x^exp)
$scibin=substr($binint,0,1).".".substr($binint,1).$binpoint
;
//Create mantissa
if ($scibin>1
) {
$mantissa=substr($scibin,2
);
} else {
$mantissa=substr($scibin,1
);
}
//Fill mantissa to 23 bit value
$fill23=23-strlen($mantissa
);
for (
$i=0; $i<$fill23; $i
++) {
$mantissa.="0"
;
}
//Convert fraction separator position to exponent value
if ($exp>0) { $exp
--; }
//Adjust to binary notation exponent value
$exp+=127
;
//Convert to 8 bit binary
$exp=decbin($exp
);
//Find 1 bit sign value
if ($number>0) { $sign=0; } else { $sign=1
; }
//Compose final binary value
$binfinal=$sign.$exp.$mantissa
;
//Reorganizae number into 4digit/bits packets and
//finally convert to decimal value and to hex.
$hexfinal.=dechex(bindec(substr($binfinal,0,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,4,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,8,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,12,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,16,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,20,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,24,4
)));
$hexfinal.=dechex(bindec(substr($binfinal,28,4
)));

return
$hexfinal
;
}
/** Using either function will return a value which can be used on the other function
* to return the original input. (Hope it helps =)
**/
?>

 


Second function (HEX2FLOAT32n) converts Hexadecimal 8 digit strings to fixed decimals float numbers. (complementary to FLOAT2HEX32n above)

<?php /** Complementary functions to CONVERT PHP FLOATING POINT NUMBERS or DECIMALS
* (IEEE 754 single-precision 32 bit) TO HEXADECIMAL AND BACK.
*

* @created on 28/Jan/2010 / time spent: 2hours approx.
* @special thanks to Thomas Finley's article "Floating Point"
*
http://tfinley.net/notes/cps104/floating.html
*
* These functions allow to convert any php floating point numbers with the
* notation 1.234 (or fixed point numbers) into their corresponding 8 digits
* hexadecimal notation. (i.e.- 1557897.40 -> 49BE2C4B <- 1557897.40) by
* disregarding their implicit format and treating them at will as either
* integer, decimals, binary numbers, hexadecimal values, or plain text.
*
**/

/** HEX2FLOAT32n
* (Convert 8 digit hexadecimal values to fixed decimals float numbers (single-precision 32bits)
* Accepts 8 digit hexadecimal values on a string (i.e.- F1A9B02C) and single integer to fix number of decimals
* @usage:
* hex2float32n("c9be2c49",2); returns -> "-1557897.13"
**/
function hex2float32n($number,$nd) {
//Separate each hexadecimal digit
for ($i=0; $i<strlen($number); $i
++) {
$hex[]=substr($number,$i,1
);
}
//Convert each hexadecimal digit to integer
for ($i=0; $i<count($hex); $i
++) {
$dec[]=hexdec($hex[$i
]);
}
//Convert each decimal value to 4bit binary and join on a string
for ($i=0; $i<count($dec); $i
++) {
$binfinal.=sprintf("%04d",decbin($dec[$i
]));
}
//Get sign 1bit value
$sign=substr($binfinal,0,1
);
//Get exponent 8bit value
$exp=substr($binfinal,1,8
);
//Get mantissa 23bit value
$mantissa=substr($binfinal,9
);
//Convert & adjunt binary exponent to integer
$exp=bindec($exp
);
$exp-=127
;
//Assign mantissa to pre-scientific binary notation
$scibin=$mantissa
;
//Split $scibin into integral & fraction parts through exponent
$binint=substr($scibin,0,$exp
);
$binpoint=substr($scibin,$exp
);
//Convert integral binary part to decimal integer
$intnumber=bindec("1".$binint
);
//Split each binary fractional digit
for ($i=0; $i<strlen($binpoint); $i
++) {
$tmppoint[]=substr($binpoint,$i,1
);
}
//Reverse order to work backwards
$tmppoint=array_reverse($tmppoint
);
//Convert base 2 digits to decimal
$tpointnumber=number_format($tmppoint[0]/2,strlen($binpoint),'.',''
);
for (
$i=1; $i<strlen($binpoint); $i
++) {
$pointnumber=number_format($tpointnumber/2,strlen($binpoint),'.',''
);
$tpointnumber=$tmppoint[$i+1].substr($pointnumber,1
);
}
//Join both decimal section to get final number
$floatfinal=$intnumber+$pointnumber
;
//Convert to positive or negative based on binary sign bit
if ($sign==1) { $floatfinal=-$floatfinal
; }

//Format float number to fixed decimals required
return number_format($floatfinal,$nd,'.',''
);

}
/** Using either function will return a value which can be used on the other function
* to return the original input. (Hope it helps =)
**/
?>



Note: manasseh@smartconputerinc.com wrote:

I found that 00000000 hex was converting to 1.0 decimal. From the Wikipedia article on IEEE-754 floating point:

The true significand includes 23 fraction bits to the right of the binary point and an implicit leading bit (to the left of the binary point) with value 1 unless the exponent is stored with all zeros.

In hex2float32n, replace:

$intnumber=bindec("1".$binint);

with

if ($exp <> -127)
{ $intnumber=bindec("1".$binint); };

and then 00000000 works correctly without affecting "normal" numbers.