http://qs321.pair.com?node_id=59106

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

Furthering a question of mine that was answered about generating a list of IPs, i discovered that what i needed required BigInt to store some of the results.

My question is simple I think - just not to me:

currently I get the IP into a variable as I was told using:

$ip1 = map { unpack "N", pack "C4", split /\./} $ARGV[0];

$ARGV[0] will be a dot ip eg: 203.10.10.56

Anything above 128.0.0.0 (roughly) is too large to store in a standard variable so I need ip1 to be a BigInt. As far as I know it needs to be declared with a string in the form: $ip1 = Math::BigInt->new("123456789");

So how could I adapt this map line to initialise a BigInt with the equivalent numerical value as a string? Anything I try to do to manipulate the $ip1 standard variable results in an error saying it is too large.

Thanks

Replies are listed 'Best First'.
(tye)Re: BigInt usage
by tye (Sage) on Feb 17, 2001 at 11:39 UTC

    That would be true if you used signed integers. For unsigned integers, you will be fine. Even if you needed a few more bytes, Perl uses doubles so you can accurately deal with integers of about 56-bits each.

            - tye (but my friends call me "Tye")
      Sounds fair to me, so I tried it - am I correct in using "L" instead of "N" in the unpack?

      my @range = map { unpack "L", pack "C4", split /\./ } qw(10.0.0.0 10.1 +.0.0); print "@range\n"; # the numbers themselves print $range[1]-$range[0]+1, "\n"; # how many addresses for ($range[0]..$range[1]) { print join(".", unpack "C4", pack "N", $_), "\n"; }

      When i do the above it reverses the input to 0.0.0.10 -> 0.0.1.10 I can fix this up I guess, but why is it doing that?

      Am I using the wrong unpack data type?

        After some stuffing around I came up with a solution:

        The large integers are never a problem until the 'for' loop, so I just used the ACTUAL host count instead of the numbers representing each address, then added the original address representation that was subtracted when it is joined.

        I have limited the width of the range regardless because what I am doing with the IPs is fruitless when there are more than 1 million. So BigInt is useless anyway.

        my @range = map { unpack "N", pack "C4", split /\./ } $ARGV[0], $ARGV[ +1]; for (0..$range[1]-$range[0]) { print join(".", unpack "C4", pack "N", $_+$range[0]), "\n"; }
        I can't fault the code now, does exactly what I want in every case.

        Thanks.

(tye)Re2: BigInt usage
by tye (Sage) on Feb 17, 2001 at 21:15 UTC

    Sorry, my previous reply was a bit of a quickie. I did some experiments and wanted to now more explicitly fill in some of the bits that you and I hinted at.

    Your original code and my modified version here:

    my @range= unpack "N*", pack "C*", split /[. ]+/, "@ARGV";
    both work for any list of valid IP addresses in dotted-decimal notation, even those above 128.0.0.0.

    The problem comes when you try to do:

    $range[0]..$range[1]
    with such large numbers. That causes Perl to squawk:
    Range iterator outside integer range at ...
    Which was news to me; thanks for teaching me something. (:

    Your solution of using 0..$range[1]-$range[0] is quite good. You could also do:

    for( $_= $range[0]; $_ <= $range[1]; $_++ )
    Both should work for even 56-bit integers (I don't recall IPv6 address notation...). Though, you can't use the "N" format for pack()/unpack() for more than 32 bits, so you'd need to build up the number:
    sub octets2num { my $val= 0; for( split /\./, shift(@_) ) { $val= 0x100*$val + $_; } return $val; } sub num2octects { my $val= shift(@_); my @bytes; while( 0 < $val ) { unshift @bytes, $val%0x100; $val= int( $val / 0x100 ); } return ! @bytes ? "0" : join ".", @bytes }
    Note that using bit-wise operators (or use integer) above would break these. (I think using push instead of unshift and adding a reverse might be slightly more efficient.)

    Update: I couldn't help golfing the first line of code a bit.

            - tye (just playing; that's how I usually learn)
      Thanks, all is happy now. I left my first line 'ungolfed' as I actually have 7 other unrelated parameters on the command line after the two IPs.

      I thought I should answer my original question about BigInt initialisation that was rightfully dispelled.

      I was assuming Perl was as strongly typed as Java, and it is done a lot in Java to force data types to an outputable format from default output functions in some of the java system calls.

      Java would do something like this to force an integer to a string so it can pass it to initialise a BigInt: $bigint1.new("",$integer); In Perl there is no need, and the best thing is that BigInts can have ordinary arithmetic functions applied to them, most other BigInts I have seen in other languages have their own BigInt arithmetic functions which make life a mind bending hassle  $x=$bigint1.add($x,$y); for example.

      So..... my point?

      Numbers and strings are interchangeable (as written in many a Perl book) so you can just assign a numeric variable in the place of a string:

      use Math::BigInt; $std1 = 67*34; $bigint1 = Math::BigInt->new($std1); print "std: $std1\nbigint: $bigint1\n";
      Just wanted to contribute to the topic as we veered off it :)