A
A. Sinan Unur
Hello all:
In one of my programs, I had to read data that was saved in binary
format. Some of the data consisted of IEEE 754 single precision floats
saved as 32 bit integers (in network order). My pack/unpack skills are
not that great (I don't think they can handle this case) so I wrote
something to handle the conversion of these numbers.
I would very much appreciate if you can take a look at the code and see
if I am doing anything that I should not be doing or if there is a
better way of doing this.
The function in question is ieee_single_from_int in the string below.
The function is a straight-forward application of the manual steps
needed to go from the integer representation to the floating point
number.
I would like to know if there is an obvious way of doing this that I
have missed or if there is a CPAN module that already handles these
kinds of conversions. If not, I'll package this as a module and start
preparing my first ever CPAN contribution ;-)
You can use http://babbage.cs.qc.edu/IEEE-754/32bit.html to check for
correctness. http://en.wikipedia.org/wiki/IEEE_754 explains the format.
#!/usr/bin/perl
use strict;
use warnings;
my $buffer;
# The following loop replaces the routine to read reasonably
# sized chunks from the file.
while ( my $line = <DATA> ) {
my $hex;
last unless ( $hex ) = ($line =~ /\A\d{7}: ([[:xdigit:] ]+)/);
while ( $hex =~ /([[:xdigit:]]{2})/g ) {
$buffer .= chr( hex $1 );
}
}
for ( my $i = 0; $i < length $buffer; $i += 4 ) {
my $uint32 = unpack 'N', substr( $buffer, $i, 4 );
my ($v, $e) = ieee_single_from_int( $uint32 );
if ( defined $v ) {
printf "%8.8x : % .16f\n", $uint32, $v;
}
else {
warn sprintf "%8.8x : %s\n", $uint32, $e;
}
}
use constant DENOMINATOR => 0x00800000;
use constant UINT32_MASK => 0xffffffff;
use constant SIGN_MASK => 0x80000000;
use constant FRAC_MASK => 0x007fffff;
use constant EXP_MASK => 0x7f800000;
sub ieee_single_from_int {
my $uint32 = ( $_[0] & UINT32_MASK );
my $exp = ( $uint32 & EXP_MASK ) >> 23;
my $frac = $uint32 & FRAC_MASK;
my $sign = $uint32 & SIGN_MASK ;
my ($v, $e);
if ( $exp and $exp < 0xff ) {
$v = ( 1 + $frac / DENOMINATOR ) * ( 2**( $exp - 127) );
}
elsif( $exp == 0x00 ) {
$v = ( $frac / DENOMINATOR ) * ( 2**( -126 ) );
}
elsif( $exp == 0xff ) {
$e = $frac ? "NaN"
: $sign ? "-Infinity"
: "+Infinity";
}
$v = -$v if defined( $v ) and $sign;
return wantarray ? ( $v, $e ) : $v;
}
__DATA__
0000420: 4016 2933 3f1b 739a be86 8200 c00d c853 @.)3?.s........S
0000430: bf18 7633 404a 3eba bfc5 b34d 3ea3 00a7 ..v3@J>....M>...
0000440: bfae 1e10 3e30 8d00 bfa0 02da bfb9 2bed ....>0........+.
0000450: 3f33 66da bfbc 9b4d 3fa3 c200 c088 cd93 ?3f....M?.......
0000460: 40f2 5e4a 4005 5407 c086 b92a bf61 5f8a @.^[email protected]....*.a_.
0000470: bf2a 75da 3f5d 2a4d bf9a 1373 bfbd 475a .*u.?]*M...s..GZ
Thank you for your time.
Sinan
In one of my programs, I had to read data that was saved in binary
format. Some of the data consisted of IEEE 754 single precision floats
saved as 32 bit integers (in network order). My pack/unpack skills are
not that great (I don't think they can handle this case) so I wrote
something to handle the conversion of these numbers.
I would very much appreciate if you can take a look at the code and see
if I am doing anything that I should not be doing or if there is a
better way of doing this.
The function in question is ieee_single_from_int in the string below.
The function is a straight-forward application of the manual steps
needed to go from the integer representation to the floating point
number.
I would like to know if there is an obvious way of doing this that I
have missed or if there is a CPAN module that already handles these
kinds of conversions. If not, I'll package this as a module and start
preparing my first ever CPAN contribution ;-)
You can use http://babbage.cs.qc.edu/IEEE-754/32bit.html to check for
correctness. http://en.wikipedia.org/wiki/IEEE_754 explains the format.
#!/usr/bin/perl
use strict;
use warnings;
my $buffer;
# The following loop replaces the routine to read reasonably
# sized chunks from the file.
while ( my $line = <DATA> ) {
my $hex;
last unless ( $hex ) = ($line =~ /\A\d{7}: ([[:xdigit:] ]+)/);
while ( $hex =~ /([[:xdigit:]]{2})/g ) {
$buffer .= chr( hex $1 );
}
}
for ( my $i = 0; $i < length $buffer; $i += 4 ) {
my $uint32 = unpack 'N', substr( $buffer, $i, 4 );
my ($v, $e) = ieee_single_from_int( $uint32 );
if ( defined $v ) {
printf "%8.8x : % .16f\n", $uint32, $v;
}
else {
warn sprintf "%8.8x : %s\n", $uint32, $e;
}
}
use constant DENOMINATOR => 0x00800000;
use constant UINT32_MASK => 0xffffffff;
use constant SIGN_MASK => 0x80000000;
use constant FRAC_MASK => 0x007fffff;
use constant EXP_MASK => 0x7f800000;
sub ieee_single_from_int {
my $uint32 = ( $_[0] & UINT32_MASK );
my $exp = ( $uint32 & EXP_MASK ) >> 23;
my $frac = $uint32 & FRAC_MASK;
my $sign = $uint32 & SIGN_MASK ;
my ($v, $e);
if ( $exp and $exp < 0xff ) {
$v = ( 1 + $frac / DENOMINATOR ) * ( 2**( $exp - 127) );
}
elsif( $exp == 0x00 ) {
$v = ( $frac / DENOMINATOR ) * ( 2**( -126 ) );
}
elsif( $exp == 0xff ) {
$e = $frac ? "NaN"
: $sign ? "-Infinity"
: "+Infinity";
}
$v = -$v if defined( $v ) and $sign;
return wantarray ? ( $v, $e ) : $v;
}
__DATA__
0000420: 4016 2933 3f1b 739a be86 8200 c00d c853 @.)3?.s........S
0000430: bf18 7633 404a 3eba bfc5 b34d 3ea3 00a7 ..v3@J>....M>...
0000440: bfae 1e10 3e30 8d00 bfa0 02da bfb9 2bed ....>0........+.
0000450: 3f33 66da bfbc 9b4d 3fa3 c200 c088 cd93 ?3f....M?.......
0000460: 40f2 5e4a 4005 5407 c086 b92a bf61 5f8a @.^[email protected]....*.a_.
0000470: bf2a 75da 3f5d 2a4d bf9a 1373 bfbd 475a .*u.?]*M...s..GZ
Thank you for your time.
Sinan