001/*
002 * Units of Measurement Reference Implementation
003 * Copyright (c) 2005-2023, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
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.concurrent.ConcurrentHashMap;
043import java.util.logging.Level;
044import java.util.logging.Logger;
045
046import tech.units.indriya.spi.NumberSystem;
047
048/**
049 * Facade for internal number arithmetic.
050 * 
051 * @author Andi Huber
052 * @author Werner Keil
053 * @version 1.6, June 7, 2023
054 * @since 2.0
055 */
056public final class Calculus {
057        
058        private static final Logger log = Logger.getLogger(Calculus.class.getName());
059                
060        /**
061         * The default MathContext used for BigDecimal calculus.
062         */
063        public static final MathContext DEFAULT_MATH_CONTEXT = MathContext.DECIMAL128;
064
065        /**
066         * Exposes (non-final) the MathContext used for BigDecimal calculus.
067         */
068        public static MathContext MATH_CONTEXT = DEFAULT_MATH_CONTEXT;
069        
070        private static NumberSystem currentSystem;
071        
072    private static final String DEFAULT_NUMBER_SYSTEM = "tech.units.indriya.function.DefaultNumberSystem";
073
074    /**
075     * All available {@link NumberSystem NumberSystems} used for Number arithmetic.
076     */
077    public static List<NumberSystem> getAvailableNumberSystems() {
078        List<NumberSystem> systems = new ArrayList<>();
079        ServiceLoader<NumberSystem> loader = ServiceLoader.load(NumberSystem.class, NumberSystem.class.getClassLoader());
080        loader.forEach(systems::add);
081        return systems;
082    }
083
084    /**
085     * Returns the current {@link NumberSystem} used for Number arithmetic.
086     */
087    public static NumberSystem currentNumberSystem() {
088        if (currentSystem == null) {
089                currentSystem = getNumberSystem(DEFAULT_NUMBER_SYSTEM);
090        }
091        return currentSystem;
092    }
093    
094    /**
095     * Sets the current number system
096     *
097     * @param system
098     *          the new current number system.
099     * @see #currentNumberSystem
100     */
101    public static void setCurrentNumberSystem(NumberSystem system) {
102        currentSystem = system;
103    }
104
105    /**
106     * Returns the given {@link NumberSystem} used for Number arithmetic by (class) name.
107     */
108    public static NumberSystem getNumberSystem(String name) {
109        final ServiceLoader<NumberSystem> loader = ServiceLoader.load(NumberSystem.class, NumberSystem.class.getClassLoader());
110        final Iterator<NumberSystem> it = loader.iterator();
111        while (it.hasNext()) {
112            NumberSystem system = it.next();
113            if (name.equals(system.getClass().getName())) {
114                return system;
115            }
116        }
117        throw new IllegalArgumentException("NumberSystem " + name + " not found");
118    }
119    
120        /**
121         * Pi calculation with Machin's formula.
122         * 
123         * @see <a href= "http://mathworld.wolfram.com/PiFormulas.html" >Pi Formulas</a>
124         * 
125         */
126        static final class Pi {
127
128                private static final BigDecimal TWO = new BigDecimal("2");
129                private static final BigDecimal THREE = new BigDecimal("3");
130                private static final BigDecimal FOUR = new BigDecimal("4");
131                private static final BigDecimal FIVE = new BigDecimal("5");
132                private static final BigDecimal TWO_HUNDRED_THIRTY_NINE = new BigDecimal("239");
133
134                /**
135             * Memoization of Pi by number-of-digits,
136             * as used by {@link PowerOfPiConverter} to match Pi's precision with that of
137             * the current {@link MathContext}.
138             */
139            private static final Map<Integer, BigDecimal> piCache = new ConcurrentHashMap<>();
140                
141            // this is a utility class, don't instantiate
142                private Pi() {}
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                        return piCache.computeIfAbsent(numDigits, key->calculatePi(numDigits));
150                }
151                
152                /**
153                 * Calculates Pi up to numDigits. 
154                 */
155                private static BigDecimal calculatePi(int numDigits) {
156                // adds an arbitrary safety margin of 10 digits to the requested number of digits
157            // (this is a guess, without any particular research to back that up)
158                    final int calcDigits = numDigits + 10;
159            return FOUR
160                    .multiply((FOUR.multiply(arccot(FIVE, calcDigits)))
161                    .subtract(arccot(TWO_HUNDRED_THIRTY_NINE, calcDigits)))
162                    .setScale(numDigits, RoundingMode.DOWN);
163                }
164
165                /** Compute arccot via the Taylor series expansion. */
166                private static BigDecimal arccot(BigDecimal x, int numDigits) {
167                        BigDecimal unity = BigDecimal.ONE.setScale(numDigits, RoundingMode.DOWN);
168                        BigDecimal sum = unity.divide(x, RoundingMode.DOWN);
169                        BigDecimal xpower = new BigDecimal(sum.toString());
170                        BigDecimal term = null;
171                        int nTerms = 0;
172
173                        BigDecimal nearZero = BigDecimal.ONE.scaleByPowerOfTen(-numDigits);
174                        log.log(Level.FINER, ()->"arccot: ARGUMENT=" + x + " (nearZero=" + nearZero + ")");
175                        boolean add = false;
176                        // Add one term of Taylor series each time thru loop. Stop looping
177                        // when _term_
178                        // gets very close to zero.
179                        for (BigDecimal n = THREE; term == null || !term.equals(BigDecimal.ZERO); n = n.add(TWO)) {
180                                if (term != null && term.compareTo(nearZero) < 0)
181                                        break;
182                                xpower = xpower.divide(x.pow(2), RoundingMode.DOWN);
183                                term = xpower.divide(n, RoundingMode.DOWN);
184                                sum = add ? sum.add(term) : sum.subtract(term);
185                                add = !add;
186                                if(log.isLoggable(Level.FINEST)) {
187                                    log.log(Level.FINEST, "arccot: term=" + term);    
188                                }
189                                nTerms++;
190                        }
191                        if(log.isLoggable(Level.FINEST)) {
192                            log.log(Level.FINER, "arccot: done. nTerms=" + nTerms);
193                        }
194                        return sum;
195                }
196        }
197        
198        // -- NORMAL FORM TABLE OF COMPOSITION
199        
200        private final static Map<Class<? extends AbstractConverter>, Integer> normalFormOrder = new HashMap<>(9);
201
202    public static Map<Class<? extends AbstractConverter>, Integer> getNormalFormOrder() {
203        synchronized (normalFormOrder) {
204            if(normalFormOrder.isEmpty()) {
205                normalFormOrder.put(AbstractConverter.IDENTITY.getClass(), 0);
206                normalFormOrder.put(PowerOfIntConverter.class, 1); 
207                normalFormOrder.put(RationalConverter.class, 2); 
208                normalFormOrder.put(PowerOfPiConverter.class, 3);
209                normalFormOrder.put(DoubleMultiplyConverter.class, 4);
210                normalFormOrder.put(AddConverter.class, 5);
211                normalFormOrder.put(LogConverter.class, 6); 
212                normalFormOrder.put(ExpConverter.class, 7);
213                normalFormOrder.put(AbstractConverter.Pair.class, 99);        
214            }
215        }
216        
217        return Collections.unmodifiableMap(normalFormOrder);
218    }
219        
220}