Java double и работает с действительно небольшими значениями

Мне нужно сохранить продукт с несколькими значениями вероятности, которые действительно низки (например, 1E-80). Использование примитивного java double приведет к нулю из-за недостаточного потока. Я не хочу, чтобы значение было равным нулю, потому что позже будет большее число (например, 1E100), которое приведет к значениям в пределах диапазона, который может обрабатывать double.

Итак, я создал другой класс (MyDouble), который работает над сохранением базовой части и частей экспоненты. При выполнении вычислений, например умножения, я умножаю базовые части и добавляю экспоненты.

Программа выполняется с примитивным двойным типом. Однако, когда я использую свой собственный класс (MyDouble), программа работает очень медленно. Я думаю, что это из-за новых объектов, которые я должен каждый раз создавать для создания простых операций, и сборщик мусора должен выполнять большую работу, когда объекты больше не нужны.

Мой вопрос: есть ли лучший способ, по-вашему, я могу решить эту проблему? Если нет, есть ли способ, чтобы ускорить программу с помощью моего собственного класса (MyDouble)?

[Примечание: взятие журнала и последующее взятие экспоненты не решают мою проблему]

Класс MyDouble:

public class MyDouble {
    public MyDouble(double base, int power){
    this.base = base;
    this.power = power;
    }

    public static MyDouble multiply(double... values) {
    MyDouble returnMyDouble = new MyDouble(0);
    double prodBase = 1;
    int prodPower = 0;
    for( double val : values) {
            MyDouble ad = new MyDouble(val);
            prodBase *= ad.base;
            prodPower += ad.power;
        }   
        String newBaseString = "" + prodBase;
        String[] splitted = newBaseString.split("E");   
        double newBase = 0; int newPower = 0;
        if(splitted.length == 2) {
            newBase = Double.parseDouble(splitted[0]);
            newPower = Integer.parseInt(splitted[1]);
        } else {
            newBase = Double.parseDouble(splitted[0]);
            newPower = 0;
        }
        returnMyDouble.base = newBase;
        returnMyDouble.power = newPower + prodPower;        
        return returnMyDouble;
    }
}

Ответ 1

Медленность может быть вызвана промежуточными строковыми объектами, которые создаются в split и string concats.

Попробуйте следующее:

/**
 * value = base * 10 ^ power.
 */

public class MyDouble {

    // Threshold values to determine whether given double is too small or not. 
private static final double SMALL_EPSILON = 1e-8;
private static final double SMALL_EPSILON_MULTIPLIER = 1e8;
private static final int    SMALL_EPSILON_POWER = 8;

private double myBase;
private int    myPower;

public MyDouble(double base, int power){
    myBase  = base;
    myPower = power;
}

public MyDouble(double base) 
{
    myBase  = base;
    myPower = 0;
    adjustPower();
}

/**
 * If base value is too small, increase the base by multiplying with some number and 
 * decrease the power accordingly. 
 * <p> E.g 0.000 000 000 001 * 10^1  => 0.0001 * 10^8  
 */
private void adjustPower()
{
    // Increase the base & decrease the power 
    // if given double value is less than threshold.
    if (myBase < SMALL_EPSILON) {
        myBase = myBase * SMALL_EPSILON_MULTIPLIER;
        myPower -= SMALL_EPSILON_POWER;
    }
}

/**
 * This method multiplies given double and updates this object.
 */
public void multiply(MyDouble d)
{
    myBase  *= d.myBase;
    myPower += d.myPower;
    adjustPower();
}

/**
 * This method multiplies given primitive double value with this object and update the 
 * base and power.
 */
public void multiply(double d)
{
    multiply(new MyDouble(d));
}

@Override
public String toString()
{
    return "Base:" + myBase + ", Power=" + myPower;
}

/**
 * This method multiplies given double values and returns MyDouble object.
 * It make sure that too small double values do not zero out the multiplication result. 
 */
public static MyDouble multiply(double...values) 
{
    MyDouble result = new MyDouble(1);
    for (int i=0; i<values.length; i++) {
        result.multiply(values[i]);
    }
    return result;
}

public static void main(String[] args) {
    MyDouble r = MyDouble.multiply(1e-80, 1e100);
    System.out.println(r);
}

}

Если это все еще медленно для вашей цели, вы можете модифицировать метод multiply(), чтобы напрямую работать с примитивным двойным вместо создания объекта MyDouble.

Ответ 2

То, как это решается, - это работать в пространстве журналов - это тривизирует проблему. Когда вы говорите, что это не работает, можете ли вы дать конкретную информацию о том, почему? Вероятность недостижения является распространенной проблемой в вероятностных моделях, и я не думаю, что когда-либо знал, что она решена каким-либо другим способом.

Вспомним, что log (a * b) - это только log (a) + log (b). Аналогично log (a/b) является log (a) - log (b). Я предполагаю, что, поскольку вы работаете с вероятностями его умножения и деления, которые вызывают проблемы с потоком; недостатком пространства журналов является то, что вам нужно использовать специальные процедуры для вычисления log (a + b), на которые я могу направить вас, если это ваша проблема.

Таким образом, простой ответ заключается в работе в лог-пространстве и повторной экспоненте в конце, чтобы получить читаемый человеком номер.

Ответ 3

Вы пытаетесь разобрать строки при каждом умножении. Почему бы вам не рассчитать все значения в какой-то структуре, как реальную и экспоненциальную, как шаг предварительного расчета, а затем создать алгоритмы для умножения, добавления, разбиения, мощности и других.

Также вы можете добавить флаг для больших/малых чисел. Я думаю, что вы не будете использовать как 1e100, так и 1e-100 за один расчет (чтобы упростить некоторые вычисления), и вы могли бы улучшить время вычисления для разных пар (больших, больших), (малых, малых), (больших, малых).

Ответ 4

Вы можете использовать

BigDecimal bd = BigDecimal.ONE.scaleByPowerOfTen(-309)
        .multiply(BigDecimal.ONE.scaleByPowerOfTen(-300))
        .multiply(BigDecimal.ONE.scaleByPowerOfTen(300));
System.out.println(bd);

печатает

1E-309

Или, если вы используете шкалу log10

double d = -309 + -300 + 300;
System.out.println("1E"+d);

печатает

1E-309.0

Ответ 5

Я уверен, что это будет намного медленнее, чем двойной, но, вероятно, важным фактором будет манипуляция строками. Не могли бы вы избавиться от этого и вычислить власть через арифметику? Даже рекурсивная или итеративная арифметика может быть быстрее, чем преобразование в String для захвата битов числа.

Ответ 6

В высокопроизводительном приложении вы хотите найти способ хранения основной информации в примитивах. В этом случае, возможно, вы можете разделить байты длинной или другой переменной так, чтобы фиксированная часть была базой.

Затем вы можете создавать собственные методы умножения длинными или длинными, как если бы они были двойными. Вы захватываете биты, представляющие базу и exp, и обрезаете соответственно.

В некотором смысле, вы повторно изобретаете колесо здесь, так как вам нужен байт-код, который эффективно выполняет операцию, которую вы ищете.

изменить:

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