views:

88

answers:

1

I need to do some arithmetic with large hexadecimal numbers below, but when I try to output I'm getting overflow error messages "Hexadecimal number > 0xffffffff non-portable", messages about not portable, or the maximum 32bit hex value FFFFFFFF. All of which imply that the standard language and output routines only cope with 32 bit values. I need 64bit values and have done a lot of research but found nothing that BOTH enables the arithmetic AND outputs the large number in hex.

my $result = 0x00000200A0000000 + 
             ( ( $id & 0xFFFFF ) * 2 ) + ( ( $id / 0x100000 ) * 0x40000000 );

So, for $id with the following values I should get $result:

$id = 0, $result = 0x00000200A0000000
$id = 1, $result = 0x00000200A0000002
$id = 2, $result = 0x00000200A0000004

Can anyone help please?

Here is my inconclusive research results, with reasons why:

+5  A: 
#!/usr/bin/perl

use strict;
use warnings;

use bigint qw/hex/;

for my $id (0 ,1, 2) {
    my $result = hex("0x00000200A0000000") + 
        ( ( $id & 0xFFFFF ) * 2 ) + ( ( $id / 0x100000 ) * 0x40000000 );
    printf "%d: %#016x\n", $id, $result;
}

The bigint pragma replaces the hex function with a version that can handle numbers that large. It also transparently makes the mathematical operators deal with big ints instead of the ints on the target platform.

Note, this only works in Perl 5.10 and later. If you are running an earlier version of Perl 5, you can try this:

#!/usr/bin/perl

use strict;
use warnings;
use bigint;

use Carp;

sub bighex {
    my $hex = shift;

    my $part = qr/[0-9a-fA-F]{8}/;
    croak "$hex is not a 64-bit hex number"
        unless my ($high, $low) = $hex =~ /^0x($part)($part)$/;

    return hex("0x$low") + (hex("0x$high") << 32);
}

sub to_bighex {
    my $decimal = shift;
    croak "$decimal is not an unsigned integer"
            unless $decimal =~ /^[0-9]+$/;

    my $high = $decimal >> 32;
    my $low  = $decimal & 0xFFFFFFFF;

    return sprintf("%08x%08x", $high, $low);
}

for my $id (0 ,1, 2) {
    my $result = bighex("0x00000200A0000000");
    $result += ( ( $id & 0xFFFFF ) * 2 ) + ( ( $id / 0x100000 ) * 0x40000000 );
    print "$id ", to_bighex($result), "\n";
}
Chas. Owens
+1: excellent answer...
drewk
Sometimes you don't need portability. And reaching for the bigint hammer to do 64 bit math on a 64 bit supporting perl just doesn't appeal to me. That printf() call you do already throws portability out the window.
ysth
@ysth Yeah, I saw that and wrote a second version that should work on all 32-bit machines.
Chas. Owens
@Chas.Owens. -thank you very much for answering so quick but I got:unknown option hex at /usr/lib/perl5/5.8.8/bigint.pm line 142.BEGIN failed--compilation aborted at XXXX line 23 (line 23: use bigint qw/hex/;
Rob
That means you are using Perl 5.8.9 or lower. You will need to use the second version. The `hex` replacement only works in Perl 5.10 or later.
Chas. Owens
Brilliant - 2nd one works for me. I am using CentOS with v5.8.8. +1 and Accepted answer. I tested it out for a range of values and all are fine. Well done, @Chas. Owens. Thank you very much. Later, I'd like to upgrade to 5.10 but being a production server this might be risk for the live applications running on it.
Rob
Make it clearer that the second version is needed even with 5.10+ when you have 32 bit ints?
ysth
(As an aside, I wouldn't have used bigint; I prefer using Math::BigInt explicitly where needed.)
ysth