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}