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}