ExtendedPrecision.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.statistics.distribution;

  18. import org.apache.commons.numbers.core.DD;

  19. /**
  20.  * Computes extended precision floating-point operations.
  21.  *
  22.  * <p>Extended precision computation is delegated to the {@link DD} class. The methods here
  23.  * verify the arguments to the computations will not overflow.
  24.  */
  25. final class ExtendedPrecision {
  26.     /** sqrt(2 pi) as a double-double number.
  27.      * Divided into two parts from the value sqrt(2 pi) computed to 64 decimal digits. */
  28.     static final DD SQRT2PI = DD.ofSum(2.5066282746310007, -1.8328579980459167e-16);

  29.     /** Threshold for a big number that may overflow when squared. 2^500. */
  30.     private static final double BIG = 0x1.0p500;
  31.     /** Threshold for a small number that may underflow when squared. 2^-500. */
  32.     private static final double SMALL = 0x1.0p-500;
  33.     /** Scale up by 2^600. */
  34.     private static final double SCALE_UP = 0x1.0p600;
  35.     /** Scale down by 2^600. */
  36.     private static final double SCALE_DOWN = 0x1.0p-600;
  37.     /** X squared value where {@code exp(-0.5*x*x)} cannot increase accuracy using the round-off
  38.      * from x squared. */
  39.     private static final int EXP_M_HALF_XX_MIN_VALUE = 2;
  40.     /** Approximate x squared value where {@code exp(-0.5*x*x) == 0}. This is above
  41.      * {@code -2 * ln(2^-1074)} due to rounding performed within the exp function. */
  42.     private static final int EXP_M_HALF_XX_MAX_VALUE = 1491;

  43.     /** No instances. */
  44.     private ExtendedPrecision() {}

  45.     /**
  46.      * Multiply the term by sqrt(2 pi).
  47.      *
  48.      * @param x Value (assumed to be positive)
  49.      * @return x * sqrt(2 pi)
  50.      */
  51.     static double xsqrt2pi(double x) {
  52.         // Note: Do not convert x to absolute for this use case
  53.         if (x > BIG) {
  54.             if (x == Double.POSITIVE_INFINITY) {
  55.                 return Double.POSITIVE_INFINITY;
  56.             }
  57.             return computeXsqrt2pi(x * SCALE_DOWN) * SCALE_UP;
  58.         } else if (x < SMALL) {
  59.             // Note: Ignore possible zero for this use case
  60.             return computeXsqrt2pi(x * SCALE_UP) * SCALE_DOWN;
  61.         } else {
  62.             return computeXsqrt2pi(x);
  63.         }
  64.     }

  65.     /**
  66.      * Compute {@code a * sqrt(2 * pi)}.
  67.      *
  68.      * @param a Value
  69.      * @return the result
  70.      */
  71.     private static double computeXsqrt2pi(double a) {
  72.         return SQRT2PI.multiply(a).hi();
  73.     }

  74.     /**
  75.      * Compute {@code sqrt(2 * x * x)}.
  76.      *
  77.      * <p>The result is computed using a high precision computation of
  78.      * {@code sqrt(2 * x * x)} avoiding underflow or overflow of {@code x}
  79.      * squared.
  80.      *
  81.      * @param x Value (assumed to be positive)
  82.      * @return {@code sqrt(2 * x * x)}
  83.      */
  84.     static double sqrt2xx(double x) {
  85.         // Note: Do not convert x to absolute for this use case
  86.         if (x > BIG) {
  87.             if (x == Double.POSITIVE_INFINITY) {
  88.                 return Double.POSITIVE_INFINITY;
  89.             }
  90.             return computeSqrt2aa(x * SCALE_DOWN) * SCALE_UP;
  91.         } else if (x < SMALL) {
  92.             // Note: Ignore possible zero for this use case
  93.             return computeSqrt2aa(x * SCALE_UP) * SCALE_DOWN;
  94.         } else {
  95.             return computeSqrt2aa(x);
  96.         }
  97.     }

  98.     /**
  99.      * Compute {@code sqrt(2 * a * a)}.
  100.      *
  101.      * @param a Value
  102.      * @return the result
  103.      */
  104.     private static double computeSqrt2aa(double a) {
  105.         return DD.ofProduct(2 * a, a).sqrt().hi();
  106.     }

  107.     /**
  108.      * Compute {@code exp(-0.5*x*x)} with high accuracy. This is performed using information in the
  109.      * round-off from {@code x*x}.
  110.      *
  111.      * <p>This is accurate at large x to 1 ulp until exp(-0.5*x*x) is close to sub-normal. For very
  112.      * small exp(-0.5*x*x) the adjustment is sub-normal and bits can be lost in the adjustment for a
  113.      * max observed error of {@code < 2} ulp.
  114.      *
  115.      * <p>At small x the accuracy cannot be improved over using exp(-0.5*x*x). This occurs at
  116.      * {@code x <= sqrt(2)}.
  117.      *
  118.      * @param x Value
  119.      * @return exp(-0.5*x*x)
  120.      * @see <a href="https://issues.apache.org/jira/browse/STATISTICS-52">STATISTICS-52</a>
  121.      */
  122.     static double expmhxx(double x) {
  123.         final double z = x * x;
  124.         if (z <= EXP_M_HALF_XX_MIN_VALUE) {
  125.             return Math.exp(-0.5 * z);
  126.         } else if (z >= EXP_M_HALF_XX_MAX_VALUE) {
  127.             // exp(-745.5) == 0
  128.             return 0;
  129.         }
  130.         final DD x2 = DD.ofSquare(x);
  131.         return expxx(-0.5 * x2.hi(), -0.5 * x2.lo());
  132.     }

  133.     /**
  134.      * Compute {@code exp(a+b)} with high accuracy assuming {@code a+b = a}.
  135.      *
  136.      * <p>This is accurate at large positive a to 1 ulp. If a is negative and exp(a) is close to
  137.      * sub-normal a bit of precision may be lost when adjusting result as the adjustment is sub-normal
  138.      * (max observed error {@code < 2} ulp). For the use case of multiplication of a number less than
  139.      * 1 by exp(-x*x), a = -x*x, the result will be sub-normal and the rounding error is lost.
  140.      *
  141.      * <p>At small |a| the accuracy cannot be improved over using exp(a) as the round-off is too small
  142.      * to create terms that can adjust the standard result by more than 0.5 ulp. This occurs at
  143.      * {@code |a| <= 1}.
  144.      *
  145.      * @param a High bits of a split number
  146.      * @param b Low bits of a split number
  147.      * @return exp(a+b)
  148.      * @see <a href="https://issues.apache.org/jira/projects/NUMBERS/issues/NUMBERS-177">
  149.      * Numbers-177: Accurate scaling by exp(z*z)</a>
  150.      */
  151.     private static double expxx(double a, double b) {
  152.         // exp(a+b) = exp(a) * exp(b)
  153.         // = exp(a) * (exp(b) - 1) + exp(a)
  154.         // Assuming:
  155.         // 1. -746 < a < 710 for no under/overflow of exp(a)
  156.         // 2. a+b = a
  157.         // As b -> 0 then exp(b) -> 1; expm1(b) -> b
  158.         // The round-off b is limited to ~ 0.5 * ulp(746) ~ 5.68e-14
  159.         // and we can use an approximation for expm1 (x/1! + x^2/2! + ...)
  160.         // The second term is required for the expm1 result but the
  161.         // bits are not significant to change the following sum with exp(a)

  162.         final double ea = Math.exp(a);
  163.         // b ~ expm1(b)
  164.         return ea * b + ea;
  165.     }
  166. }