Логарифм для BigInteger

У меня есть номер BigInteger, например больше 2 64. Теперь я хочу вычислить логарифм этого числа BigInteger, но метода BigInteger.log() не существует. Как мне вычислить (натуральный) логарифм моего большого BigInteger значения?


person RAVITEJA SATYAVADA    schedule 26.07.2011    source источник
comment
Вам нужно все значение или только целую его часть (как при делении)?   -  person RiaD    schedule 04.07.2013


Ответы (5)


Если вы хотите поддерживать сколь угодно большие целые числа, небезопасно просто делать

Math.log(bigInteger.doubleValue());

потому что это не сработает, если аргумент превышает диапазон double (около 2 ^ 1024 или 10 ^ 308, т. е. более 300 десятичных цифр).

Вот мой собственный класс, который предоставляет методы

double logBigInteger(BigInteger val);
double logBigDecimal(BigDecimal val);
BigDecimal expBig(double exponent);
BigDecimal powBig(double a, double b);

Они работают безопасно, даже когда BigDecimal / BigInteger слишком велики (или слишком малы), чтобы их можно было представить как тип double.

import java.math.*;

/**
 * Provides some mathematical operations on {@code BigDecimal} and {@code BigInteger}.
 * Static methods.
 */
public class BigMath {

    public static final double LOG_2 = Math.log(2.0);
    public static final double LOG_10 = Math.log(10.0);

    // numbers greater than 10^MAX_DIGITS_10 or e^MAX_DIGITS_E are considered unsafe ('too big') for floating point operations
    private static final int MAX_DIGITS_10 = 294;
    private static final int MAX_DIGITS_2 = 977; // ~ MAX_DIGITS_10 * LN(10)/LN(2)
    private static final int MAX_DIGITS_E = 677; // ~ MAX_DIGITS_10 * LN(10)

    /**
     * Computes the natural logarithm of a {@link BigInteger} 
     * <p>
     * Works for really big integers (practically unlimited), even when the argument 
     * falls outside the {@code double} range
     * <p>
     * 
     * 
     * @param val Argument
     * @return Natural logarithm, as in {@link java.lang.Math#log(double)}<br>
     * {@code Nan} if argument is negative, {@code NEGATIVE_INFINITY} if zero.
     */
    public static double logBigInteger(BigInteger val) {
        if (val.signum() < 1)
            return val.signum() < 0 ? Double.NaN : Double.NEGATIVE_INFINITY;
        int blex = val.bitLength() - MAX_DIGITS_2; // any value in 60..1023 works here
        if (blex > 0)
            val = val.shiftRight(blex);
        double res = Math.log(val.doubleValue());
        return blex > 0 ? res + blex * LOG_2 : res;
    }

    /**
     * Computes the natural logarithm of a {@link BigDecimal} 
     * <p>
     * Works for really big (or really small) arguments, even outside the double range.
     * 
     * @param val Argument
     * @return Natural logarithm, as in {@link java.lang.Math#log(double)}<br>
     * {@code Nan} if argument is negative, {@code NEGATIVE_INFINITY} if zero.
     */
    public static double logBigDecimal(BigDecimal val) {
        if (val.signum() < 1)
            return val.signum() < 0 ? Double.NaN : Double.NEGATIVE_INFINITY;
        int digits = val.precision() - val.scale();
        if (digits < MAX_DIGITS_10 && digits > -MAX_DIGITS_10)
            return Math.log(val.doubleValue());
        else
            return logBigInteger(val.unscaledValue()) - val.scale() * LOG_10;
    }

    /**
     * Computes the exponential function, returning a {@link BigDecimal} (precision ~ 16).
     * <p>
     * Works for very big and very small exponents, even when the result 
     * falls outside the double range.
     *
     * @param exponent Any finite value (infinite or {@code Nan} throws {@code IllegalArgumentException})    
     * @return The value of {@code e} (base of the natural logarithms) raised to the given exponent, 
     * as in {@link java.lang.Math#exp(double)}
     */
    public static BigDecimal expBig(double exponent) {
        if (!Double.isFinite(exponent))
            throw new IllegalArgumentException("Infinite not accepted: " + exponent);
        // e^b = e^(b2+c) = e^b2 2^t with e^c = 2^t 
        double bc = MAX_DIGITS_E;
        if (exponent < bc && exponent > -bc)
            return new BigDecimal(Math.exp(exponent), MathContext.DECIMAL64);
        boolean neg = false;
        if (exponent < 0) {
            neg = true;
            exponent = -exponent;
        }
        double b2 = bc;
        double c = exponent - bc;
        int t = (int) Math.ceil(c / LOG_10);
        c = t * LOG_10;
        b2 = exponent - c;
        if (neg) {
            b2 = -b2;
            t = -t;
        }
        return new BigDecimal(Math.exp(b2), MathContext.DECIMAL64).movePointRight(t);
    }

    /**
     * Same as {@link java.lang.Math#pow(double,double)} but returns a {@link BigDecimal} (precision ~ 16).
     * <p>
     * Works even for outputs that fall outside the {@code double} range.
     * <br>
     * The only limitation is that {@code b * log(a)} cannot exceed the {@code double} range. 
     * 
     * @param a Base. Should be non-negative 
     * @param b Exponent. Should be finite (and non-negative if base is zero)
     * @return Returns the value of the first argument raised to the power of the second argument.
     */
    public static BigDecimal powBig(double a, double b) {
        if (!(Double.isFinite(a) && Double.isFinite(b)))
            throw new IllegalArgumentException(
                    Double.isFinite(b) ? "base not finite: a=" + a : "exponent not finite: b=" + b);
        if (b == 0)
            return BigDecimal.ONE;
        else if (b == 1)
            return BigDecimal.valueOf(a);
        if (a <= 0) {
            if (a == 0) {
                if (b >= 0)
                    return BigDecimal.ZERO;
                else
                    throw new IllegalArgumentException("0**negative = infinite b=" + b);
            } else
                throw new IllegalArgumentException("negative base a=" + a);
        }
        double x = b * Math.log(a);
        if (Math.abs(x) < MAX_DIGITS_E)
            return BigDecimal.valueOf(Math.pow(a, b));
        else
            return expBig(x);
    }

}
person leonbloy    schedule 02.11.2011
comment
В чем разница и особенно преимущество над ответом Питера Лоури? Кажется, что оба они имеют одинаковую точность, не так ли? - person Maarten Bodewes; 26.09.2015
comment
@MaartenBodewes Преимущество объясняется в первом абзаце моего ответа (и в моем комментарии к ответу Питера). Для не очень больших BigInteger они эквивалентны. - person leonbloy; 26.09.2015
comment
Ой, я неправильно истолковал ответ. - person Maarten Bodewes; 26.09.2015
comment
@ahoffer Нет, это не волшебство :-) Немного математики, начиная с log (a) = log (a / 2 ^ k) + k log (2) - person leonbloy; 17.05.2016
comment
У вас есть или вы знаете logBigInteger(BigInteger val), который возвращает Bigdecimal? - person Sathimantha Malalasekera; 02.11.2020
comment
@Cenfracee Думаю, BigDecimal.valueOf(logBigInteger(val)) должно хватить - person leonbloy; 02.11.2020

Мне немного помог Google, но, видимо, вам не нужно напрямую применять журнал к очень большим числам BigInteger, поскольку его можно разбить следующим образом:

928 = 1000 * 0.928
lg 928 = lg 1000 + lg 0.928 = 3 + lg 0.928

Таким образом, ваша проблема сводится к вычислению / приближению логарифмов, которые позволяют произвольно увеличивать точность, возможно, math.stackexchange.com?

person prusswan    schedule 26.07.2011
comment
Это журнал 1000 ПЛЮС, журнал 928 - НЕ РАЗ - person Jim Garrison; 27.09.2019

Преобразуйте его в формат BigDecimal так:

new BigDecimal(val); // where val is a BigInteger  

и журнал вызовов из BigDecimalUtils на нем : D

person Angel O'Sphere    schedule 26.07.2011
comment
где я могу найти BigDecimalUtils? - person wutzebaer; 08.09.2013
comment
Google для этого, есть несколько библиотек с открытым исходным кодом, которые включают такой класс, например на numericalmethod.com - person Angel O'Sphere; 11.09.2013

Насколько точным он вам нужен? Если вам нужно всего 15 цифр точности, вы можете сделать

BigInteger bi =
double log = Math.log(bi.doubleValue());

Это будет работать для значений до 1023 бит. После этого ценность уже не влезала в двойник.

person Peter Lawrey    schedule 26.07.2011
comment
Определение очень точного журнала дает иррациональные числа, требующие бесконечной точности. - person Peter Lawrey; 26.07.2011
comment
На самом деле я должен использовать значение журнала этого большого целого числа как границу для выполнения некоторых других операций ... \ - person RAVITEJA SATYAVADA; 26.07.2011
comment
@user - вы не ответили на вопрос Питера. Насколько точна вам действительно нужна? (Насколько это возможно или очень точные ответы - неразумные ответы.) - person Stephen C; 26.07.2011
comment
Помимо точности, это имеет проблемы для veryBigInteger, которые не подходят для Double (скажем, 13 ^ 333) - person leonbloy; 04.10.2012

Если вы можете использовать Google Guava и вам нужен только журнал с основанием 2 или 10, вы можете использовать методы из _ 1_ класс.

Если вам нужна другая база, вы всегда можете использовать формулу изменения логарифма для преобразования одной из них в нужную.

person Jeff Evans    schedule 25.08.2016
comment
Странно, что в исходном вопросе задавался натуральный логарифм, но затем в последующем комментарии ответили, что основание 2 приемлемо. - person seh; 25.08.2016
comment
Правда, хотя всегда можно воспользоваться заменой базовой формулы. - person Jeff Evans; 14.10.2019