Difference between revisions of "Math::BigInt"

From HalfgeekKB
Jump to navigation Jump to search
Line 30: Line 30:
 
=Converting a binary string to a BigInt=
 
=Converting a binary string to a BigInt=
  
Actually, I've implemented something much cooler that exploits Math::BigInt's ability to parse hex stringsThe updated code is part of the new [[Xana]] library Xana::Math::RawDataUtil.
+
This code conveniently reinterprets raw bytes as [[hexadecimals|hexadecimal]] digits, big-endian style, so that the data may be cast into a bigintThis code doesn't do any buffering; for that, try my new Xana::Math::RawDataUtil module in [[Xana]].
  
  use Math::BigInt lib => 'GMP';
+
# Unsigned
 
+
my $a = "ZYXWVUT";
  # This function works by decoding a binary string
+
my $ba = new Math::BigInt '0x' . unpack('H*',$a);
  # 3 bytes at a time (4 could cause sign trouble).
+
# Test
  # Anything that comes from this function is unsigned.
+
$ba == '25430983861228884'
  sub bin2ubig {
+
    or die "ba=$ba";
    my $len = length($_[0]);
 
    my $off = 0;
 
    my $cml = Math::BigInt->new('0');
 
 
   
 
   
    if($len == 0) {
+
# Signed
      return $cml;
+
sub raw_to_signed_bigint ($) {
    }
+
    # The MSB is the sign bit.
 +
    if(unpack('C',substr($_[0],0,1)) & 0x80) {
 +
        my $hex = unpack('H*',$_[0]);
 +
        # Invert the hex digits.
 +
        $hex =~ tr/0123456789aAbBcCdDeEfF/fedcba9876554433221100/;
 +
        my $bigint = new Math::BigInt "0x$hex";
 +
        # Then, the ones complement is the right answer.
 +
        return $bigint->bnot();
 +
    } else {
 +
        # The normal unsigned thing.
 +
        return Math::BigInt->new( '0x' . unpack('H*',$_[0]) );
 +
    }
 +
}
 
   
 
   
    my $sub;
+
# Tests
 +
my $bb = raw_to_signed_bigint "\xFF\x54\xAB\x56\x73\x14\xE0\xF5\x2E";
 +
$bb == '-12345678901234567890'
 +
    or die "bb=$bb";
 
   
 
   
    # The first get is the length % 3.
+
my $bc = raw_to_signed_bigint "\xFF";
    # This way, the rest of the gets
+
$bc == '-1'
    # can just be 3.
+
    or die "bc=$bc";
    {
 
      my $get = $len % 3;
 
 
   
 
   
      # But if the length is a multiple of three,
+
  my $bd = raw_to_signed_bigint "ZYXWVUT";
      # cut to the main part.
+
  $bd == '25430983861228884'
      last unless $get;
+
    or die "bd=$bd";
   
 
      $sub = "\0" . substr($_[0],$off,$get);
 
      $sub = "\0" . $sub while length($sub) < 4;
 
      ($sub) = unpack('N',$sub);
 
      $cml->badd($sub);
 
      $off += $get;
 
    }
 
   
 
    # All right, now the remainder of the data
 
    # is a multiple of three bytes.
 
    for(; $off < $len; $off += 3) {
 
      # Shift the existing number
 
      # left by 24 bits.
 
      $cml->bmul(0x1000000);
 
 
      # Decode a new part and add it.
 
      ($sub) = unpack('N', "\0" . substr($_[0],$off,3));
 
      $cml->badd($sub);
 
    }
 
 
    # That should be it.
 
    return $cml;
 
  }
 
 
  # This function decodes a binary string to its ones-
 
  # complement form.
 
  sub binnot {
 
    my $oc = "";
 
    my $off = 0;
 
    my $len = length($_[0]);
 
 
    my($a,$b,$c,$d,$e,$f,$g,$h);
 
 
    use integer;
 
    while($len >= 16) {
 
      ($a,$b,$c,$d,$e,$f,$g,$h)
 
        = unpack('SSSSSSSS',substr($_[0],$off,16));
 
      $a = ~$a & 0xFFFF; $b = ~$b & 0xFFFF;
 
      $c = ~$c & 0xFFFF; $d = ~$d & 0xFFFF;
 
      $e = ~$e & 0xFFFF; $f = ~$f & 0xFFFF;
 
      $g = ~$g & 0xFFFF; $h = ~$h & 0xFFFF;
 
      $oc .= pack('SSSSSSSS', $a,$b,$c,$d,$e,$f,$g,$h);
 
      $off += 16;
 
      $len -= 16
 
    }
 
    while($len >= 8) {
 
      ($a,$b,$c,$d)
 
        = unpack('SSSS',substr($_[0],$off,8));
 
      $a = ~$a & 0xFFFF; $b = ~$b & 0xFFFF;
 
      $c = ~$c & 0xFFFF; $d = ~$d & 0xFFFF;
 
      $oc .= pack('SSSS',$a,$b,$c,$d);
 
      $off += 8;
 
      $len -= 8;
 
    }
 
    while($len) {
 
      $a = unpack('C',substr($_[0],$off,1));
 
      $a = ~$a & 0xFF;
 
      $oc .= pack('C',$a);
 
      $off += 1;
 
      $len -= 1;
 
    }
 
    return $oc;
 
  }
 
 
  # This function detects a twos-complement negative.
 
  # If negative, the string is converted to its
 
  # ones-complement, processed, and increased by one.
 
  sub bin2sbig {
 
    return Math::BigInt->new('0') unless length($_[0]);
 
 
    # Is it negative?
 
    unless( unpack('C',substr($_[0],0,1)) & 0x80 ) {
 
      # No.
 
      return bin2ubig(@_);
 
    }
 
 
    # Yes.
 
    return bin2ubig(scalar binnot($_[0]))->bnot();
 
  }
 
  
 
=See Also=
 
=See Also=
  
 
* [http://search.cpan.org/~tels/Math-BigInt-1.75/lib/Math/BigInt.pm CPAN]
 
* [http://search.cpan.org/~tels/Math-BigInt-1.75/lib/Math/BigInt.pm CPAN]

Revision as of 10:28, 8 April 2005

Use

Specifying lib forces a library, but if that library isn't installed, fallback is automatic.

use Math::BigInt lib => 'GMP';

In-place Modification

Modification is in-place, regardless of context. A new object is not created for a result.

use Math::BigInt;

# Void context

my $a = new Math::BigInt '27';
$a->badd(3);

print "$a"; # 30

# Scalar context

my $b = new Math::BigInt '27';
my $c = $b->badd(3);

print "$b"; # 30
print "$c"; # 30
print $b == $c ? "Same object" : "Not same object";
  # Same object

Converting a binary string to a BigInt

This code conveniently reinterprets raw bytes as hexadecimal digits, big-endian style, so that the data may be cast into a bigint. This code doesn't do any buffering; for that, try my new Xana::Math::RawDataUtil module in Xana.

# Unsigned
my $a = "ZYXWVUT";
my $ba = new Math::BigInt '0x' . unpack('H*',$a);
# Test
$ba == '25430983861228884'
    or die "ba=$ba";

# Signed
sub raw_to_signed_bigint ($) {
    # The MSB is the sign bit.
    if(unpack('C',substr($_[0],0,1)) & 0x80) {
        my $hex = unpack('H*',$_[0]);
        # Invert the hex digits.
        $hex =~ tr/0123456789aAbBcCdDeEfF/fedcba9876554433221100/;
        my $bigint = new Math::BigInt "0x$hex";
        # Then, the ones complement is the right answer.
        return $bigint->bnot();
    } else {
        # The normal unsigned thing.
        return Math::BigInt->new( '0x' . unpack('H*',$_[0]) );
    }
}

# Tests
my $bb = raw_to_signed_bigint "\xFF\x54\xAB\x56\x73\x14\xE0\xF5\x2E";
$bb == '-12345678901234567890'
    or die "bb=$bb";

my $bc = raw_to_signed_bigint "\xFF";
$bc == '-1'
    or die "bc=$bc";

my $bd = raw_to_signed_bigint "ZYXWVUT";
$bd == '25430983861228884'
    or die "bd=$bd";

See Also