001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2020, Units of Measurement project.
004 *
005 * All rights reserved.
006 *
007 * Redistribution and use in source and binary forms, with or without modification,
008 * are permitted provided that the following conditions are met:
009 *
010 * 1. Redistributions of source code must retain the above copyright notice,
011 *    this list of conditions and the following disclaimer.
012 *
013 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
014 *    and the following disclaimer in the documentation and/or other materials provided with the distribution.
015 *
016 * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products
017 *    derived from this software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
020 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
021 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
022 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
023 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package tech.units.indriya.function;
031
032import java.math.BigDecimal;
033import java.math.MathContext;
034import java.math.RoundingMode;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.HashMap;
038import java.util.Iterator;
039import java.util.List;
040import java.util.Map;
041import java.util.ServiceLoader;
042import java.util.logging.Level;
043import java.util.logging.Logger;
044
045import tech.units.indriya.spi.NumberSystem;
046
047/**
048 * Facade for internal number arithmetic.
049 * 
050 * @author Andi Huber
051 * @author Werner Keil
052 * @version 1.4, August 21, 2019
053 * @since 2.0
054 */
055public final class Calculus {
056        
057        private static final Logger log = Logger.getLogger(Calculus.class.getName());
058                
059        /**
060         * The default MathContext used for BigDecimal calculus.
061         */
062        public static final MathContext DEFAULT_MATH_CONTEXT = MathContext.DECIMAL128;
063
064        /**
065         * Exposes (non-final) the MathContext used for BigDecimal calculus.
066         */
067        public static MathContext MATH_CONTEXT = DEFAULT_MATH_CONTEXT;
068        
069        private static NumberSystem currentSystem;
070        
071    private static final String DEFAULT_NUMBER_SYSTEM = "tech.units.indriya.function.DefaultNumberSystem";
072
073    /**
074     * All available {@link NumberSystem NumberSystems} used for Number arithmetic.
075     */
076    public static List<NumberSystem> getAvailableNumberSystems() {
077        List<NumberSystem> systems = new ArrayList<>();
078        ServiceLoader<NumberSystem> loader = ServiceLoader.load(NumberSystem.class);
079        loader.forEach(NumberSystem -> {
080            systems.add(NumberSystem);
081        });
082        return systems;
083    }
084
085    /**
086     * Returns the current {@link NumberSystem} used for Number arithmetic.
087     */
088    public static NumberSystem currentNumberSystem() {
089        if (currentSystem == null) {
090                currentSystem = getNumberSystem(DEFAULT_NUMBER_SYSTEM);
091        }
092        return currentSystem;
093    }
094    
095    /**
096     * Sets the current number system
097     *
098     * @param system
099     *          the new current number system.
100     * @see #currentNumberSystem
101     */
102    public static void setCurrentNumberSystem(NumberSystem system) {
103        currentSystem = system;
104    }
105
106    /**
107     * Returns the given {@link NumberSystem} used for Number arithmetic by (class) name.
108     */
109    public static NumberSystem getNumberSystem(String name) {
110        final ServiceLoader<NumberSystem> loader = ServiceLoader.load(NumberSystem.class);
111        final Iterator<NumberSystem> it = loader.iterator();
112        while (it.hasNext()) {
113            NumberSystem provider = it.next();
114            if (name.equals(provider.getClass().getName())) {
115                return provider;
116            }
117        }
118        throw new IllegalArgumentException("NumberSystem " + name + " not found");
119    }
120    
121        
122        /**
123         * Memoization of Pi by number-of-digits.
124         */
125        private static final Map<Integer, BigDecimal> piCache = new HashMap<>();
126        
127        /**
128         * Pi calculation with Machin's formula.
129         * 
130         * @see <a href= "http://mathworld.wolfram.com/PiFormulas.html" >Pi Formulas</a>
131         * 
132         */
133        static final class Pi {
134
135                private static final BigDecimal TWO = new BigDecimal("2");
136                private static final BigDecimal THREE = new BigDecimal("3");
137                private static final BigDecimal FOUR = new BigDecimal("4");
138                private static final BigDecimal FIVE = new BigDecimal("5");
139                private static final BigDecimal TWO_HUNDRED_THIRTY_NINE = new BigDecimal("239");
140
141                private Pi() {
142                }
143
144                public static BigDecimal ofNumDigits(int numDigits) {
145                        
146                        if(numDigits<=0) {
147                                throw new IllegalArgumentException("numDigits is required to be greater than zero");
148                        }
149                        
150                        return piCache.computeIfAbsent(numDigits, __->{
151                                
152                                final int calcDigits = numDigits + 10;
153                                return FOUR
154                                                .multiply((FOUR.multiply(arccot(FIVE, calcDigits)))
155                                                .subtract(arccot(TWO_HUNDRED_THIRTY_NINE, calcDigits)))
156                                                .setScale(numDigits, RoundingMode.DOWN);
157                                
158                        });
159                }
160
161                /** Compute arccot via the Taylor series expansion. */
162                private static BigDecimal arccot(BigDecimal x, int numDigits) {
163                        BigDecimal unity = BigDecimal.ONE.setScale(numDigits, RoundingMode.DOWN);
164                        BigDecimal sum = unity.divide(x, RoundingMode.DOWN);
165                        BigDecimal xpower = new BigDecimal(sum.toString());
166                        BigDecimal term = null;
167                        int nTerms = 0;
168
169                        BigDecimal nearZero = BigDecimal.ONE.scaleByPowerOfTen(-numDigits);
170                        log.log(Level.FINER, ()->"arccot: ARGUMENT=" + x + " (nearZero=" + nearZero + ")");
171                        boolean add = false;
172                        // Add one term of Taylor series each time thru loop. Stop looping
173                        // when _term_
174                        // gets very close to zero.
175                        for (BigDecimal n = THREE; term == null || !term.equals(BigDecimal.ZERO); n = n.add(TWO)) {
176                                if (term != null && term.compareTo(nearZero) < 0)
177                                        break;
178                                xpower = xpower.divide(x.pow(2), RoundingMode.DOWN);
179                                term = xpower.divide(n, RoundingMode.DOWN);
180                                sum = add ? sum.add(term) : sum.subtract(term);
181                                add = !add;
182                                if(log.isLoggable(Level.FINEST)) {
183                                    log.log(Level.FINEST, "arccot: term=" + term);    
184                                }
185                                nTerms++;
186                        }
187                        if(log.isLoggable(Level.FINEST)) {
188                            log.log(Level.FINER, "arccot: done. nTerms=" + nTerms);
189                        }
190                        return sum;
191                }
192        }
193        
194        // -- NORMAL FORM TABLE OF COMPOSITION
195        
196        private final static Map<Class<? extends AbstractConverter>, Integer> normalFormOrder = new HashMap<>(9);
197
198    public static Map<Class<? extends AbstractConverter>, Integer> getNormalFormOrder() {
199        synchronized (normalFormOrder) {
200            if(normalFormOrder.isEmpty()) {
201                normalFormOrder.put(AbstractConverter.IDENTITY.getClass(), 0);
202                normalFormOrder.put(PowerOfIntConverter.class, 1); 
203                normalFormOrder.put(RationalConverter.class, 2); 
204                normalFormOrder.put(PowerOfPiConverter.class, 3);
205                normalFormOrder.put(DoubleMultiplyConverter.class, 4);
206                normalFormOrder.put(AddConverter.class, 5);
207                normalFormOrder.put(LogConverter.class, 6); 
208                normalFormOrder.put(ExpConverter.class, 7);
209                normalFormOrder.put(AbstractConverter.Pair.class, 99);        
210            }
211        }
212        
213        return Collections.unmodifiableMap(normalFormOrder);
214    }
215        
216}