Java double和使用非常小的值
我必须存储几个非常低的概率值的乘积(例如,1E-80)。由于下溢,使用原语javadouble将导致零。我不希望值变为零,因为稍后会有一个更大的数字(例如,1E100),它将使值在double可以处理的范围内 因此,我自己创建了一个不同的类(MyDouble),用于保存基本部分和指数部分。在进行计算时,例如乘法,我将基础部分相乘,然后添加指数 该程序使用原语双精度类型速度快。然而,当我使用我自己的类(MyDouble)时,程序非常慢。我认为这是因为我每次都必须创建新的对象来创建简单的操作,而当不再需要这些对象时,垃圾收集器必须做大量的工作 我的问题是,你认为我有更好的办法解决这个问题吗?如果没有,有没有办法让我用自己的类(MyDouble)加速程序 [注意:记录日志并随后记录指数并不能解决我的问题] MyDouble类:Java double和使用非常小的值,java,double,probability,Java,Double,Probability,我必须存储几个非常低的概率值的乘积(例如,1E-80)。由于下溢,使用原语javadouble将导致零。我不希望值变为零,因为稍后会有一个更大的数字(例如,1E100),它将使值在double可以处理的范围内 因此,我自己创建了一个不同的类(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
中包装双精度算术