All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.numbers.gamma.SpecialMath Maven / Gradle / Ivy

There is a newer version: 1.2
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.commons.numbers.gamma;

/**
 * Special math functions.
 *
 * @since 1.1
 */
final class SpecialMath {
    /** Minimum x for log1pmx(x). */
    private static final double X_MIN = -1;
    /** Low threshold to use log1p(x) - x. */
    private static final double X_LOW = -0.79149064;
    /** High threshold to use log1p(x) - x. */
    private static final double X_HIGH = 1;
    /** 2^-6. */
    private static final double TWO_POW_M6 = 0x1.0p-6;
    /** 2^-12. */
    private static final double TWO_POW_M12 = 0x1.0p-12;
    /** 2^-20. */
    private static final double TWO_POW_M20 = 0x1.0p-20;
    /** 2^-53. */
    private static final double TWO_POW_M53 = 0x1.0p-53;

    /** Private constructor. */
    private SpecialMath() {
        // intentionally empty.
    }

    /**
     * Returns {@code log(1 + x) - x}. This function is accurate when {@code x -> 0}.
     *
     * 

This function uses a Taylor series expansion when x is small ({@code |x| < 0.01}): * *

     * ln(1 + x) - x = -x^2/2 + x^3/3 - x^4/4 + ...
     * 
* *

or around 0 ({@code -0.791 <= x <= 1}): * *

     * ln(x + a) = ln(a) + 2 [z + z^3/3 + z^5/5 + z^7/7 + ... ]
     *
     * z = x / (2a + x)
     * 
* *

For a = 1: * *

     * ln(x + 1) - x = -x + 2 [z + z^3/3 + z^5/5 + z^7/7 + ... ]
     *               = z * (-x + 2z^2 [ 1/3 + z^2/5 + z^4/7 + ... ])
     * 
* *

The code is based on the {@code log1pmx} documentation for the R DPQ package with addition of the * direct Taylor series for tiny x. * *

See Abramowitz, M. and Stegun, I. A. (1972) Handbook of Mathematical Functions. New York: * Dover. Formulas 4.1.24 and 4.2.29, p.68. Wikipedia: Abramowitz_and_Stegun * provides links to the full text which is in public domain. * * @param x Value x * @return {@code log(1 + x) - x} */ static double log1pmx(double x) { // -1 is the minimum supported value if (x <= X_MIN) { return x == X_MIN ? Double.NEGATIVE_INFINITY : Double.NaN; } // Use the thresholds documented in the R implementation if (x < X_LOW || x > X_HIGH) { return Math.log1p(x) - x; } final double a = Math.abs(x); // Addition to the R version for small x. // Use a direct Taylor series: // ln(1 + x) = x - x^2/2 + x^3/3 - x^4/4 + ... if (a < TWO_POW_M6) { return log1pmxSmall(x, a); } // The use of the following series is fast converging: // ln(x + 1) - x = -x + 2 [z + z^3/3 + z^5/5 + z^7/7 + ... ] // = z * (-x + 2z^2 [ 1/3 + z^2/5 + z^4/7 + ... ]) // z = x / (2 + x) // // Tests show this is more accurate when |x| > 1e-4 than the direct Taylor series. // The direct series can be modified to sum multiple terms together for a small // increase in precision to a closer match to this variation but the direct series // takes approximately 3x longer to converge. final double z = x / (2 + x); final double zz = z * z; // Series sum // sum(k=0,...,Inf; zz^k/(3+k*2)) = 1/3 + zz/5 + zz^2/7 + zz^3/9 + ... ) double sum = 1.0 / 3; double numerator = 1; int denominator = 3; for (;;) { numerator *= zz; denominator += 2; final double sum2 = sum + numerator / denominator; // Since |x| <= 1 the additional terms will reduce in magnitude. // Iterate until convergence. Expected iterations: // x iterations // -0.79 38 // -0.5 15 // -0.1 5 // 0.1 5 // 0.5 10 // 1.0 15 if (sum2 == sum) { break; } sum = sum2; } return z * (2 * zz * sum - x); } /** * Returns {@code log(1 + x) - x}. This function is accurate when * {@code x -> 0}. * *

This function uses a Taylor series expansion when x is small * ({@code |x| < 0.01}): * *

     * ln(1 + x) - x = -x^2/2 + x^3/3 - x^4/4 + ...
     * 
* *

No loop iterations are used as the series is directly expanded * for a set number of terms based on the absolute value of x. * * @param x Value x (assumed to be small) * @param a Absolute value of x * @return {@code log(1 + x) - x} */ private static double log1pmxSmall(double x, double a) { // Use a direct Taylor series: // ln(1 + x) = x - x^2/2 + x^3/3 - x^4/4 + ... // Reverse the summation (small to large) for a marginal increase in precision. // To stop the Taylor series the next term must be less than 1 ulp from the // answer. // x^n/n < |log(1+x)-x| * eps // eps = machine epsilon = 2^-53 // x^n < |log(1+x)-x| * eps // n < (log(|log(1+x)-x|) + log(eps)) / log(x) // In practice this is a conservative limit. final double x2 = x * x; if (a < TWO_POW_M53) { // Below machine epsilon. Addition of x^3/3 is not possible. // Subtract from zero to prevent creating -0.0 for x=0. return 0 - x2 / 2; } final double x4 = x2 * x2; // +/-9.5367431640625e-07: log1pmx = -4.547470617660916e-13 : // -4.5474764000725028e-13 // n = 4.69 if (a < TWO_POW_M20) { // n=5 return x * x4 / 5 - x4 / 4 + x * x2 / 3 - x2 / 2; } // +/-2.44140625E-4: log1pmx = -2.9797472637290841e-08 : -2.9807173914456693e-08 // n = 6.49 if (a < TWO_POW_M12) { // n=7 return x * x2 * x4 / 7 - x2 * x4 / 6 + x * x4 / 5 - x4 / 4 + x * x2 / 3 - x2 / 2; } // Assume |x| < 2^-6 // +/-0.015625: log1pmx = -0.00012081346403474586 : -0.00012335696813916864 // n = 10.9974 // n=11 final double x8 = x4 * x4; return x * x2 * x8 / 11 - x2 * x8 / 10 + x * x8 / 9 - x8 / 8 + x * x2 * x4 / 7 - x2 * x4 / 6 + x * x4 / 5 - x4 / 4 + x * x2 / 3 - x2 / 2; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy