开发者

Binary representation of float to decimal conversions in Perl

I read Stack Overflow question How do I convert a binary string to a number in Perl? on how to convert binary integers to decimal or vice versa in Perl. But how do I do this for float as well?

For开发者_开发技巧 example, conversion from 5.375 to 101.011 and vice versa.


sub number_to_binary_string {
    my $in = shift;
    my $sign = $in < 0 and $in = abs $in;
    my $out = sprintf "%b.", int $in;
    substr $out, 0, 0, '-' if $sign;
    $in -= int $in;
    do {
        if ($in >= .5) {
            $out .= '1';
            $in -= .5;
        }
        else {
            $out .= '0';
        }
        $in *= 2;
    } while $in > 0;
    return $out;
}

sub binary_string_to_number {
    my $in = shift;
    my ($int,$frac) = split /\./, $in;
    my $sign = $int =~ s/^-//;
    my $out = oct "0b$int";
    my $mult = 1;
    for my $digit (split //, $frac) {
        $mult *= .5;
        $out += $mult * $digit;
    }
    $out = -$out if $sign;
    return $out;
}


Below is a machine- and build-specific implementation (NV = little-endian double).

It returns the number stored exactly, and it supports NaN, Infinity, -Infinity and -0 and subnormals. It trims leading zeros and trailing decimal zeroes.

sub double_to_bin {
   my ($n) = @_;
   my ($s, $e, $m) = unpack 'a a11 a52', unpack 'B64', "".reverse pack 'F', $n;
   $s = $s ? '-' : '';
   $e = oct("0b$e");

   if ($e == 0x7ff) {
      return ($m =~ /1/) ? 'NaN' : $s . 'Infinity'
   } elsif ($e == 0x000) {
      $m = "0$m";  $e -= 52;
   } else {
      $m = "1$m";  $e -= 1075;
   }

   if ($e >= 0) {
      $m .= ('0' x $e);
   } elsif ($e >= -52) {
      substr($m, $e+53, 0, '.');
   } else {
      $m = '0.' . ('0' x (-$e-53)) . $m;
   }

   $m =~ s/^0+(?!\.)//;
   $m =~ s/(?:\..*1\K|\.)0+\z//;
   return $s . $m;
}


Here's a sketch of an interesting "portable" implementation. It doesn't handle any of the interesting edge-cases like integers, NaNs, infinities, or even negative numbers because I'm lazy, but extending it wouldn't be so hard.

(my $bin = sprintf "%b.%032b", int($num), 2**32 * ($num - int($num)))
    =~ s/\.?0+$//;

The 2**32 seems like an architecture-specific magic number but in fact it's basically just how many bits of precision you want after the dot. Too small and you get harmless truncation; too large and there's potential for overflow (since %b probably casts to UV sometime before doing its formatting).


$TO_BIN = '-b';
$TO_DEC = '-d';
($op, $n ) = @ARGV;
die("USAGE: $0 -b <dec_to_convert> | -d <bin_to_convert>\n") unless ( $op =~ /^($TO_BIN|$TO_DEC)$/ && $n );

for (split(//,$n)) {
    if ($_ eq ".") {
        $f=".";
    } else {
        if (defined $f) { $f.=$_ } else { $i.=$_ }
    }   
} 
$ci = sprintf("%b", $i)          if $op eq $TO_BIN;
$ci = sprintf("%d", eval "0b$i") if $op eq $TO_DEC;

@f=split(//,$f) if $f;
if ($op eq $TO_BIN) {
    while( $f && length($cf) < 16 ) { 
        ($f *= 2) =~ s/(\d)(\.?.*)/$2/;
        $cf .= $1 ? '1' : '0';
    }
} else {
    for ($i=1;$i<@f;$i++) {
        $cf = ($cf + $f[@f-$i])/2;
    }
}
$cf=~s/^.*\.|^/./ if $cf;
print("$ci$cf\n");
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜