Math-BigInt

 view release on metacpan or  search on metacpan

lib/Math/BigFloat.pm  view on Meta::CPAN

051870721134999999837297804995105973173281609631859502445945534690830264252
230825334468503526193118817101000313783875288658753320838142061717766914730
359825349042875546873115956286388235378759375195778185778053217122680661300
192787661119590921642019893809525720106548586327886593615338182796823030195
EOF

        # Should we round up?

        my $round_up;

        # From the string above, we need to extract the number of digits we
        # want plus extra characters for the newlines.

        my $nchrs = $n + int($n / 75);

        # Extract the digits we want.

        my $digits = substr($all_digits, 0, $nchrs);

        # Find out whether we should round up or down. Rounding is easy, since
        # pi is trancendental. With directed rounding, it doesn't matter what
        # the following digits are. With rounding to nearest, we only have to
        # look at one extra digit.

        if ($rmode eq 'trunc') {
            $round_up = 0;
        } else {
            my $next_digit = substr($all_digits, $nchrs, 1);
            $round_up = $next_digit lt '5' ? 0 : 1;
        }

        # Remove the newlines.

        $digits =~ tr/0-9//cd;

        # Now do the rounding. We could easily make the regex substitution
        # handle all cases, but we avoid using the regex engine when it is
        # simple to avoid it.

        if ($round_up) {
            my $last_digit = substr($digits, -1, 1);
            if ($last_digit lt '9') {
                substr($digits, -1, 1) = ++$last_digit;
            } else {
                $digits =~ s{([0-8])(9+)$}
                            { ($1 + 1) . ("0" x CORE::length($2)) }e;
            }
        }

        # Convert to an object.

        $pi = bless {
                     sign => '+',
                     _m   => $LIB -> _new($digits),
                     _es  => CORE::length($digits) > 1 ? '-' : '+',
                     _e   => $LIB -> _new($n - 1),
                    }, $class;

    } else {

        # For large accuracy, the arctan formulas become very inefficient with
        # Math::BigFloat, so use Brent-Salamin (aka AGM or Gauss-Legendre).

        # Use a few more digits in the intermediate computations.
        $n += 8;

        $HALF = $class -> new($HALF) unless ref($HALF);
        my ($an, $bn, $tn, $pn)
          = ($class -> bone, $HALF -> copy() -> bsqrt($n),
             $HALF -> copy() -> bmul($HALF), $class -> bone);
        while ($pn < $n) {
            my $prev_an = $an -> copy();
            $an -> badd($bn) -> bmul($HALF, $n);
            $bn -> bmul($prev_an) -> bsqrt($n);
            $prev_an -> bsub($an);
            $tn -> bsub($pn * $prev_an * $prev_an);
            $pn -> badd($pn);
        }
        $an -> badd($bn);
        $an -> bmul($an, $n) -> bdiv(4 * $tn, $n);

        $an -> round(@r);
        $pi = $an;
    }

    if (defined $r[0]) {
        $pi -> accuracy($r[0]);
    } elsif (defined $r[1]) {
        $pi -> precision($r[1]);
    }

    $pi -> _dng() if ($pi -> is_int() ||
                      $pi -> is_inf() ||
                      $pi -> is_nan());

    %$self = %$pi;
    bless $self, ref($pi);
    return $self;
}

sub copy {
    my ($x, $class);
    if (ref($_[0])) {           # $y = $x -> copy()
        $x = shift;
        $class = ref($x);
    } else {                    # $y = Math::BigInt -> copy($y)
        $class = shift;
        $x = shift;
    }

    carp "Rounding is not supported for ", (caller(0))[3], "()" if @_;

    my $copy = bless {}, $class;

    $copy->{sign} = $x->{sign};
    $copy->{_es}  = $x->{_es};
    $copy->{_m}   = $LIB->_copy($x->{_m});
    $copy->{_e}   = $LIB->_copy($x->{_e});

    $copy->{accuracy}  = $x->{accuracy} if exists $x->{accuracy};
    $copy->{precision} = $x->{precision} if exists $x->{precision};

lib/Math/BigFloat.pm  view on Meta::CPAN


    $v = $x -> copy();
    $v = $v -> binc();                  # v = x+1
    $x = $x -> bdec();
    $u = $x -> copy();                  # u = x-1; x = x-1

    $x = $x -> bdiv($v, $scaleup);        # first term: u/v

    $numer = $u -> copy();              # numerator
    $denom = $v -> copy();              # denominator

    $u = $u -> bmul($u);                # u^2
    $v = $v -> bmul($v);                # v^2

    $numer = $numer -> bmul($u);        # u^3
    $denom = $denom -> bmul($v);        # v^3

    $factor = $class -> new(3);
    $f = $class -> new(2);

    while (1) {
        my $next = $numer -> copy() -> bround($scaleup)
          -> bdiv($denom -> copy() -> bmul($factor) -> bround($scaleup), $scaleup);

        $next->{accuracy} = undef;
        $next->{precision} = undef;
        my $x_prev = $x -> copy();
        $x = $x -> badd($next);

        last if $x -> bacmp($x_prev) == 0;

        # calculate things for the next term
        $numer  = $numer -> bmul($u);
        $denom  = $denom -> bmul($v);
        $factor = $factor -> badd($f);
    }

    $x = $x -> bmul($f);             # $x *= 2
    $x = $x -> bround($scale);
}

sub _log_10 {
    # Internal log function based on reducing input to the range of 0.1 .. 9.99
    # and then "correcting" the result to the proper one. Modifies $x in place.
    my ($x, $scale) = @_;
    my $class = ref $x;

    # Taking blog() from numbers greater than 10 takes a *very long* time, so
    # we break the computation down into parts based on the observation that:
    #  blog(X*Y) = blog(X) + blog(Y)
    # We set Y here to multiples of 10 so that $x becomes below 1 - the smaller
    # $x is the faster it gets. Since 2*$x takes about 10 times as long, we
    # make it faster by about a factor of 100 by dividing $x by 10.

    # The same observation is valid for numbers smaller than 0.1, e.g.
    # computing log(1) is fastest, and the further away we get from 1, the
    # longer it takes. So we also 'break' this down by multiplying $x with 10
    # and subtract the log(10) afterwards to get the correct result.

    # To get $x even closer to 1, we also divide by 2 and then use log(2) to
    # correct for this. For instance if $x is 2.4, we use the formula:
    #  blog(2.4 * 2) == blog(1.2) + blog(2)
    # and thus calculate only blog(1.2) and blog(2), which is faster in total
    # than calculating blog(2.4).

    # In addition, the values for blog(2) and blog(10) are cached.

    # Calculate the number of digits before the dot, i.e., 1 + floor(log10(x)):
    #   x = 123      => dbd =  3
    #   x = 1.23     => dbd =  1
    #   x = 0.0123   => dbd = -1
    #   x = 0.000123 => dbd = -3
    #   etc.

    my $dbd = $LIB->_num($x->{_e});
    $dbd = -$dbd if $x->{_es} eq '-';
    $dbd += $LIB->_len($x->{_m});

    # more than one digit (e.g. at least 10), but *not* exactly 10 to avoid
    # infinite recursion

    my $calc = 1;               # do some calculation?

    # No upgrading or downgrading in the intermediate computations.

    my $upg = $class -> upgrade();
    my $dng = $class -> downgrade();
    $class -> upgrade(undef);
    $class -> downgrade(undef);

    # disable the shortcut for 10, since we need log(10) and this would recurse
    # infinitely deep
    if ($x->{_es} eq '+' &&                     # $x == 10
        ($LIB->_is_one($x->{_e}) &&
         $LIB->_is_one($x->{_m})))
    {
        $dbd = 0;               # disable shortcut
        # we can use the cached value in these cases
        if ($scale <= $LOG_10_A) {
            $x = $x -> bzero();
            $x = $x -> badd($LOG_10); # modify $x in place
            $calc = 0;                      # no need to calc, but round
        }
        # if we can't use the shortcut, we continue normally
    } else {
        # disable the shortcut for 2, since we maybe have it cached
        if (($LIB->_is_zero($x->{_e}) &&        # $x == 2
             $LIB->_is_two($x->{_m})))
        {
            $dbd = 0;           # disable shortcut
            # we can use the cached value in these cases
            if ($scale <= $LOG_2_A) {
                $x = $x -> bzero();
                $x = $x -> badd($LOG_2); # modify $x in place
                $calc = 0;                     # no need to calc, but round
            }
            # if we can't use the shortcut, we continue normally
        }
    }

    # if $x = 0.1, we know the result must be 0-log(10)



( run in 0.695 second using v1.01-cache-2.11-cpan-e1769b4cff6 )