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
18 // (C) Copyright John Maddock 2006.
19 // Use, modification and distribution are subject to the
20 // Boost Software License, Version 1.0. (See accompanying file
21 // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
22
23 package org.apache.commons.numbers.gamma;
24
25 import java.util.function.DoubleSupplier;
26
27 /**
28 * Utility tools used by the Boost functions.
29 *
30 * <p>This code has been adapted from the <a href="https://www.boost.org/">Boost</a>
31 * {@code c++} implementations in {@code <boost/math/tools/>}.
32 * All work is copyright John Maddock 2006 and subject to the Boost Software License.
33 */
34 final class BoostTools {
35 /**
36 * The minimum epsilon value for relative error in the summation.
37 * Equal to Math.ulp(1.0) or 2^-52.
38 *
39 * <h2>Note</h2>
40 *
41 * <p>The summation will terminate when any additional terms are too small to
42 * change the sum. Assuming additional terms are reducing in magnitude this
43 * occurs when the term is 1 ULP from the sum:
44 * <pre>
45 * ulp(sum) >= term
46 * </pre>
47 *
48 * <p>The epsilon is used to set a configurable threshold using:
49 * <pre>
50 * sum * eps >= term
51 * </pre>
52 * <p>The minimum epsilon is the smallest value that will create a value
53 * {@code >= 1} ULP and {@code < 2} ULP of the sum. For any normal number the ULP
54 * of all values with the same exponent b is scalb(1.0, b - 52). This can
55 * be achieved by multiplication by 2^-52.
56 */
57 private static final double EPSILON = 0x1.0p-52;
58 /**
59 * The minimum epsilon value for relative error in the Kahan summation.
60 * This can be lower than {@link #EPSILON}. Set to 2^-62.
61 *
62 * <h2>Note</h2>
63 *
64 * <p>The Kahan summation uses a carry term to extend the precision of the sum.
65 * This extends the 53-bit mantissa by adding more bits to hold round-off error.
66 * Thus the term may effect the sum when it has a magnitude smaller than 1 ULP
67 * of the current sum. The epsilon can be lowered from 2^-52 to include the
68 * extra bits in the convergence criteria. The lower limit for the epsilon is
69 * 2^-104. Boost uses an epsilon specified as a number of bits of accuracy. Each
70 * lowering of the epsilon by a factor of 2 adds a guard digit to the sum.
71 * Slower converging series will benefit from a lower epsilon. This uses 2^-62
72 * to add 10 guard digits and allow testing of different thresholds when the
73 * Kahan summation is used, for example in the gamma function. Lowering the
74 * epsilon further is only of benefit if the terms can be computed exactly.
75 * Otherwise the rounding errors of the early terms affect the final result as
76 * much as the inclusion of extra guard digits.
77 */
78 private static final double KAHAN_EPSILON = 0x1.0p-62;
79 /** Message for failure to converge. */
80 private static final String MSG_FAILED_TO_CONVERGE = "Failed to converge within %d iterations";
81
82 /** Private constructor. */
83 private BoostTools() {
84 // intentionally empty.
85 }
86
87 /**
88 * Sum the series.
89 *
90 * <p>Adapted from {@code boost/math/tools/series.hpp}.
91 *
92 * @param func Series generator
93 * @param epsilon Maximum relative error allowed
94 * @param maxTerms Maximum number of terms
95 * @return result
96 */
97 static double sumSeries(DoubleSupplier func, double epsilon, int maxTerms) {
98 return sumSeries(func, epsilon, maxTerms, 0);
99 }
100
101 /**
102 * Sum the series.
103 *
104 * <p>Adapted from {@code boost/math/tools/series.hpp}.
105 *
106 * @param func Series generator
107 * @param epsilon Maximum relative error allowed
108 * @param maxTerms Maximum number of terms
109 * @param initValue Initial value
110 * @return result
111 */
112 static double sumSeries(DoubleSupplier func, double epsilon, int maxTerms, double initValue) {
113 // Note:
114 // The Boost code requires eps to be non-zero. It is created in the
115 // <boost/math/policies/policy.hpp> as a non-zero relative error term.
116 // An alternative termination condition with a divide is:
117 // (eps < Math.abs(nextTerm / result))
118 //
119 // Here the argument is checked against the minimum epsilon for a double
120 // to provide functional equivalence with the Boost policy.
121 // In the min eps case the loop terminates if the most recently added term is
122 // 0 or 1 ulp of the result. This condition is acceptable if the next
123 // computed term will be at most half of the most recent term (thus
124 // cannot be added to the current result).
125
126 final double eps = getEpsilon(epsilon, EPSILON);
127
128 int counter = maxTerms;
129
130 double result = initValue;
131 double nextTerm;
132 do {
133 nextTerm = func.getAsDouble();
134 result += nextTerm;
135 } while (Math.abs(eps * result) < Math.abs(nextTerm) && --counter > 0);
136
137 if (counter <= 0) {
138 throw new ArithmeticException(
139 String.format(MSG_FAILED_TO_CONVERGE, maxTerms));
140 }
141
142 return result;
143 }
144
145 /**
146 * Sum the series using Kahan summation.
147 *
148 * <p>Adapted from {@code boost/math/tools/series.hpp}.
149 *
150 * @param func Series generator
151 * @param epsilon Maximum relative error allowed
152 * @param maxTerms Maximum number of terms
153 * @return result
154 */
155 static double kahanSumSeries(DoubleSupplier func, double epsilon, int maxTerms) {
156 return kahanSumSeries(func, epsilon, maxTerms, 0);
157 }
158
159 /**
160 * Sum the series using Kahan summation.
161 *
162 * <p>Adapted from {@code boost/math/tools/series.hpp}.
163 *
164 * @param func Series generator
165 * @param epsilon Maximum relative error allowed
166 * @param maxTerms Maximum number of terms
167 * @param initValue Initial value
168 * @return result
169 */
170 static double kahanSumSeries(DoubleSupplier func, double epsilon, int maxTerms, double initValue) {
171 final double eps = getEpsilon(epsilon, KAHAN_EPSILON);
172
173 int counter = maxTerms;
174
175 // Kahan summation:
176 // https://en.wikipedia.org/wiki/Kahan_summation_algorithm
177 // This summation is accurate if the term is smaller in magnitude
178 // than the current sum. This is a condition required for the
179 // series termination thus the extended precision sum need not
180 // check magnitudes of terms to compute the carry.
181
182 double result = initValue;
183 double carry = 0;
184 double nextTerm;
185 do {
186 nextTerm = func.getAsDouble();
187 final double y = nextTerm - carry;
188 final double t = result + y;
189 carry = t - result;
190 carry -= y;
191 result = t;
192 } while (Math.abs(eps * result) < Math.abs(nextTerm) && --counter > 0);
193
194 if (counter <= 0) {
195 throw new ArithmeticException(
196 String.format(MSG_FAILED_TO_CONVERGE, maxTerms));
197 }
198
199 return result;
200 }
201
202 /**
203 * Gets the epsilon ensuring it satisfies the minimum allowed value.
204 *
205 * <p>This is returning the maximum of the two arguments.
206 * Do not use Math.max as it returns NaN if either value is NaN.
207 * In this case the desired result in the default minEpsilon, not NaN.
208 * Math.max will also check ordering when terms are equal to support
209 * -0.0 and 0.0. This does not apply here and a single conditional
210 * returns the desired result.
211 *
212 * @param epsilon Configured epsilon
213 * @param minEpsilon Minimum allowed epsilon
214 * @return the epsilon
215 */
216 private static double getEpsilon(double epsilon, double minEpsilon) {
217 return epsilon > minEpsilon ? epsilon : minEpsilon;
218 }
219
220 /**
221 * Evaluate the polynomial using Horner's method.
222 * The coefficients are used in descending order, for example a polynomial of order
223 * 3 requires 4 coefficients:
224 * <pre>
225 * f(x) = c[3] * x^3 + c[2] * x^2 + c[1] * x + c[0]
226 * </pre>
227 *
228 * @param c Polynomial coefficients (must have length > 0)
229 * @param x Argument x
230 * @return polynomial value
231 */
232 static double evaluatePolynomial(double[] c, double x) {
233 final int count = c.length;
234 double sum = c[count - 1];
235 for (int i = count - 2; i >= 0; --i) {
236 sum *= x;
237 sum += c[i];
238 }
239 return sum;
240 }
241 }