001/* 002 * Units of Measurement Reference Implementation 003 * Copyright (c) 2005-2018, 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.BigInteger; 034import java.math.MathContext; 035import java.util.Objects; 036 037import javax.measure.Prefix; 038import javax.measure.UnitConverter; 039 040import tech.units.indriya.AbstractConverter; 041import tech.uom.lib.common.function.IntBaseSupplier; 042import tech.uom.lib.common.function.IntExponentSupplier; 043 044/** 045 * UnitConverter for numbers in base^exponent representation. 046 * @author Andi Huber 047 * @author Werner Keil 048 * @version 1.2, May 10, 2018 049 * @since 2.0 050 */ 051public final class PowerOfIntConverter extends AbstractConverter 052 implements IntBaseSupplier, IntExponentSupplier { 053 private static final long serialVersionUID = 3546932001671571300L; 054 055 private final int base; 056 private final int exponent; 057 private final int hashCode; 058 private final double doubleFactor; // for double calculus only 059 060 /** 061 * Creates a converter with the specified Prefix. 062 * 063 * @param prefix 064 * the prefix for the factor. 065 */ 066 public static PowerOfIntConverter of(Prefix prefix) { 067 return new PowerOfIntConverter(prefix.getBase(), prefix.getExponent()); 068 } 069 070 /** 071 * Creates a converter with a factor represented by specified base^exponent. 072 * 073 * @param base 074 * @param exponent 075 * @return 076 */ 077 public static PowerOfIntConverter of(int base, int exponent) { 078 return new PowerOfIntConverter(base, exponent); 079 } 080 081 protected PowerOfIntConverter(int base, int exponent) { 082 if(base == 0) { 083 throw new IllegalArgumentException("base cannot be zero (because 0^0 is undefined)"); 084 } 085 this.base = base; 086 this.exponent = exponent; 087 this.doubleFactor = Math.pow(base, exponent); 088 this.hashCode = Objects.hash(base, exponent); 089 } 090 091 public int getBase() { 092 return base; 093 } 094 095 public int getExponent() { 096 return exponent; 097 } 098 099 @Override 100 public boolean isIdentity() { 101 if( base == 1 ) { 102 return true; // 1^x = 1 103 } 104 return exponent == 0; // x^0 = 1, for any x!=0 105 // [ahuber] 0^0 is undefined, but we guard against base==0 in the constructor, 106 // and there is no composition, that changes the base 107 } 108 109 @Override 110 public boolean isLinear() { 111 return true; 112 } 113 114 @Override 115 protected boolean isSimpleCompositionWith(AbstractConverter that) { 116 if (that instanceof PowerOfIntConverter) { 117 return ((PowerOfIntConverter) that).base == this.base; 118 } 119 return that instanceof RationalConverter; 120 } 121 122 @Override 123 protected AbstractConverter simpleCompose(AbstractConverter that) { 124 if (that instanceof PowerOfIntConverter) { 125 PowerOfIntConverter other = (PowerOfIntConverter) that; 126 if(this.base == other.base) { // always true due to guard above 127 return composeSameBaseNonIdentity(other); 128 } 129 } 130 if (that instanceof RationalConverter) { 131 return (AbstractConverter) toRationalConverter().concatenate((RationalConverter) that); 132 } 133 throw new IllegalStateException(String.format( 134 "%s.simpleCompose() not handled for converter %s", 135 this, that)); 136 } 137 138 @Override 139 public AbstractConverter inverseWhenNotIdentity() { 140 return new PowerOfIntConverter(base, -exponent); 141 } 142 143 @Override 144 protected Number convertWhenNotIdentity(BigInteger value, MathContext ctx) { 145 //[ahuber] exact number representation of factor 146 final BigInteger bintFactor = BigInteger.valueOf(base).pow(Math.abs(exponent)); 147 148 if(exponent>0) { 149 return bintFactor.multiply(value); 150 } 151 152 //[ahuber] we try to return an exact BigInteger if possible 153 final BigInteger[] divideAndRemainder = value.divideAndRemainder(bintFactor); 154 final BigInteger divisionResult = divideAndRemainder[0]; 155 final BigInteger divisionRemainder = divideAndRemainder[1]; 156 157 if(BigInteger.ZERO.compareTo(divisionRemainder) == 0) { 158 return divisionResult; 159 } 160 161 //[ahuber] fallback to BigDecimal, thats where we are loosing 'exactness' 162 final BigDecimal bdecFactor = new BigDecimal(bintFactor); 163 final BigDecimal bdecValue = new BigDecimal(value); 164 165 return bdecValue.divide(bdecFactor, Calculus.MATH_CONTEXT); 166 } 167 168 @Override 169 public BigDecimal convertWhenNotIdentity(BigDecimal value, MathContext ctx) throws ArithmeticException { 170 171 //[ahuber] thats where we are loosing 'exactness' 172 final BigDecimal bdecFactor = new BigDecimal(BigInteger.valueOf(base).pow(Math.abs(exponent))); 173 final BigDecimal bdecValue = (BigDecimal) value; 174 175 return exponent>0 176 ? bdecValue.multiply(bdecFactor, ctx) 177 : bdecValue.divide(bdecFactor, ctx); 178 } 179 180 @Override 181 public double convertWhenNotIdentity(double value) { 182 //[ahuber] multiplication is probably non-critical regarding preservation of precision 183 return value * doubleFactor; 184 } 185 186 @Override 187 public boolean equals(Object obj) { 188 if (this == obj) { 189 return true; 190 } 191 if (obj instanceof UnitConverter) { 192 UnitConverter other = (UnitConverter) obj; 193 if(this.isIdentity() && other.isIdentity()) { 194 return true; 195 } 196 } 197 if (obj instanceof PowerOfIntConverter) { 198 PowerOfIntConverter other = (PowerOfIntConverter) obj; 199 return this.base == other.base && this.exponent == other.exponent; 200 } 201 return false; 202 } 203 204 @Override 205 public final String transformationLiteral() { 206 if(base<0) { 207 return String.format("x -> x * (%s)^%s", base, exponent); 208 } 209 return String.format("x -> x * %s^%s", base, exponent); 210 } 211 212 @Override 213 public int compareTo(UnitConverter o) { 214 if (this == o) { 215 return 0; 216 } 217 if(this.isIdentity() && o.isIdentity()) { 218 return 0; 219 } 220 if (o instanceof PowerOfIntConverter) { 221 PowerOfIntConverter other = (PowerOfIntConverter) o; 222 int c = Integer.compare(base, other.base); 223 if(c!=0) { 224 return c; 225 } 226 return Integer.compare(exponent, other.exponent); 227 } 228 return this.getClass().getName().compareTo(o.getClass().getName()); 229 } 230 231 @Override 232 public int hashCode() { 233 return hashCode; 234 } 235 236 // -- HELPER 237 238 private PowerOfIntConverter composeSameBaseNonIdentity(PowerOfIntConverter other) { 239 // no check for identity required 240 return new PowerOfIntConverter(this.base, this.exponent + other.exponent); 241 } 242 243 public RationalConverter toRationalConverter() { 244 return exponent>0 245 ? new RationalConverter(BigInteger.valueOf(base).pow(exponent), BigInteger.ONE) 246 : new RationalConverter(BigInteger.ONE, BigInteger.valueOf(base).pow(-exponent)); 247 } 248}