John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

Suppose I have 64-bit numbers (some signed, some unsigned) in big-endian format. If I know that the actual value fits in 52 bits, I can use floating-point "doubles" to do the real work.

So, what's the easiest/fastest/best way to "unpack" the 8-byte value into a Perl scalar that holds a normal floating point number?

Ideas:

—John

Replies are listed 'Best First'.
(tye)Re: 52-bit numbers as floating point
by tye (Sage) on Aug 08, 2002 at 03:49 UTC

    The first one is pretty easy. See (tye)Re: 64 bit Integer anyone? for my version. It even detects if the number didn't quite fit in the 52 bits.

            - tye (but my friends call me "Tye")
      That's terrific, thanks.

      That uses the first idea: unpack as two L's.

      It seems your design goal was portability, since you automatically detect the endian.

      I see that you try to unpack "Q" first (in the signed form, you should be using "q"). I like that idea, but I think I'll swap out the whole sub so it only tries once when it decides it can't do that. In yours, you'll confuse a valid 0 integer with failure.

      I noticed you wrote 1+~0 for 0x100000000, and my first impression was that this was also for portability. But, I think it's nonportable now, and that's just clearer than writing it out in decimal (the hex form overflows Perl's parser and is not allowed). But, unpack "L" is specifically 32-bit fields and not necessarily the native size, and nowhere does it say what size bit operators operate on, but I think this is the definition of "native size".

      So, you are assuming that the native size is 32 bits if quadword support is disabled. I think I'll use 2**32 for clarity, and trust the optomizer to fold the constant arithmetic down.

      —John

      update: I didn't notice the extra ;1; at the end of the eval block. Cute. But easy to miss.

        In yours, you'll confuse a valid 0 integer with failure.

        No, it won't. Your other points are valid. (:

                - tye (but my friends call me "Tye")
Re: 52-bit numbers as floating point
by kschwab (Vicar) on Aug 08, 2002 at 03:40 UTC
    The author of the Spreadsheet::WriteExcel module appears to have had a similar problem.

    Spreadsheet::WriteExcel will work on the majority of Windows, UNIX and Macintosh platforms. Specifically, the module will work on any system where perl packs floats in the 64 bit IEEE format. The float must also be in little-endian format but it will be reversed if necessary. Thus:

     
    print join(" ", map { sprintf "%#02x", $_ } 
          unpack("C*", pack "d", 1.2345)), "\n";
    
    should give (or in reverse order): 
    
        0x8d 0x97 0x6e 0x12 0x83 0xc0 0xf3 0x3f
    
    So, as I read it, if you aren't concerned about portability, the following should work:
    my $dfloat=unpack("d",reverse($yourbigendian));
    (remove the reverse bit if your box is big-endian. I'm assuming it's not, since you mentioned ActiveState..)

    Update: Seems I was a bit confused about what format he did have the numbers in. ( In my defense, I'm still not clear :) Perhaps a pointer to the IEEE double format would make this node somewhat useful ? See This page from Sun

      The code you posted originally is still a good idea, so that the script can validate its assumptions that a "d" value is indeed stored this way.

      Thanks for the link! That's exactly what I was looking for.

      I think that code you quoted validates his assumptions about how "d" values are stored on the implementation. But it doesn't say anything about why he needs that or what the format is. Presumably, he's also manipulating bits directly.

      The line you suggest will unpack a float, but doesn't tell me how I arrage the bits to make a float in the first place!