在Java中将double转换为BigDecimal

24
我编写了一个Java程序,用于计算黎曼ζ函数的值。在程序内部,我制作了一个库来计算必要的复杂函数,如atan、cos等。这两个程序中的所有内容都通过doubleBigDecimal数据类型访问。但在计算Zeta函数的大数值时会出现重大问题。
Zeta函数的数值近似参考:

直接在高值处评估这个近似会出现问题,例如当s = (230+30i)时,其具有大的复杂形式。我非常感谢在这里获得关于此的信息here。由于我在adaptiveQuad方法中写错了一些内容,所以S2.minus(S1)的评估会产生错误。
例如,通过这个程序计算Zeta(2+3i)可以生成:
Calculation of the Riemann Zeta Function in the form Zeta(s) = a + ib.

Enter the value of [a] inside the Riemann Zeta Function: 2
Enter the value of [b] inside the Riemann Zeta Function: 3
The value for Zeta(s) is 7.980219851133409E-1 - 1.137443081631288E-1*i
Total time taken is 0.469 seconds.

哪一个是 正确

Zeta(100+0i) 生成

Calculation of the Riemann Zeta Function in the form Zeta(s) = a + ib.

Enter the value of [a] inside the Riemann Zeta Function: 100
Enter the value of [b] inside the Riemann Zeta Function: 0
The value for Zeta(s) is 1.000000000153236E0
Total time taken is 0.672 seconds.

相对于Wolfram,这也是正确的。问题出在被标记为adaptiveQuad的方法内部。 Zeta(230+30i)生成。
Calculation of the Riemann Zeta Function in the form Zeta(s) = a + ib.

Enter the value of [a] inside the Riemann Zeta Function: 230
Enter the value of [b] inside the Riemann Zeta Function: 30
The value for Zeta(s) is 0.999999999999093108519845391615339162047254997503854254342793916541606842461539820124897870147977114468145672577664412128509813042591501204781683860384769321084473925620572315416715721728082468412672467499199310913504362891199180150973087384370909918493750428733837552915328069343498987460727711606978118652477860450744628906250 - 38.005428584222228490409289204403133867487950535704812764806874887805043029499897666636162309572126423385487374863788363786029170239477119910868455777891701471328505006916099918492113970510619110472506796418206225648616641319533972054228283869713393805956289770456519729094756021581247296126093715429306030273437500E-15*i
Total time taken is 1.746 seconds.

虚部与Wolfram相比略有不同。
评估积分的算法被称为自适应求积法,可在此处找到double Java实现here。自适应求积法应用以下方法。
// adaptive quadrature
public static double adaptive(double a, double b) {
    double h = b - a;
    double c = (a + b) / 2.0;
    double d = (a + c) / 2.0;
    double e = (b + c) / 2.0;
    double Q1 = h/6  * (f(a) + 4*f(c) + f(b));
    double Q2 = h/12 * (f(a) + 4*f(d) + 2*f(c) + 4*f(e) + f(b));
    if (Math.abs(Q2 - Q1) <= EPSILON)
        return Q2 + (Q2 - Q1) / 15;
    else
        return adaptive(a, c) + adaptive(c, b);
}

这是我第四次尝试编写该程序

/**************************************************************************
**
**    Abel-Plana Formula for the Zeta Function
**
**************************************************************************
**    Axion004
**    08/16/2015
**
**    This program computes the value for Zeta(z) using a definite integral
**    approximation through the Abel-Plana formula. The Abel-Plana formula
**    can be shown to approximate the value for Zeta(s) through a definite
**    integral. The integral approximation is handled through the Composite
**    Simpson's Rule known as Adaptive Quadrature.
**************************************************************************/

import java.util.*;
import java.math.*;


public class AbelMain5 extends Complex {
    private static MathContext MC = new MathContext(512,
            RoundingMode.HALF_EVEN);
    public static void main(String[] args) {
        AbelMain();
    }

    // Main method
    public static void AbelMain() {
        double re = 0, im = 0;
        double start, stop, totalTime;
        Scanner scan = new Scanner(System.in);
        System.out.println("Calculation of the Riemann Zeta " +
                "Function in the form Zeta(s) = a + ib.");
        System.out.println();
        System.out.print("Enter the value of [a] inside the Riemann Zeta " +
                "Function: ");
        try {
                re = scan.nextDouble();
        }
        catch (Exception e) {
           System.out.println("Please enter a valid number for a.");
        }
        System.out.print("Enter the value of [b] inside the Riemann Zeta " +
                "Function: ");
        try {
                im = scan.nextDouble();
        }
        catch (Exception e) {
           System.out.println("Please enter a valid number for b.");
        }
        start = System.currentTimeMillis();
        Complex z = new Complex(new BigDecimal(re), new BigDecimal(im));
        System.out.println("The value for Zeta(s) is " + AbelPlana(z));
        stop = System.currentTimeMillis();
        totalTime = (double) (stop-start) / 1000.0;
        System.out.println("Total time taken is " + totalTime + " seconds.");
    }

    /**
     * The definite integral for Zeta(z) in the Abel-Plana formula.
         * <br> Numerator = Sin(z * arctan(t))
         * <br> Denominator = (1 + t^2)^(z/2) * (e^(2*pi*t) - 1)
     * @param t - the value of t passed into the integrand.
         * @param z - The complex value of z = a + i*b
     * @return the value of the complex function.
    */
    public static Complex f(double t, Complex z) {
        Complex num = (z.multiply(Math.atan(t))).sin();
        Complex D1 = new Complex(1 + t*t).pow(z.divide(TWO));
        Complex D2 = new Complex(Math.pow(Math.E, 2.0*Math.PI*t) - 1.0);
        Complex den = D1.multiply(D2);
        return num.divide(den, MC);
    }

    /**
     * Adaptive quadrature - See http://www.mathworks.com/moler/quad.pdf
     * @param a - the lower bound of integration.
         * @param b - the upper bound of integration.
         * @param z - The complex value of z = a + i*b
     * @return the approximate numerical value of the integral.
    */
    public static Complex adaptiveQuad(double a, double b, Complex z) {
        double EPSILON = 1E-10;
        double step = b - a;
        double c = (a + b) / 2.0;
        double d = (a + c) / 2.0;
        double e = (b + c) / 2.0;

        Complex S1 = (f(a, z).add(f(c, z).multiply(FOUR)).add(f(b, z))).
                multiply(step / 6.0);
        Complex S2 = (f(a, z).add(f(d, z).multiply(FOUR)).add(f(c, z).multiply
                (TWO)).add(f(e, z).multiply(FOUR)).add(f(b, z))).multiply
                (step / 12.0);
        Complex result = (S2.subtract(S1)).divide(FIFTEEN, MC);

        if(S2.subtract(S1).mod() <= EPSILON)
            return S2.add(result);
        else
            return adaptiveQuad(a, c, z).add(adaptiveQuad(c, b, z));
    }

    /**
     * The definite integral for Zeta(z) in the Abel-Plana formula.
         * <br> value =  1/2 + 1/(z-1) + 2 * Integral
         * @param z - The complex value of z = a + i*b
     * @return the value of Zeta(z) through value and the
         * quadrature approximation.
    */
    public static Complex AbelPlana(Complex z) {
        Complex C1 = ONEHALF.add(ONE.divide(z.subtract(ONE), MC));
        Complex C2  =  TWO.multiply(adaptiveQuad(1E-16, 100.0, z));
        if ( z.real().doubleValue() == 0 && z.imag().doubleValue() == 0)
            return new Complex(0.0, 0.0);
        else
            return C1.add(C2);
    }

}

复数(BigDecimal

/**************************************************************************
**
**    Complex Numbers
**
**************************************************************************
**    Axion004
**    08/20/2015
**
**    This class is necessary as a helper class for the calculation of
**    imaginary numbers. The calculation of Zeta(z) inside AbelMain is in
**    the form of z = a + i*b.
**************************************************************************/

import java.math.BigDecimal;
import java.math.MathContext;
import java.text.DecimalFormat;
import java.text.NumberFormat;

public class Complex extends Object{
    private BigDecimal re;
    private BigDecimal im;

    /**
        BigDecimal constant for zero
    */
    final static Complex ZERO = new Complex(BigDecimal.ZERO) ;

    /**
        BigDecimal constant for one half
    */
    final static Complex ONEHALF = new Complex(new BigDecimal(0.5));

    /**
        BigDecimal constant for one
    */
    final static Complex ONE = new Complex(BigDecimal.ONE);

    /**
        BigDecimal constant for two
    */
    final static Complex TWO = new Complex(new BigDecimal(2.0));

    /**
        BigDecimal constant for four
    */
    final static Complex FOUR = new Complex(new BigDecimal(4.0)) ;

    /**
        BigDecimal constant for fifteen
    */
    final static Complex FIFTEEN = new Complex(new BigDecimal(15.0)) ;

    /**
        Default constructor equivalent to zero
    */
    public Complex() {
        re = BigDecimal.ZERO;
        im = BigDecimal.ZERO;
    }

    /**
        Constructor with real part only
        @param x Real part, BigDecimal
    */
    public Complex(BigDecimal x) {
        re = x;
        im = BigDecimal.ZERO;
    }

    /**
        Constructor with real part only
        @param x Real part, double
    */
    public Complex(double x) {
        re = new BigDecimal(x);
        im = BigDecimal.ZERO;
    }

    /**
        Constructor with real and imaginary parts in double format.
        @param x Real part
        @param y Imaginary part
    */
    public Complex(double x, double y) {
        re= new BigDecimal(x);
        im= new BigDecimal(y);
    }

    /**
        Constructor for the complex number z = a + i*b
        @param re Real part
        @param im Imaginary part
    */
    public Complex (BigDecimal re, BigDecimal im) {
        this.re = re;
        this.im = im;
    }

    /**
        Real part of the Complex number
        @return Re[z] where z = a + i*b.
    */
    public BigDecimal real() {
        return re;
    }

    /**
        Imaginary part of the Complex number
        @return Im[z] where z = a + i*b.
    */
    public BigDecimal imag() {
        return im;
    }

    /**
        Complex conjugate of the Complex number
        in which the conjugate of z is z-bar.
        @return z-bar where z = a + i*b and z-bar = a - i*b
    */
    public Complex conjugate() {
        return new Complex(re, im.negate());
    }

    /**
     * Returns the sum of this and the parameter.

       @param augend the number to add
       @param mc the context to use
       @return this + augend
     */
    public Complex add(Complex augend,MathContext mc)
    {
        //(a+bi)+(c+di) = (a + c) + (b + d)i
        return new Complex(
            re.add(augend.re,mc),
            im.add(augend.im,mc));
    }

    /**
       Equivalent to add(augend, MathContext.UNLIMITED)
       @param augend the number to add
       @return this + augend
     */
    public Complex add(Complex augend)
    {
        return add(augend, MathContext.UNLIMITED);
    }

    /**
        Addition of Complex number and a double.
        @param d is the number to add.
        @return z+d where z = a+i*b and d = double
    */
    public Complex add(double d){
        BigDecimal augend = new BigDecimal(d);
        return new Complex(this.re.add(augend, MathContext.UNLIMITED),
                this.im);
    }

    /**
     * Returns the difference of this and the parameter.
       @param subtrahend the number to subtract
       @param mc the context to use
       @return this - subtrahend
     */
    public Complex subtract(Complex subtrahend, MathContext mc)
    {
        //(a+bi)-(c+di) = (a - c) + (b - d)i
        return new Complex(
            re.subtract(subtrahend.re,mc),
            im.subtract(subtrahend.im,mc));
    }

    /**
     * Equivalent to subtract(subtrahend, MathContext.UNLIMITED)
       @param subtrahend the number to subtract
       @return this - subtrahend
     */
    public Complex subtract(Complex subtrahend)
    {
        return subtract(subtrahend,MathContext.UNLIMITED);
    }

    /**
        Subtraction of Complex number and a double.
        @param d is the number to subtract.
        @return z-d where z = a+i*b and d = double
    */
    public Complex subtract(double d){
        BigDecimal subtrahend = new BigDecimal(d);
        return new Complex(this.re.subtract(subtrahend, MathContext.UNLIMITED),
                this.im);
    }

    /**
     * Returns the product of this and the parameter.
       @param multiplicand the number to multiply by
       @param mc the context to use
       @return this * multiplicand
     */
    public Complex multiply(Complex multiplicand, MathContext mc)
    {
        //(a+bi)(c+di) = (ac - bd) + (ad + bc)i
        return new Complex(
            re.multiply(multiplicand.re,mc).subtract(im.multiply
                (multiplicand.im,mc),mc),
            re.multiply(multiplicand.im,mc).add(im.multiply
                (multiplicand.re,mc),mc));
    }

    /**
       Equivalent to multiply(multiplicand, MathContext.UNLIMITED)
       @param multiplicand the number to multiply by
       @return this * multiplicand
     */
    public Complex multiply(Complex multiplicand)
    {
        return multiply(multiplicand,MathContext.UNLIMITED);
    }

    /**
        Complex multiplication by a double.
        @param d is the double to multiply by.
        @return z*d where z = a+i*b and d = double
    */
    public Complex multiply(double d){
        BigDecimal multiplicand = new BigDecimal(d);
        return new Complex(this.re.multiply(multiplicand, MathContext.UNLIMITED)
                ,this.im.multiply(multiplicand, MathContext.UNLIMITED));
    }

    /**
        Modulus of a Complex number or the distance from the origin in
        * the polar coordinate plane.
        @return |z| where z = a + i*b.
    */
    public double mod() {
        if ( re.doubleValue() != 0.0 || im.doubleValue() != 0.0)
            return Math.sqrt(re.multiply(re).add(im.multiply(im))
                    .doubleValue());
        else
            return 0.0;
    }

    /**
     * Modulus of a Complex number squared
     * @param z = a + i*b
     * @return |z|^2 where z = a + i*b
    */
    public double abs(Complex z) {
        double doubleRe = re.doubleValue();
        double doubleIm = im.doubleValue();
        return doubleRe * doubleRe + doubleIm * doubleIm;
    }

    public Complex divide(Complex divisor)
    {
        return divide(divisor,MathContext.UNLIMITED);
    }

    /**
        * The absolute value squared.
        * @return The sum of the squares of real and imaginary parts.
        * This is the square of Complex.abs() .
        */
    public BigDecimal norm()
    {
                return re.multiply(re).add(im.multiply(im)) ;
    }

    /**
        * The absolute value of a BigDecimal.
        * @param mc amount of precision
        * @return BigDecimal.abs()
        */
    public BigDecimal abs(MathContext mc)
    {
                return BigDecimalMath.sqrt(norm(),mc) ;
    }

    /** The inverse of the the Complex number.
          @param mc amount of precision
          @return 1/this
        */
    public Complex inverse(MathContext mc)
    {
                final BigDecimal hyp = norm() ;
                /* 1/(x+iy)= (x-iy)/(x^2+y^2 */
                return new Complex( re.divide(hyp,mc), im.divide(hyp,mc)
                        .negate() ) ;
    }

    /** Divide through another BigComplex number.
        @param oth the other complex number
        @param mc amount of precision
        @return this/other
        */
    public Complex divide(Complex oth, MathContext mc)
    {
                /* implementation: (x+iy)/(a+ib)= (x+iy)* 1/(a+ib) */
                return multiply(oth.inverse(mc),mc) ;
    }

   /**
        Division of Complex number by a double.
        @param d is the double to divide
        @return new Complex number z/d where z = a+i*b
    */
    public Complex divide(double d){
        BigDecimal divisor = new BigDecimal(d);
        return new Complex(this.re.divide(divisor, MathContext.UNLIMITED),
                this.im.divide(divisor, MathContext.UNLIMITED));
    }

    /**
        Exponential of a complex number (z is unchanged).
        <br> e^(a+i*b) = e^a * e^(i*b) = e^a * (cos(b) + i*sin(b))
        @return exp(z) where z = a+i*b
    */
    public Complex exp () {
        return new Complex(Math.exp(re.doubleValue()) * Math.cos(im.
                doubleValue()), Math.exp(re.doubleValue()) *
                Math.sin(im.doubleValue()));
    }

     /**
        The Argument of a Complex number or the angle in radians
        with respect to polar coordinates.
        <br> Tan(theta) = b / a, theta = Arctan(b / a)
        <br> a is the real part on the horizontal axis
        <br> b is the imaginary part of the vertical axis
        @return arg(z) where z = a+i*b.
    */
    public double arg() {
        return Math.atan2(im.doubleValue(), re.doubleValue());
    }

    /**
        The log or principal branch of a Complex number (z is unchanged).
        <br> Log(a+i*b) = ln|a+i*b| + i*Arg(z) = ln(sqrt(a^2+b^2))
        * + i*Arg(z) = ln (mod(z)) + i*Arctan(b/a)
        @return log(z) where z = a+i*b
    */
    public Complex log() {
        return new Complex(Math.log(this.mod()), this.arg());
    }

    /**
        The square root of a Complex number (z is unchanged).
        Returns the principal branch of the square root.
        <br> z = e^(i*theta) = r*cos(theta) + i*r*sin(theta)
        <br> r = sqrt(a^2+b^2)
        <br> cos(theta) = a / r, sin(theta) = b / r
        <br> By De Moivre's Theorem, sqrt(z) = sqrt(a+i*b) =
        * e^(i*theta / 2) = r(cos(theta/2) + i*sin(theta/2))
        @return sqrt(z) where z = a+i*b
    */
    public Complex sqrt() {
        double r = this.mod();
        double halfTheta = this.arg() / 2;
        return new Complex(Math.sqrt(r) * Math.cos(halfTheta), Math.sqrt(r) *
                Math.sin(halfTheta));
    }

    /**
        The real cosh function for Complex numbers.
        <br> cosh(theta) = (e^(theta) + e^(-theta)) / 2
        @return cosh(theta)
    */
    private double cosh(double theta) {
        return (Math.exp(theta) + Math.exp(-theta)) / 2;
    }

    /**
        The real sinh function for Complex numbers.
        <br> sinh(theta) = (e^(theta) - e^(-theta)) / 2
        @return sinh(theta)
    */
    private double sinh(double theta) {
        return (Math.exp(theta) - Math.exp(-theta)) / 2;
    }

     /**
        The sin function for the Complex number (z is unchanged).
        <br> sin(a+i*b) = cosh(b)*sin(a) + i*(sinh(b)*cos(a))
        @return sin(z) where z = a+i*b
    */
    public Complex sin() {
        return new Complex(cosh(im.doubleValue()) * Math.sin(re.doubleValue()),
                sinh(im.doubleValue())* Math.cos(re.doubleValue()));
    }

    /**
        The cos function for the Complex number (z is unchanged).
        <br> cos(a +i*b) = cosh(b)*cos(a) + i*(-sinh(b)*sin(a))
        @return cos(z) where z = a+i*b
    */
    public Complex cos() {
        return new Complex(cosh(im.doubleValue()) * Math.cos(re.doubleValue()),
                -sinh(im.doubleValue()) * Math.sin(re.doubleValue()));
    }

    /**
        The hyperbolic sin of the Complex number (z is unchanged).
        <br> sinh(a+i*b) = sinh(a)*cos(b) + i*(cosh(a)*sin(b))
        @return sinh(z) where z = a+i*b
    */
    public Complex sinh() {
        return new Complex(sinh(re.doubleValue()) * Math.cos(im.doubleValue()),
                cosh(re.doubleValue()) * Math.sin(im.doubleValue()));
    }

    /**
        The hyperbolic cosine of the Complex number (z is unchanged).
        <br> cosh(a+i*b) = cosh(a)*cos(b) + i*(sinh(a)*sin(b))
        @return cosh(z) where z = a+i*b
    */
    public Complex cosh() {
        return new Complex(cosh(re.doubleValue()) *Math.cos(im.doubleValue()),
                sinh(re.doubleValue()) * Math.sin(im.doubleValue()));
    }

    /**
        The tan of the Complex number (z is unchanged).
        <br> tan (a+i*b) = sin(a+i*b) / cos(a+i*b)
        @return tan(z) where z = a+i*b
    */
    public Complex tan() {
        return (this.sin()).divide(this.cos());
    }

    /**
        The arctan of the Complex number (z is unchanged).
        <br> tan^(-1)(a+i*b) = 1/2 i*(log(1-i*(a+b*i))-log(1+i*(a+b*i))) =
        <br> -1/2 i*(log(i*a - b+1)-log(-i*a + b+1))
        @return arctan(z) where z = a+i*b
    */
    public Complex atan(){
        Complex ima = new Complex(0.0,-1.0);    //multiply by negative i
        Complex num = new Complex(this.re.doubleValue(),this.im.doubleValue()
                -1.0);
        Complex den = new Complex(this.re.negate().doubleValue(),this.im
                .negate().doubleValue()-1.0);
        Complex two = new Complex(2.0, 0.0);    // divide by 2
        return ima.multiply(num.divide(den).log()).divide(two);
    }

    /**
     * The Math.pow equivalent of two Complex numbers.
     * @param z - the complex base in the form z = a + i*b
     * @return z^y where z = a + i*b and y = c + i*d
    */
    public Complex pow(Complex z){
        Complex a = z.multiply(this.log(), MathContext.UNLIMITED);
        return a.exp();
    }

    /**
     * The Math.pow equivalent of a Complex number to the power
         * of a double.
     * @param d - the double to be taken as the power.
     * @return z^d where z = a + i*b and d = double
    */
     public Complex pow(double d){
         Complex a=(this.log()).multiply(d);
         return a.exp();
     }

    /**
        Override the .toString() method to generate complex numbers, the
        * string representation is now a literal Complex number.
        @return a+i*b, a-i*b, a, or i*b as desired.
    */
    public String toString() {

        NumberFormat formatter = new DecimalFormat();
        formatter = new DecimalFormat("#.###############E0");

        if (re.doubleValue() != 0.0 && im.doubleValue()  > 0.0) {
            return formatter.format(re) + " + " + formatter.format(im)
                    +"*i";
        }
        if (re.doubleValue() !=0.0 && im.doubleValue() < 0.0) {
            return formatter.format(re) + " - "+ formatter.format(im.negate())
                    + "*i";
        }
        if (im.doubleValue() == 0.0) {
            return formatter.format(re);
        }
        if (re.doubleValue() == 0.0) {
            return formatter.format(im) + "*i";
        }
        return formatter.format(re) + " + i*" + formatter.format(im);
    }
}

我正在审查下面的答案。 其中一个问题可能是由于
Complex num = (z.multiply(Math.atan(t))).sin();
Complex D1 = new Complex(1 + t*t).pow(z.divide(TWO));
Complex D2 = new Complex(Math.pow(Math.E, 2.0*Math.PI*t) - 1.0);
Complex den = D1.multiply(D2, MathContext.UNLIMITED);

我并没有使用BigDecimal.pow(BigDecimal)。尽管如此,我认为这不是导致浮点数算术产生差异的直接问题。 编辑: 我尝试了Zeta函数的新积分逼近。最终,我将开发一种计算BigDecimal.pow(BigDecimal)的新方法。

谢谢Laune,我明白为什么这不是必要的。我正在寻找一种方法来调整程序,而不必将所有内容都更改为BigDecimal。 - Axion004
1
比较 Zeta(2+3i) 和 Zeta(100+0i) 的结果和时间(!),让我觉得自适应积分方法根本不好。为什么第二次积分在0.005秒后停止?显然,远离的6.402很快就被生成,表明下一步产生的值大约相同。二次积分要求函数“良好行为”,而对于较大的a值,这个函数显然不是这样的。 - laune
这个(新的)Complex类,使用BigDecimal:你真的认为在使用只有15位有效数字的double调用所有Math函数时,进行高精度计算会有所帮助吗? - laune
1
你使用这种特定的近似方法来计算 zeta(s) 有什么原因吗?在我看来,还有更简单的数值方法可供选择。 - J Richard Snape
1
@Axion004 请看下面的Java代码。我已经将其运行在230 +30i上,精度为120位数字,1e-30 epsilon和1e-80作为积分的零限制。我会告诉你它输出了什么。 - J Richard Snape
显示剩余11条评论
3个回答

6

注意我同意@laune的回答中的所有评论,但我觉得您可能仍然希望继续下去。请确保您真正理解了1)并明白它对您意味着什么-很容易进行大量复杂计算以产生无意义的结果。


Java中的任意精度浮点函数

再重申一下,我认为您的问题实际上是与您选择的数学和数值方法有关,但是这里提供了使用Apfloat库的实现。我强烈建议您使用现成的任意精度库(或类似的库),因为它避免了您需要“自己编写”任意精度数学函数(例如powexpsinatan等)。您说

最终,我将开发一种新方法来计算BigDecimal.pow(BigDecimal)

这真的很难做到正确。

您还需要注意常数的精度-请注意,我使用Apfloat示例实现来计算PI到一定数量(对于某些定义来说是大量!)的有效数字。我在某种程度上相信Apfloat库在指数运算中使用了足够精确的e值-如果您想要检查,可以查看源代码。


不同的积分公式计算zeta函数

您在其中一个编辑中提供了三种不同的基于积分的方法:enter image description here
编号为25.5.12的是您目前在问题中使用的方法(尽管可以轻松地在零处进行计算),但由于@laune的回答中的2),它很难处理。我通过integrand1()实现了25.5.12的内容-我建议您使用不同的t范围和不同的s = a + 0i绘制它,并了解其行为。或者查看Wolfram的mathworld中关于zeta文章中的图表。编号为25.5.11的我通过integrand2()实现,代码如下。


代码

虽然我有点不愿意发布代码,因为它可能会因为上述所有问题导致错误的结果 - 但是我已经编码了您想要做的事情,使用任意精度浮点对象作为变量。

如果您想更改使用的公式(例如从25.5.11更改为25.5.12),则可以更改包装函数f()返回的积分被积函数,或者更好地将adaptiveQuad更改为接受封装在带有接口的class中的任意被积函数方法……如果要使用其他积分公式之一,则还必须更改findZeta()中的算术运算。

尽情玩弄开头的常数。我没有测试很多组合,因为我认为这里的数学问题超过了编程问题。

我将其设置为大约2000次调用自适应积分方法并匹配Wolfram值的前15位左右来执行2+3i

我已经测试它仍然适用于PRECISION = 120lEPSILON=1e-15。该程序与您提供的三个测试用例中的Wolfram Alpha第18位或左右的显着数字相匹配。最后一个(230+30i)甚至在快速计算机上也需要很长时间 - 它调用函数积分函数超过100,000次。请注意,我在积分中将40用作INFINITY的值-不是很高-但更高的值会出现已经讨论过的问题1)……

注意:这并不快速(您将测量分钟或小时,而不是秒数 - 但只要您愿意接受10^-15 ~= 10^-70的结果,就可以得到真正的快速效果!)。它将为您提供与Wolfram Alpha相匹配的一些数字;)您可能希望将PRECISION降至约20,将INFINITY降至10,将EPSILON降至1e-10,以先使用小的s验证一些结果……我保留了一些打印功能,以便每第100次告诉您adaptiveQuad被调用的舒适度。

再次强调:无论您的精确度有多好-都无法克服计算zeta所涉及的函数的数学特性。例如,我非常怀疑这是Wolfram Alpha的计算方法。如果您想要更易处理的方法,请查找级数求和方法。


import java.io.PrintWriter;
import org.apfloat.ApcomplexMath;
import org.apfloat.Apcomplex;
import org.apfloat.Apfloat;
import org.apfloat.samples.Pi;

public class ZetaFinder
{
    //Number of sig figs accuracy.  Note that infinite should be reserved
    private static long PRECISION = 40l; 
    // Convergence criterion for integration
    static Apfloat EPSILON = new Apfloat("1e-15",PRECISION);

    //Value of PI - enhanced using Apfloat library sample calculation of Pi in constructor,
    //Fast enough that we don't need to hard code the value in.
    //You could code hard value in for perf enhancement
    static Apfloat PI = null; //new Apfloat("3.14159"); 

    //Integration limits - I found too high a value for "infinity" causes integration
    //to terminate on first iteration.  Plot the integrand to see why...
    static Apfloat INFINITE_LIMIT = new Apfloat("40",PRECISION);
    static Apfloat ZERO_LIMIT = new Apfloat("1e-16",PRECISION); //You can use zero for the 25.5.12

    static Apfloat one = new Apfloat("1",PRECISION);
    static Apfloat two = new Apfloat("2",PRECISION);
    static Apfloat four = new Apfloat("4",PRECISION);
    static Apfloat six = new Apfloat("6",PRECISION);
    static Apfloat twelve = new Apfloat("12",PRECISION);
    static Apfloat fifteen = new Apfloat("15",PRECISION);

    static int counter = 0;

    Apcomplex s = null;

    public ZetaFinder(Apcomplex s)
    {
        this.s = s;
        Pi.setOut(new PrintWriter(System.out, true));
        Pi.setErr(new PrintWriter(System.err, true));
        PI = (new Pi.RamanujanPiCalculator(PRECISION+10, 10)).execute(); //Get Pi to a higher precision than integer consts
        System.out.println("Created a Zeta Finder based on Abel-Plana for s="+s.toString() + " using PI="+PI.toString());
    }

    public static void main(String[] args)
    {
        Apfloat re = new Apfloat("2", PRECISION);
        Apfloat im = new Apfloat("3", PRECISION);
        Apcomplex s = new Apcomplex(re,im);

        ZetaFinder finder = new ZetaFinder(s);

        System.out.println(finder.findZeta());
    }

    private Apcomplex findZeta()
    {
        Apcomplex retval = null;

        //Method currently in question (a.k.a. 25.5.12)
        //Apcomplex mult = ApcomplexMath.pow(two, this.s);
        //Apcomplex firstterm = (ApcomplexMath.pow(two, (this.s.add(one.negate())))).divide(this.s.add(one.negate()));

        //Easier integrand method (a.k.a. 25.5.11)
        Apcomplex mult = two;
        Apcomplex firstterm = (one.divide(two)).add(one.divide(this.s.add(one.negate())));

        Apfloat limita = ZERO_LIMIT;//Apfloat.ZERO;
        Apfloat limitb = INFINITE_LIMIT;
        System.out.println("Trying to integrate between " + limita.toString() + " and " + limitb.toString());
        Apcomplex integral = adaptiveQuad(limita, limitb);
        retval = firstterm.add((mult.multiply(integral)));
        return retval;
    }

    private Apcomplex adaptiveQuad(Apfloat a, Apfloat b) {
        //if (counter % 100 == 0)
        {
            System.out.println("In here for the " + counter + "th time");
        }
        counter++;
        Apfloat h = b.add(a.negate());
        Apfloat c = (a.add(b)).divide(two);
        Apfloat d = (a.add(c)).divide(two);
        Apfloat e = (b.add(c)).divide(two);

        Apcomplex Q1 = (h.divide(six)).multiply(f(a).add(four.multiply(f(c))).add(f(b)));
        Apcomplex Q2 = (h.divide(twelve)).multiply(f(a).add(four.multiply(f(d))).add(two.multiply(f(c))).add(four.multiply(f(e))).add(f(b)));
        if (ApcomplexMath.abs(Q2.add(Q1.negate())).compareTo(EPSILON) < 0)
        {
            System.out.println("Returning");
            return Q2.add((Q2.add(Q1.negate())).divide(fifteen));
        }
        else
        {
            System.out.println("Recursing with intervals "+a+" to " + c + " and " + c + " to " +d);
            return adaptiveQuad(a, c).add(adaptiveQuad(c, b));
        }
    }

    private Apcomplex f(Apfloat x)
    {
        return integrand2(x);
    }

    /*
     * Simple test integrand (z^2)
     * 
     * Can test implementation by asserting that the adaptiveQuad
     * with this function evaluates to z^3 / 3
     */
    private Apcomplex integrandTest(Apfloat t)
    {
        return ApcomplexMath.pow(t, two);
    }

    /*
     * Abel-Plana formulation integrand
     */
    private Apcomplex integrand1(Apfloat t)
    {
        Apcomplex numerator = ApcomplexMath.sin(this.s.multiply(ApcomplexMath.atan(t)));
        Apcomplex bottomlinefirstbr = one.add(ApcomplexMath.pow(t, two));
        Apcomplex D1 = ApcomplexMath.pow(bottomlinefirstbr, this.s.divide(two));
        Apcomplex D2 = (ApcomplexMath.exp(PI.multiply(t))).add(one);    
        Apcomplex denominator = D1.multiply(D2);
        Apcomplex retval = numerator.divide(denominator);       
        //System.out.println("Integrand evaluated at "+t+ " is "+retval);
        return retval;
    }

    /*
     * Abel-Plana formulation integrand 25.5.11 
     */
    private Apcomplex integrand2(Apfloat t)
    {
        Apcomplex numerator = ApcomplexMath.sin(this.s.multiply(ApcomplexMath.atan(t)));
        Apcomplex bottomlinefirstbr = one.add(ApcomplexMath.pow(t, two));
        Apcomplex D1 = ApcomplexMath.pow(bottomlinefirstbr, this.s.divide(two));
        Apcomplex D2 = ApcomplexMath.exp(two.multiply(PI.multiply(t))).add(one.negate());    
        Apcomplex denominator = D1.multiply(D2);
        Apcomplex retval = numerator.divide(denominator);       
        //System.out.println("Integrand evaluated at "+t+ " is "+retval);
        return retval;
    }
}

关于“正确性”的注释

请注意,在你的答案中,将zeta(2+3i)zeta(100)与Wolfram相比,“正确”,因为它们的误差分别为约1e-10和约1e-9(它们在第10位和第9位小数处不同),但你对zeta(230 + 30i)感到担心,因为它在虚部中显示出10e-14级别的误差(38e-155e-70都非常接近零)。因此,在某些意义上,你称之为“错误”的那个值比你称之为“正确”的那个更接近Wolfram值。也许你担心前导数字不同,但这并不是准确度的衡量标准。


最后的说明

除非你这样做是为了学习函数的行为以及浮点精度如何与之交互,否则不要这样做。即使Apfloat自己的文档也说:

这个软件包是为极端精度而设计的。结果可能比您预期的要少几个数字(约10个),并且结果中的最后几个数字(约10个)可能不准确。如果您计划使用只有几百个数字的数,可以使用类似PARI(它是免费的,并可从ftp://megrez.math.u-bordeaux.fr获得)或商业程序,例如Mathematica或Maple。

现在,我会添加Python中的mpmath到这个列表中,作为一种免费的替代方案。


你提供了一个出色的答案。有时候,当你做错事情时,你会学到更多。我正在消化你们两个的答案(我看到了我忽略了laune建议的问题#1所犯的巨大错误)。我打算花费这个周末去经历各种情况,非常感谢你们的帮助。 - Axion004

5

(1) 这个积分使用了adaptQuad算法,起始区间为[0,10]。当z=a+ib时,随着a和b=0取值越来越大,被积函数会变得越来越震荡,仅在[0,5]内的零点数量就与a成比例,并且对于z=100,这个数量会上升到43。

因此,从包含一个或多个零点的区间开始进行近似是有风险的,正如程序所示。对于z=100,被积函数在0、5和10处分别为0、-2.08E-78和7.12E-115。因此,将Simpson公式的结果与1E-20进行比较会返回true,而结果是完全错误的。

(2) AbelPlana方法中的计算涉及两个复数C1和C2。当z=a+0i时,它们是实数,下表显示了它们在各种a值下的取值:

  a    C1          C2
 10    5.689E1     1.024E3
 20    2.759E4     1.048E6
 30    1.851E7     1.073E9
 40    1.409E10    1.099E12
 60    9.770E15    1.152E18
100    6.402E27    1.267E30

现在我们知道,ζ(a+0i)的值随着a的增加而逐渐接近1。当两个大于1E15的值相减时,不可能产生接近1的有意义的结果。
此外,表格还表明,为了使用这个算法得到ζ(a+0i)的良好结果,需要以约45个有效数字的精度计算C1和C2*I(其中I是积分)。任意精度数学无法避免(1)中描述的问题。
注意,在使用具有任意精度的库时,应该提供比java.lang.Math中的双精度值更好的精度,如E和PI等值。
编辑(25.5.11)在[0,10]范围内有与(25.5.12)相同数量的零。在计算0时需要小心,但它不是奇点。它避免了问题(2)。

非常好的信息,Laune。由于#1的原因,这可能比我最初想象的更困难。 - Axion004
我正在应用adaptiveQuad(0, 10.0, z)来近似计算从0到无穷的积分。 - Axion004
这对我来说非常明显。被积函数渐近地、而且快速地趋近于零。 - laune
新程序看起来更好,但由于使用divide(BigDecimal, MathContext.DECIMAL128)计算而导致出现了新问题。 - Axion004
每当除法结束时出现非终止小数扩展时,这是可以预料的;例如,1除以3。需要进行分析,确定所需的小数位数,并使用适当的上下文。 - laune
是的!https://dev59.com/LV3Va4cB1Zd3GeqPA3_8 - Axion004

1

如果您想了解如何在OP中使用任意精度算术,可以参考我的其他答案

然而,我对此产生了兴趣,并认为级数求和方法应该更加稳定。我在维基百科上找到了狄利克雷级数表示法并实现了它(下面是完整可运行的代码)。

这给了我一个有趣的见解。如果我将收敛值EPSILON设置为1e-30,则我得到与Wolfram相同的数字和指数(即虚部为1e-70以及算法仅在添加1或2个项到总和后终止。这向我暗示了两件事:

  1. Wolfram alpha使用这种求和方法或类似方法来计算返回的值。
  2. 这些值的“正确性”很难评估。例如,zeta(100)以PI为基准有一个精确的值,因此可以进行判断。我不知道这个估计值 zeta(230+30i) 是比积分方法找到的估计值更好还是更差。
  3. 这种方法收敛到zeta(2+3i)非常缓慢,可能需要将EPSILON降低才能使用。

我还发现了一篇学术论文,其中包含了计算zeta的数值方法。这表明这里的根本问题肯定是“非平凡的”!

无论如何,我在这里留下了级数求和实现作为未来可能遇到的替代方案。

import java.io.PrintWriter;

import org.apfloat.ApcomplexMath;
import org.apfloat.Apcomplex;
import org.apfloat.Apfloat;
import org.apfloat.ApfloatMath;
import org.apfloat.samples.Pi;

public class ZetaSeries {

    //Number of sig figs accuracy.  Note that infinite should be reserved
    private static long PRECISION = 100l; 
    // Convergence criterion for integration
    static Apfloat EPSILON = new Apfloat("1e-30",PRECISION);

    static Apfloat one = new Apfloat("1",PRECISION);
    static Apfloat two = new Apfloat("2",PRECISION);
    static Apfloat minus_one = one.negate();
    static Apfloat three = new Apfloat("3",PRECISION);

    private Apcomplex s = null;
    private Apcomplex s_plus_two = null;


    public ZetaSeries(Apcomplex s) {
        this.s = s;
        this.s_plus_two = two.add(s);
    }

    public static void main(String[] args) {

        Apfloat re = new Apfloat("230", PRECISION);
        Apfloat im = new Apfloat("30", PRECISION);
        Apcomplex s = new Apcomplex(re,im);
        ZetaSeries z = new ZetaSeries(s);
        System.out.println(z.findZeta());
    }

    private Apcomplex findZeta() {

        Apcomplex series_sum = Apcomplex.ZERO;
        Apcomplex multiplier = (one.divide(this.s.add(minus_one)));
        int stop_condition = 1;
        long n = 1;
        while (stop_condition > 0)
        {
            Apcomplex term_to_add = sum_term(n);
            stop_condition = ApcomplexMath.abs(term_to_add).compareTo(EPSILON);
            series_sum = series_sum.add(term_to_add);
            //if(n%50 == 0)
            {
                System.out.println("At iteration " + n + " : " + multiplier.multiply(series_sum));
            }
            n+=1;
        }
        return multiplier.multiply(series_sum);
    }

    private Apcomplex sum_term(long n_long) {
        Apfloat n = new Apfloat(n_long, PRECISION);
        Apfloat n_plus_one = n.add(one);
        Apfloat two_n = two.multiply(n);

        Apfloat t1 = (n.multiply(n_plus_one)).divide(two);
        Apcomplex t2 = (two_n.add(three).add(this.s)).divide(ApcomplexMath.pow(n_plus_one,s_plus_two));
        Apcomplex t3 = (two_n.add(minus_one).add(this.s.negate())).divide(ApcomplexMath.pow(n,this.s_plus_two));
        return t1.multiply(t2.add(t3.negate()));
    }

}

这可以通过函数方程扩展到负值,http://arxiv.org/pdf/1206.4494v21.pdf(如果不了解数学,很难阅读,我在数学方面更强)。 函数方程有许多形式(我已经阅读了Edward的大部分书籍和http://www.amazon.com/The-Riemann-Zeta-Function-Applications-Mathematics/dp/0486428133的一些内容)。 人们还可以应用标准的n = 0到无穷大1 / n ^ s之和。 我还为Riemann-Siegel公式,Cauchy-Schlomich变换,Zeta(2n)的Bernoulli数等编写了Java程序... 我可能会发布我正在研究的论文。 - Axion004
请查看以下链接:http://www.math.uconn.edu/~kconrad/math5121f15/handouts/dirichletseries.pdf 和 http://www.math.uiuc.edu/~hildebr/ant/main4.pdf(参见4.4.2)。 - Axion004

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接