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;
036import java.util.function.DoubleSupplier;
037import java.util.function.Supplier;
038
039import javax.measure.UnitConverter;
040
041import tech.units.indriya.AbstractConverter;
042import tech.uom.lib.common.function.ValueSupplier;
043
044/**
045 * <p>
046 * This class represents a converter multiplying numeric values by an exact scaling factor (represented as the quotient of two <code>BigInteger</code>
047 * numbers).
048 * </p>
049 *
050 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
051 * @author <a href="mailto:units@catmedia.us">Werner Keil</a>
052 * @version 1.0, Oct 11, 2016
053 * @since 1.0
054 */
055public final class RationalConverter extends AbstractConverter implements ValueSupplier<Double>, Supplier<Double>, DoubleSupplier {
056
057        /**
058         * 
059         */
060        private static final long serialVersionUID = 3563384008357680074L;
061
062        /**
063         * Holds the converter dividend.
064         */
065        private final BigInteger dividend;
066
067        /**
068         * Holds the converter divisor (always positive).
069         */
070        private final BigInteger divisor;
071
072        /**
073         * Creates a rational converter with the specified dividend and divisor.
074         *
075         * @param dividend
076         *          the dividend.
077         * @param divisor
078         *          the positive divisor.
079         * @throws IllegalArgumentException
080         *           if <code>divisor &lt;= 0</code>
081         */
082        public RationalConverter(BigInteger dividend, BigInteger divisor) {
083                if (divisor.compareTo(BigInteger.ZERO) <= 0)
084                        throw new IllegalArgumentException("Negative or zero divisor");
085                this.dividend = dividend; // Exact conversion.
086                this.divisor = divisor; // Exact conversion.
087        }
088
089        /**
090         * Convenience method equivalent to <code>new RationalConverter(BigInteger.valueOf(dividend), BigInteger.valueOf(divisor))</code>
091         *
092         * @param dividend
093         *          the dividend.
094         * @param divisor
095         *          the positive divisor.
096         * @throws IllegalArgumentException
097         *           if <code>divisor &lt;= 0</code>
098         * @throws IllegalArgumentException
099         *           if <code>dividend == divisor</code>
100         */
101        public RationalConverter(long dividend, long divisor) {
102                this(BigInteger.valueOf(dividend), BigInteger.valueOf(divisor));
103        }
104
105        /**
106         * Convenience method equivalent to <code>new RationalConverter(dividend, divisor)</code>
107         *
108         * @param dividend
109         *          the dividend.
110         * @param divisor
111         *          the positive divisor.
112         * @throws IllegalArgumentException
113         *           if <code>divisor &lt;= 0</code>
114         * @throws IllegalArgumentException
115         *           if <code>dividend == divisor</code>
116         */
117        public static RationalConverter of(BigInteger dividend, BigInteger divisor) {
118                return new RationalConverter(dividend, divisor);
119        }
120
121        /**
122         * Convenience method equivalent to <code>new RationalConverter(dividend, divisor)</code>
123         *
124         * @param dividend
125         *          the dividend.
126         * @param divisor
127         *          the positive divisor.
128         * @throws IllegalArgumentException
129         *           if <code>divisor &lt;= 0</code>
130         * @throws IllegalArgumentException
131         *           if <code>dividend == divisor</code>
132         */
133        public static RationalConverter of(long dividend, long divisor) {
134                return new RationalConverter(dividend, divisor);
135        }
136
137        /**
138         * Convenience method equivalent to <code>new RationalConverter(BigDecimal.valueOf(dividend).toBigInteger(), 
139         *    BigDecimal.valueOf(divisor).toBigInteger())</code>
140         *
141         * @param dividend
142         *          the dividend.
143         * @param divisor
144         *          the positive divisor.
145         * @throws IllegalArgumentException
146         *           if <code>divisor &lt;= 0</code>
147         * @throws IllegalArgumentException
148         *           if <code>dividend == divisor</code>
149         */
150        public static RationalConverter of(double dividend, double divisor) {
151                return new RationalConverter(BigDecimal.valueOf(dividend).toBigInteger(), BigDecimal.valueOf(divisor).toBigInteger());
152        }
153
154        /**
155         * Returns the integer dividend for this rational converter.
156         *
157         * @return this converter dividend.
158         */
159        public BigInteger getDividend() {
160                return dividend;
161        }
162
163        /**
164         * Returns the integer (positive) divisor for this rational converter.
165         *
166         * @return this converter divisor.
167         */
168        public BigInteger getDivisor() {
169                return divisor;
170        }
171
172        @Override
173        public double convertWhenNotIdentity(double value) {
174                return value * toDouble(dividend) / toDouble(divisor);
175        }
176
177        // Optimization of BigInteger.doubleValue() (implementation too
178        // inneficient).
179        private static double toDouble(BigInteger integer) {
180                return (integer.bitLength() < 64) ? integer.longValue() : integer.doubleValue();
181        }
182
183        @Override
184        protected Number convertWhenNotIdentity(BigInteger value, MathContext ctx) {
185                BigInteger newDividend = dividend.multiply(value);
186
187                //[ahuber] we try to return an exact BigInteger if possible
188                final BigInteger[] divideAndRemainder = newDividend.divideAndRemainder(divisor);
189                final BigInteger divisionResult = divideAndRemainder[0]; 
190                final BigInteger divisionRemainder = divideAndRemainder[1];
191
192                if(BigInteger.ZERO.compareTo(divisionRemainder) == 0) {
193                        return divisionResult;
194                }
195                //[ahuber] fallback to BigDecimal, thats where we are loosing 'exactness'       
196                return convertWhenNotIdentity(new BigDecimal(value), ctx);
197        }
198
199        @Override
200        public BigDecimal convertWhenNotIdentity(BigDecimal value, MathContext ctx) throws ArithmeticException {
201                BigDecimal decimalDividend = new BigDecimal(dividend, 0);
202                BigDecimal decimalDivisor = new BigDecimal(divisor, 0);
203                return value.multiply(decimalDividend, ctx).divide(decimalDivisor, ctx);
204        }
205
206        @Override
207        public boolean isIdentity() {
208                return dividend.equals(divisor);
209        }
210
211        @Override
212        protected boolean isSimpleCompositionWith(AbstractConverter that) {
213                if (that instanceof RationalConverter) {
214                        return true; 
215                }
216                return that instanceof PowerOfIntConverter;
217        }
218
219        @Override
220        protected AbstractConverter simpleCompose(AbstractConverter that) {
221                if (that instanceof RationalConverter) {
222                        return (AbstractConverter) composeSameType((RationalConverter) that); 
223                }
224                if (that instanceof PowerOfIntConverter) {
225                        return (AbstractConverter) composeSameType(((PowerOfIntConverter) that).toRationalConverter()); 
226                }
227                throw new IllegalStateException(String.format(
228                                "%s.simpleCompose() not handled for converter %s", 
229                                this, that));
230        }
231
232
233        @Override
234        public RationalConverter inverseWhenNotIdentity() {
235                return dividend.signum() == -1 ? new RationalConverter(getDivisor().negate(), getDividend().negate()) : new RationalConverter(getDivisor(),
236                                getDividend());
237        }
238
239        @Override
240        public final String transformationLiteral() {
241                return String.format("x -> x * (%s)/(%s)", dividend, divisor);
242        }
243
244        @Override
245        public boolean equals(Object obj) {
246                if (this == obj) {
247                        return true;
248                }
249                if (obj instanceof RationalConverter) {
250
251                        RationalConverter that = (RationalConverter) obj;
252                        return Objects.equals(dividend, that.dividend) && Objects.equals(divisor, that.divisor);
253                }
254                return false;
255        }
256
257        @Override
258        public int hashCode() {
259                return Objects.hash(dividend, divisor);
260        }
261
262        @Override
263        public boolean isLinear() {
264                return true;
265        }
266
267        @Override
268        public Double getValue() {
269                return getAsDouble();
270        }
271
272        @Override
273        public double getAsDouble() {
274                return toDouble(dividend) / toDouble(divisor);
275        }
276
277        @Override
278        public Double get() {
279                return getValue();
280        }
281
282        @Override
283        public int compareTo(UnitConverter o) {
284                if (this == o) {
285                        return 0;
286                }
287                if (o instanceof RationalConverter) {
288                        return getValue().compareTo(((RationalConverter) o).getValue());
289                }
290                return this.getClass().getName().compareTo(o.getClass().getName());
291        }
292
293        // -- HELPER
294
295        private AbstractConverter composeSameType(RationalConverter that) {
296                BigInteger newDividend = this.getDividend().multiply(that.getDividend());
297                BigInteger newDivisor = this.getDivisor().multiply(that.getDivisor());
298                BigInteger gcd = newDividend.gcd(newDivisor);
299                newDividend = newDividend.divide(gcd);
300                newDivisor = newDivisor.divide(gcd);
301                return (newDividend.equals(BigInteger.ONE) && newDivisor.equals(BigInteger.ONE)) 
302                                ? IDENTITY 
303                                                : new RationalConverter(newDividend, newDivisor);
304        }
305
306}