HaveBlue has asked for the wisdom of the Perl Monks concerning the following question:

I have a microcontroller project that uses an Atmel ATmega128 micro; a little-endian architecture. It runs a firmware that I wrote in C with the AVR glibc. This program stores a structure in the onboard EEPROM of the chip.
I have a script to convert a dump of this EEPROM to a file format that I can use in my analysis tools. The first member of each record is a 32bit floating point number. My script uses unpack to get the members of the structure and put them in a format that I can more easily analyze (in Matlab, etc.)
The problem is that my script runs on Sparc workstations; which are big-endian, while the ATmega chip stores data in a little endian format. How can I force
unpack(" f v v v", $record);

to unpack the float in the proper byte order?

Replies are listed 'Best First'.
Re: Unpacking Floating Point on architectures with different endians
by tilly (Archbishop) on May 04, 2004 at 06:42 UTC
    This won't be easy. As you can verify by looking at the documentation for pack, the "v" format is portable, but floats and doubles are only supported in native machine format. The problem that causes this goes back to how C handles floats. If at all possible, you really want to convert the float to something portable, and transmit that instead.

    If you don't want to go that way, then you're going to have to not use unpack. Instead you'll have to do some custom bit-twiddling to parse the float format and make it come out right.

      tilly's response reminded me of a key tidbit that I read in perlpacktut:

      Floating point Numbers

      For packing floating point numbers you have the choice between the pack codes f and d which pack into (or unpack from) single-precision or double-precision representation as it is provided by your system. (There is no such thing as a network representation for reals, so if you want to send your real numbers across computer boundaries, you'd better stick to ASCII representation, unless you're absolutely sure what's on the other end of the line.)


      Dave

Re: Unpacking Floating Point on architectures with different endians
by tkil (Monk) on May 04, 2004 at 06:53 UTC

    If you know that the only difference is byte order (and not bit order), you can just juggle the bytes manually. As an example, a file generated on my x86 (little-endian) box:

    $ perl -we 'my $pi = 4*atan2(1,1); print pack "f", $pi' > le-pi.bin $ od -h le-pi.bin 0000000 0fdb 4049 0000004

    To read that in on my ppc (big-endian) system, I can do:

    $ perl -we 'my $buf; read STDIN, $buf, 4; my $pi = unpack "f", reverse $buf; print "pi=$pi\n";' < le-pi.bin pi=3.14159274101257

    Doing this to just one part of a larger buffer just takes a bit more imagination (although the fact that substr can be used as an lvalue might make it amusing. Untested, but does this work?)

    # reverse endianness of first 4-byte float: substr( $buf, 0, 4 ) = reverse substr( $buf, 0, 4 );
Re: Unpacking Floating Point on architectures with different endians
by sgifford (Prior) on May 04, 2004 at 06:55 UTC
    The documentation for pack says there's no builtin way to do it:
      Real numbers (floats and doubles) are in
      the native machine format only; due to the
      multiplicity of floating formats around,
      and the lack of a standard "network" rep­
      resentation, no facility for interchange
      has been made.  This means that packed
      floating point data written on one machine
      may not be readable on another - even if
      both use IEEE floating point arithmetic
      (as the endian-ness of the memory repre­
      sentation is not part of the IEEE spec).
    
      See also the perlport manpage.
    

    I don't really know much about the machine format of floating point numbers, but if the only problem is endianness, unpacking as characters, reversing them, putting them back together, and then interpreting them as native format might work. Something like this:

    #!/usr/bin/perl my $f = "3.6"; my $floatpack = pack("d",$f); my $floatpack2 = pack("c*",reverse unpack("c*",$floatpack)); $f2 = unpack("d",$floatpack2); print "f2=$f2\n"; my $floatpack3 = pack("c*",reverse unpack("c*",$floatpack2)); $f3 = unpack("d",$floatpack3); print "f3=$f3\n";
Re: Unpacking Floating Point on architectures with different endians
by hawtin (Prior) on May 04, 2004 at 07:34 UTC

    When faced with this problem I found the best way was to convert to a binary string so that I could see what was going on. Have a look at this node for an example of code that should help

Re: Unpacking Floating Point on architectures with different endians
by hv (Prior) on May 04, 2004 at 12:49 UTC

    perl-5.10.0 will have new pack template modifiers "<" and ">" to force little-endian or big-endian interpretation of any of dDfFiIjJlLpPqQsS. Until then, fiddling with the bits as described by others above is likely to be your only option.

    Hugo

Re: Unpacking Floating Point on architectures with different endians
by jmcnamara (Monsignor) on May 04, 2004 at 18:10 UTC

    If both platforms use IEEE 754 format then you can just reverse the result of unpack "f" to change endianess.

    For a portable method of unpacking IEEE 754 floats see Re: Extracting IEEE 754 floating point numbers.

    I've been planning to roll this and some other methods into a Pack::Float module. I have the code written but I haven't had time to write a good test suite (the hard part ;-) ).

    --
    John.