8

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 32-bit hex value FFFFFFFF.

All of which imply that the standard language and output routines only cope with 32 bit values. I need 64-bit values and have done a lot of research, but I 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

How can I do this?

Here is my inconclusive research results, with reasons why:


Edit: Update - new requirement and supplied solution - please feel free to offer comments

Chas. Owens answer is still accepted and excellent (part 2 works for me, haven't tried the part 1 version for newer Perl, though I would invite others to confirm it).

However, another requirement was to be able to convert back from the result to the original id.

So I've written the code to do this, here's the full solution, including @Chas. Owens original solution, followed by the implementation for this new requirement:

#!/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, 0xFFFFF, 0x100000, 0x100001, 0x1FFFFF, 0x200000, 0x7FDFFFFF ) {
    my $result = bighex("0x00000200A0000000");
    $result += ( ( $id & 0xFFFFF ) * 2 ) + ( ( $id / 0x100000 ) * 0x40000000 );

    my $clusterid = to_bighex($result);

# the convert back code here:
my $clusterid_asHex = bighex("0x".$clusterid);
my $offset = $clusterid_asHex - bighex("0x00000200A0000000");
my $index_small_units = ( $offset / 2 ) & 0xFFFFF;
my $index_0x100000_units = ( $offset / 0x40000000 ) * 0x100000;
my $index = $index_0x100000_units + $index_small_units;


    print "\$id = ".to_bighex( $id ).
          " clusterid = ".$clusterid.
          " back to \$id = ".to_bighex( $index ).
          " \n";
}

Try out this code at http://ideone.com/IMsp6.

Community
  • 1
  • 1
therobyouknow
  • 6,604
  • 13
  • 56
  • 73
  • That isn't an error message, it's a warning. Specifically, one that your code may work where perl uses 64 bit integers but not where it uses 32 bit integers. If in fact you have and will always have 64 bit integers, disable it with `no warnings "portable";` – ysth Oct 06 '10 at 15:42
  • @ysth That is a bad idea. The code will no longer be portable then. Silencing warnings by turning them off is a bad practice. See my answer for a better solution. – Chas. Owens Oct 06 '10 at 15:46
  • Even though accepted another answer, polite for me to respond to you: Just for the record: Took your advice about no warnings "portable"; but still got error message: "Integer overflow in hexadecimal number at ./code.pl line 9" - code used: #!/usr/bin/perl -w use strict; use warnings; no warnings "portable"; my $id = 1; my $result = 0x0000_0200_A000_0000 + ( ( $id & 0xFFFFF ) * 2 ) + ( ( $id / 0x100000 ) * 0x4000_0000 ); – therobyouknow Oct 06 '10 at 16:37

2 Answers2

14
#!/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
  • 64,182
  • 22
  • 135
  • 226
  • 3
    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 Oct 06 '10 at 15:54
  • 2
    @ysth Yeah, I saw that and wrote a second version that should work on all 32-bit machines. – Chas. Owens Oct 06 '10 at 16:15
  • @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/; – therobyouknow Oct 06 '10 at 16:16
  • 2
    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 Oct 06 '10 at 16:18
  • 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. – therobyouknow Oct 06 '10 at 16:28
  • Make it clearer that the second version is needed even with 5.10+ when you have 32 bit ints? – ysth Oct 06 '10 at 16:46
  • (As an aside, I wouldn't have used bigint; I prefer using Math::BigInt explicitly where needed.) – ysth Oct 06 '10 at 16:47
  • Also have a look at my updated question to see the reverse of this calculation - as I need to get the original id value back from the result. Thanks to everyone, a great learning exercise. – therobyouknow May 30 '11 at 17:35
1

The comment by ysth is right. Short example of 64-bit arithmetics using Perl from Debian stretch without Math::BigInt aka "use bigint":

#!/usr/bin/perl -wwi

sub do_64bit_arith {
    use integer;
    my $x = ~2;
    $x <<= 4;
    printf "0x%08x%08x\n", $x>>32, $x;
}

do_64bit_arith();
exit 0;

The script prints 0xffffffffffffffffffffffffffffffd0.

sizif
  • 174
  • 5