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}