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}