/* * Copyright (c) 2009-2015 * IT-Consulting Stephan Schloepke (http://www.schloepke.de/) * klemm software consulting Mirko Klemm (http://www.klemm-scs.com/) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.jbasics.math.approximation; import org.jbasics.checker.ContractCheck; import org.jbasics.math.BigDecimalMathLibrary; import org.jbasics.math.BoundedMathFunction; import org.jbasics.math.MathFunction; import org.jbasics.math.NumberConverter; import org.jbasics.utilities.DataUtilities; import java.math.BigDecimal; import java.math.MathContext; import java.math.RoundingMode; /** * Calculate the derivation of a function by using a numerical approximation. The calculation is made with a {@link * MathContext} more precise than the one given in order achieve a precise result within the given {@link MathContext}. * However since it is a numerical approach calculating the derivation it is not guaranteed to be precise. But it is a * very good approximation of the derivation. */ public class DerivateNumericalApproximation implements BoundedMathFunction<BigDecimal> { private final MathFunction<?> function; private final BigDecimal lowerBoundary; private final BigDecimal upperBoundary; /** * Create the numerical derivation function for the given {@link MathFunction}. If the {@link MathContext} * implements {@link BoundedMathFunction} the boundaries of the function will hold as well for the derivation of the * function. * * @param function The function to derive (must NOT be null) */ public DerivateNumericalApproximation(final MathFunction<?> function) { this.function = ContractCheck.mustNotBeNull(function, "function"); //$NON-NLS-1$ if (function instanceof BoundedMathFunction<?>) { this.lowerBoundary = NumberConverter.toBigDecimal(((BoundedMathFunction<?>) function).lowerBoundery()); this.upperBoundary = NumberConverter.toBigDecimal(((BoundedMathFunction<?>) function).upperBoundery()); } else { this.lowerBoundary = null; this.upperBoundary = null; } } public DerivateNumericalApproximation(final MathFunction<?> function, final BigDecimal lowerBoundary, final BigDecimal upperBoundary) { this.function = ContractCheck.mustNotBeNull(function, "function"); //$NON-NLS-1$ this.lowerBoundary = lowerBoundary; this.upperBoundary = upperBoundary; } @Override public BigDecimal calculate(final Number x) { return calculate(null, x); } @Override public BigDecimal calculate(final MathContext mcInput, final Number xNum) { final MathContext mcIn = DataUtilities.coalesce(MathFunction.DEFAULT_MATH_CONTEXT); final BigDecimal x = NumberConverter.toBigDecimal(xNum); if (x.signum() == 0) { // In case of a zero X value we only use a forward difference of the precision // to avoid problems with a negative x value in case the function cannot handle // it. A better way maybe is to check if the function can handle negative values // by checking it's boundary? final BigDecimal x2 = BigDecimal.ONE.scaleByPowerOfTen(-mcIn.getPrecision()); final BigDecimal x1 = this.lowerBoundary != null && this.lowerBoundary.compareTo(BigDecimal.ZERO) < 0 ? x2.negate() : x; final MathContext mc = new MathContext(mcIn.getPrecision() + 10, RoundingMode.HALF_EVEN); return NumberConverter.toBigDecimal(this.function.calculate(mc, x1)) .subtract(NumberConverter.toBigDecimal(this.function.calculate(mc, x2)), mc).divide(x1.subtract(x2, mc), mc); } else { final BigDecimal min = BigDecimal.ONE.add(BigDecimal.ONE.scaleByPowerOfTen(-mcIn.getPrecision())); final MathContext mc = new MathContext(mcIn.getPrecision() + 10, RoundingMode.HALF_EVEN); final BigDecimal x1 = x.multiply(min); final BigDecimal x2 = x.multiply(BigDecimalMathLibrary.CONSTANT_TWO).subtract(x1); final BigDecimal f1 = NumberConverter.toBigDecimal(this.function.calculate(mc, x1)); final BigDecimal f2 = NumberConverter.toBigDecimal(this.function.calculate(mc, x2)); final BigDecimal fDiff = f1.subtract(f2, mc); final BigDecimal xDiff = x1.subtract(x2, mc); return fDiff.divide(xDiff, mc); } } @Override public double calculate(final double x) { return calculate(MathContext.DECIMAL64, BigDecimal.valueOf(x)).doubleValue(); } @Override public BigDecimal lowerBoundery() { return this.lowerBoundary; } @Override public BigDecimal upperBoundery() { return this.upperBoundary; } }