SpecialMath.java

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

  18. /**
  19.  * Special math functions.
  20.  *
  21.  * @since 1.1
  22.  */
  23. final class SpecialMath {
  24.     /** Minimum x for log1pmx(x). */
  25.     private static final double X_MIN = -1;
  26.     /** Low threshold to use log1p(x) - x. */
  27.     private static final double X_LOW = -0.79149064;
  28.     /** High threshold to use log1p(x) - x. */
  29.     private static final double X_HIGH = 1;
  30.     /** 2^-6. */
  31.     private static final double TWO_POW_M6 = 0x1.0p-6;
  32.     /** 2^-12. */
  33.     private static final double TWO_POW_M12 = 0x1.0p-12;
  34.     /** 2^-20. */
  35.     private static final double TWO_POW_M20 = 0x1.0p-20;
  36.     /** 2^-53. */
  37.     private static final double TWO_POW_M53 = 0x1.0p-53;

  38.     /** Private constructor. */
  39.     private SpecialMath() {
  40.         // intentionally empty.
  41.     }

  42.     /**
  43.      * Returns {@code log(1 + x) - x}. This function is accurate when {@code x -> 0}.
  44.      *
  45.      * <p>This function uses a Taylor series expansion when x is small ({@code |x| < 0.01}):
  46.      *
  47.      * <pre>
  48.      * ln(1 + x) - x = -x^2/2 + x^3/3 - x^4/4 + ...
  49.      * </pre>
  50.      *
  51.      * <p>or around 0 ({@code -0.791 <= x <= 1}):
  52.      *
  53.      * <pre>
  54.      * ln(x + a) = ln(a) + 2 [z + z^3/3 + z^5/5 + z^7/7 + ... ]
  55.      *
  56.      * z = x / (2a + x)
  57.      * </pre>
  58.      *
  59.      * <p>For a = 1:
  60.      *
  61.      * <pre>
  62.      * ln(x + 1) - x = -x + 2 [z + z^3/3 + z^5/5 + z^7/7 + ... ]
  63.      *               = z * (-x + 2z^2 [ 1/3 + z^2/5 + z^4/7 + ... ])
  64.      * </pre>
  65.      *
  66.      * <p>The code is based on the {@code log1pmx} documentation for the <a
  67.      * href="https://rdrr.io/rforge/DPQ/man/log1pmx.html">R DPQ package</a> with addition of the
  68.      * direct Taylor series for tiny x.
  69.      *
  70.      * <p>See Abramowitz, M. and Stegun, I. A. (1972) Handbook of Mathematical Functions. New York:
  71.      * Dover. Formulas 4.1.24 and 4.2.29, p.68. <a
  72.      * href="https://en.wikipedia.org/wiki/Abramowitz_and_Stegun">Wikipedia: Abramowitz_and_Stegun</a>
  73.      * provides links to the full text which is in public domain.
  74.      *
  75.      * @param x Value x
  76.      * @return {@code log(1 + x) - x}
  77.      */
  78.     static double log1pmx(double x) {
  79.         // -1 is the minimum supported value
  80.         if (x <= X_MIN) {
  81.             return x == X_MIN ? Double.NEGATIVE_INFINITY : Double.NaN;
  82.         }
  83.         // Use the thresholds documented in the R implementation
  84.         if (x < X_LOW || x > X_HIGH) {
  85.             return Math.log1p(x) - x;
  86.         }
  87.         final double a = Math.abs(x);

  88.         // Addition to the R version for small x.
  89.         // Use a direct Taylor series:
  90.         // ln(1 + x) = x - x^2/2 + x^3/3 - x^4/4 + ...
  91.         if (a < TWO_POW_M6) {
  92.             return log1pmxSmall(x, a);
  93.         }

  94.         // The use of the following series is fast converging:
  95.         // ln(x + 1) - x = -x + 2 [z + z^3/3 + z^5/5 + z^7/7 + ... ]
  96.         //               = z * (-x + 2z^2 [ 1/3 + z^2/5 + z^4/7 + ... ])
  97.         // z = x / (2 + x)
  98.         //
  99.         // Tests show this is more accurate when |x| > 1e-4 than the direct Taylor series.
  100.         // The direct series can be modified to sum multiple terms together for a small
  101.         // increase in precision to a closer match to this variation but the direct series
  102.         // takes approximately 3x longer to converge.

  103.         final double z = x / (2 + x);
  104.         final double zz = z * z;

  105.         // Series sum
  106.         // sum(k=0,...,Inf; zz^k/(3+k*2)) = 1/3 + zz/5 + zz^2/7 + zz^3/9 + ... )

  107.         double sum = 1.0 / 3;
  108.         double numerator = 1;
  109.         int denominator = 3;
  110.         for (;;) {
  111.             numerator *= zz;
  112.             denominator += 2;
  113.             final double sum2 = sum + numerator / denominator;
  114.             // Since |x| <= 1 the additional terms will reduce in magnitude.
  115.             // Iterate until convergence. Expected iterations:
  116.             // x      iterations
  117.             // -0.79  38
  118.             // -0.5   15
  119.             // -0.1    5
  120.             //  0.1    5
  121.             //  0.5   10
  122.             //  1.0   15
  123.             if (sum2 == sum) {
  124.                 break;
  125.             }
  126.             sum = sum2;
  127.         }
  128.         return z * (2 * zz * sum - x);
  129.     }

  130.     /**
  131.      * Returns {@code log(1 + x) - x}. This function is accurate when
  132.      * {@code x -> 0}.
  133.      *
  134.      * <p>This function uses a Taylor series expansion when x is small
  135.      * ({@code |x| < 0.01}):
  136.      *
  137.      * <pre>
  138.      * ln(1 + x) - x = -x^2/2 + x^3/3 - x^4/4 + ...
  139.      * </pre>
  140.      *
  141.      * <p>No loop iterations are used as the series is directly expanded
  142.      * for a set number of terms based on the absolute value of x.
  143.      *
  144.      * @param x Value x (assumed to be small)
  145.      * @param a Absolute value of x
  146.      * @return {@code log(1 + x) - x}
  147.      */
  148.     private static double log1pmxSmall(double x, double a) {
  149.         // Use a direct Taylor series:
  150.         // ln(1 + x) = x - x^2/2 + x^3/3 - x^4/4 + ...
  151.         // Reverse the summation (small to large) for a marginal increase in precision.
  152.         // To stop the Taylor series the next term must be less than 1 ulp from the
  153.         // answer.
  154.         // x^n/n < |log(1+x)-x| * eps
  155.         // eps = machine epsilon = 2^-53
  156.         // x^n < |log(1+x)-x| * eps
  157.         // n < (log(|log(1+x)-x|) + log(eps)) / log(x)
  158.         // In practice this is a conservative limit.

  159.         final double x2 = x * x;

  160.         if (a < TWO_POW_M53) {
  161.             // Below machine epsilon. Addition of x^3/3 is not possible.
  162.             // Subtract from zero to prevent creating -0.0 for x=0.
  163.             return 0 - x2 / 2;
  164.         }

  165.         final double x4 = x2 * x2;

  166.         // +/-9.5367431640625e-07: log1pmx = -4.547470617660916e-13 :
  167.         // -4.5474764000725028e-13
  168.         // n = 4.69
  169.         if (a < TWO_POW_M20) {
  170.             // n=5
  171.             return x * x4 / 5 -
  172.                        x4 / 4 +
  173.                    x * x2 / 3 -
  174.                        x2 / 2;
  175.         }

  176.         // +/-2.44140625E-4: log1pmx = -2.9797472637290841e-08 : -2.9807173914456693e-08
  177.         // n = 6.49
  178.         if (a < TWO_POW_M12) {
  179.             // n=7
  180.             return x * x2 * x4 / 7 -
  181.                        x2 * x4 / 6 +
  182.                         x * x4 / 5 -
  183.                             x4 / 4 +
  184.                         x * x2 / 3 -
  185.                             x2 / 2;
  186.         }

  187.         // Assume |x| < 2^-6
  188.         // +/-0.015625: log1pmx = -0.00012081346403474586 : -0.00012335696813916864
  189.         // n = 10.9974

  190.         // n=11
  191.         final double x8 = x4 * x4;
  192.         return x * x2 * x8 / 11 -
  193.                    x2 * x8 / 10 +
  194.                     x * x8 /  9 -
  195.                         x8 /  8 +
  196.                x * x2 * x4 /  7 -
  197.                    x2 * x4 /  6 +
  198.                     x * x4 /  5 -
  199.                         x4 /  4 +
  200.                     x * x2 /  3 -
  201.                         x2 /  2;
  202.     }
  203. }