背景:我有以下代码将高斯函数计算为无穷级数的和。 Gaussian Function,其最简单的形式是e ^ - (x ^ 2)。 这可以使用泰勒级数展开计算为无穷级数的总和。
因此,e ^ - (x ^ 2)= 1 - (x ^ 2)+(x ^ 4)/ 2! - (x ^ 6)/ 3! ....
public static double expSeries (double x, int n){
double result = 0.0, x0 = 1.0, x1;
result+= x0;
for (int i=1; i<=n; i++){
x1 = x0 * ((x*x)/i);
if (i%2 == 0){
result += x1;
} else {
result -= x1;
}
x0 = x1;
}
return result;
}
作为比较,我使用Math.exp(-(x*x))
来查看我的功能是否正常工作。
该函数似乎适用于x
的低值,但在此之后表现不一致。以下是一些测试用例的输出:
x=1; n=10 Result : 0.3678794642857144 Math.exp: 0.36787944117144233 x=1; n=100 Result : 0.36787944117144245 Math.exp: 0.36787944117144233 x=2; n=100 Result : 0.018315638888733953 Math.exp: 0.01831563888873418 x=3; n=100 Result : 1.234098038990534E-4 Math.exp: 1.2340980408667956E-4 x=4; n=100 Result : 1.1247503313371918E-7 Math.exp: 1.1253517471925912E-7 x=5; n=100 Result : 8.181278981021932E-7 Math.exp: 1.3887943864964021E-11 x=6; n=100 Result : -0.03197975209642004 Math.exp: 2.319522830243569E-16 x=7; n=100 Result : 3.6698962220692825E10 Math.exp: 5.242885663363464E-22
我在这里缺少什么?
答案 0 :(得分:2)
你的算法看起来很好,你可能达到了双精度的极限。
我建议改写exp(x)的泰勒系列而不是exp(-x2)的算法,这对代码来说更直接:
public static double expSeries(double x, int n) {
double term = 1;
double result = term;
for (int i = 1; i <= n; i++) {
term *= x / i;
result += term;
}
return result;
}
如果需要,您可以添加expSeries_X2(x, i) { return expSeries(-x*x, i); }
。
然后我们可以使用BigDecimal
s:
public static double expSeries(double x, int n) {
BigDecimal result = ONE;
BigDecimal term = ONE;
BigDecimal x_ = new BigDecimal(x);
for (int i = 1; i <= n; i++) {
term = term.multiply(x_.divide(BigDecimal.valueOf(i), MathContext.DECIMAL128));
result = result.add(term);
}
return result.doubleValue();
}
它应该返回一个更接近你期望的结果。
答案 1 :(得分:2)
这是浮点数问题的一个很好的教训。
泰勒系列不总是计算函数值的好方法。
查看一般定义here。您通过从特定点a
外推来计算函数的值。在您的情况下,该值为零,因此exp(0) = 1
。从零开始越远,外推越差。所以无论你怎么做,所有推断都是如此。
更糟糕的是,你依赖于非常大号的交替标志来互相取消并给你一些明智的东西。如果x = 7
和e = 2.71....
,数字的大小是2 ^ 49还是3 ^ 49?确实很大。
我认为答案不应该是BigDecimal
。一个更好的想法是准确理解你正在做什么,并找出是否有更好的方法来近似大指数的函数。
高斯在统计学中用于模拟正态分布。如果您将函数参数规范化为Z-score(Z = (x-xmean)/stddev
),您会看到函数下99.9%的区域属于-3 <= Z <= +3
范围(正负三个标准)偏差)。您不可能需要超出该范围的参数。
答案 2 :(得分:0)
我已使用BigDecimal
重写公式:
public static void main(String... args){
for(int i=1;i < 8; ++i){
double l = Math.exp(-(Math.pow(i, 2)));
double r = expSeries(BigDecimal.valueOf(i), 100);
System.out.println( l + " - " + r + " = " + (l - r) );
}
}
public static double expSeries (BigDecimal x, int n){
BigDecimal result = BigDecimal.ONE, x1;
for (int i=1; i<=n; i++){
x1 = x.pow(i*2).divide(new BigDecimal(factorial(BigInteger.valueOf(i))), MathContext.DECIMAL128);
if (i%2 == 0) {
result = result.add(x1);
}
else{
result = result.subtract(x1);
}
}
return result.doubleValue();
}
public static BigInteger factorial (BigInteger num){
if (num.compareTo(BigInteger.ONE) == 0) return num;
return num.multiply(
factorial(num.subtract(BigInteger.ONE)));
}
结果:
0.36787944117144233 - 0.36787944117144233 = 0.0
0.01831563888873418 - 0.01831563888873418 = 0.0
1.2340980408667956E-4 - 1.2340980408667956E-4 = 0.0
1.1253517471925912E-7 - 1.1253517471925912E-7 = 0.0
1.3887943864964021E-11 - 1.3887943997473953E-11 = -1.3250993165605518E-19
2.3195228302435696E-16 - 0.0012040908282411062 = -0.0012040908282408742
5.242885663363464E-22 - 3.6698962251221756E10 = -3.6698962251221756E10
我会说Math.exp会失去精确度,但我并不确定;)