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