Java double和使用非常小的值

Java double和使用非常小的值,java,double,probability,Java,Double,Probability,我必须存储几个非常低的概率值的乘积(例如,1E-80)。由于下溢,使用原语javadouble将导致零。我不希望值变为零,因为稍后会有一个更大的数字(例如,1E100),它将使值在double可以处理的范围内 因此,我自己创建了一个不同的类(MyDouble),用于保存基本部分和指数部分。在进行计算时,例如乘法,我将基础部分相乘,然后添加指数 该程序使用原语双精度类型速度快。然而,当我使用我自己的类(MyDouble)时,程序非常慢。我认为这是因为我每次都必须创建新的对象来创建简单的操作,而当不

我必须存储几个非常低的概率值的乘积(例如,1E-80)。由于下溢,使用原语javadouble将导致零。我不希望值变为零,因为稍后会有一个更大的数字(例如,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;
    }
}

我相信这将比双精度测试慢很多,但可能一个很大的因素是字符串操作。你能把它去掉,用算术来计算功率吗?即使是递归或迭代算法也可能比转换为字符串来获取数字位更快。

在高性能应用程序中,您需要找到一种将基本信息存储在原语中的方法。在这种情况下,也许您可以将long或其他变量的字节拆分成固定的部分作为基

然后,您可以创建自定义的方法,将multiply long或long作为double。获取表示基和exp的位,并相应地截断

从某种意义上说,你在这里发明了轮子,因为你希望字节码能有效地执行你想要的操作

编辑:


如果你想坚持使用两个变量,你可以修改你的代码,只需要一个数组,这个数组比对象轻得多。此外,您需要删除对任何字符串解析函数的调用。这些操作非常慢。

每次执行乘法时,您都试图解析字符串。为什么不把所有的值计算成一些结构,比如实数和指数部分,作为预计算步骤,然后创建乘法、加法、细分、幂和其他算法呢


您还可以为大/小数字添加标志。我认为您不会在一次计算中同时使用1e100和1e-100(因此您可以简化一些计算),并且可以缩短不同对(大、大、(小、小)、(大、小)的计算时间。

缓慢可能是因为在拆分和字符串合并中创建了中间字符串对象

试试这个:

/**
 * 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);
}
/**
*值=基准*10^功率。
*/
公共类MyDouble{
//用于确定给定双精度是否太小的阈值。
专用静态最终双小ε=1e-8;
私有静态最终双小ε乘数=1e8;
私有静态最终int小ε幂=8;
私人双基地;
私人权力;
公共MyDouble(双基,整数幂){
myBase=base;
我的力量=力量;
}
公共MyDouble(双基)
{
myBase=base;
myPower=0;
调整功率();
}
/**
*如果基值太小,则通过乘以一些数字和来增加基值
*相应地降低功率。
*例如0.000001*10^1=>0.0001*10^8
*/
私人权力
{
//增加基础并降低功率
//如果给定的双精度值小于阈值。
if(myBase<小ε){
myBase=myBase*小ε乘数;
myPower-=小ε幂;
}
}
/**
*此方法将给定值乘以两倍并更新此对象。
*/
公共无效倍增(MYD)
{
myBase*=d.myBase;
myPower+=d.myPower;
调整功率();
}
/**
*此方法将给定的基元双精度值与此对象相乘,并更新
*基础和权力。
*/
公共空间倍增(双d)
{
乘法(新MyDouble(d));
}
@凌驾
公共字符串toString()
{
返回“Base:+myBase+”,Power=“+myPower;
}
/**
*此方法将给定的双精度值相乘并返回MyDouble对象。
*它确保过小的双精度值不会使乘法结果归零。
*/
公共静态MyDouble乘法(双…值)
{
MyDouble结果=新的MyDouble(1);
对于(int i=0;i您可以使用

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

1E-309
1E-309.0
或者如果你使用log10刻度

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

1E-309
1E-309.0

解决这一问题的方法是在日志空间中工作——它使问题变得微不足道。当你说它不工作时,你能详细说明原因吗?概率下溢是概率模型中的一个常见问题,我想我不知道它是以任何其他方式解决的

回想一下,log(a*b)只是log(a)+log(b)。类似地,log(a/b)是log(a)-log(b)。我假设,由于您使用的是导致下溢问题的概率乘和除;日志空间的缺点是,您需要使用特殊例程来计算log(a+b),如果这是你的问题,我可以告诉你


因此,简单的答案是,在日志空间中工作,并在结尾处重新求幂以获得一个人类可读的数字。

为什么不使用?如果我错了,请纠正我,但它不会有相同的问题吗?双e1=1E-309;双e2=1E-300;BigDecimal bd=new BigDecimal(e1*e2);bd=bd.multiply(new BigDecimal(1E300));System.out.println(bd.doubleValue());//给出0@RexRoy-您需要在
BigDecimal
上使用
add()
multiply()
方法,而不仅仅是在
BigDecimal
中包装双精度算术