001/*
002 * Units of Measurement Implementation for Java SE
003 * Copyright (c) 2005-2017, Jean-Marie Dautelle, Werner Keil, V2COM.
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-363 nor the names of its 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 tec.uom.se.function;
031
032import javax.measure.UnitConverter;
033
034import tec.uom.lib.common.function.ValueSupplier;
035import tec.uom.se.AbstractConverter;
036
037import java.math.BigDecimal;
038import java.math.BigInteger;
039import java.math.MathContext;
040import java.util.Objects;
041import java.util.function.DoubleSupplier;
042import java.util.function.Supplier;
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   * @throws IllegalArgumentException
082   *           if <code>dividend == divisor</code>
083   */
084  public RationalConverter(BigInteger dividend, BigInteger divisor) {
085    if (divisor.compareTo(BigInteger.ZERO) <= 0)
086      throw new IllegalArgumentException("Negative or zero divisor");
087    if (dividend.equals(divisor))
088      throw new IllegalArgumentException("Would result in identity converter");
089    this.dividend = dividend; // Exact conversion.
090    this.divisor = divisor; // Exact conversion.
091  }
092
093  /**
094   * Convenience method equivalent to <code>new RationalConverter(BigInteger.valueOf(dividend), BigInteger.valueOf(divisor))</code>
095   *
096   * @param dividend
097   *          the dividend.
098   * @param divisor
099   *          the positive divisor.
100   * @throws IllegalArgumentException
101   *           if <code>divisor &lt;= 0</code>
102   * @throws IllegalArgumentException
103   *           if <code>dividend == divisor</code>
104   */
105  public RationalConverter(long dividend, long divisor) {
106    this(BigInteger.valueOf(dividend), BigInteger.valueOf(divisor));
107  }
108
109  /**
110   * Convenience method equivalent to <code>new RationalConverter(dividend, divisor)</code>
111   *
112   * @param dividend
113   *          the dividend.
114   * @param divisor
115   *          the positive divisor.
116   * @throws IllegalArgumentException
117   *           if <code>divisor &lt;= 0</code>
118   * @throws IllegalArgumentException
119   *           if <code>dividend == divisor</code>
120   */
121  public static RationalConverter of(BigInteger dividend, BigInteger divisor) {
122    return new RationalConverter(dividend, divisor);
123  }
124
125  /**
126   * Convenience method equivalent to <code>new RationalConverter(dividend, divisor)</code>
127   *
128   * @param dividend
129   *          the dividend.
130   * @param divisor
131   *          the positive divisor.
132   * @throws IllegalArgumentException
133   *           if <code>divisor &lt;= 0</code>
134   * @throws IllegalArgumentException
135   *           if <code>dividend == divisor</code>
136   */
137  public static RationalConverter of(long dividend, long divisor) {
138    return new RationalConverter(dividend, divisor);
139  }
140
141  /**
142   * Convenience method equivalent to <code>new RationalConverter(BigDecimal.valueOf(dividend).toBigInteger(), 
143   *    BigDecimal.valueOf(divisor).toBigInteger())</code>
144   *
145   * @param dividend
146   *          the dividend.
147   * @param divisor
148   *          the positive divisor.
149   * @throws IllegalArgumentException
150   *           if <code>divisor &lt;= 0</code>
151   * @throws IllegalArgumentException
152   *           if <code>dividend == divisor</code>
153   */
154  public static RationalConverter of(double dividend, double divisor) {
155    return new RationalConverter(BigDecimal.valueOf(dividend).toBigInteger(), BigDecimal.valueOf(divisor).toBigInteger());
156  }
157
158  /**
159   * Returns the integer dividend for this rational converter.
160   *
161   * @return this converter dividend.
162   */
163  public BigInteger getDividend() {
164    return dividend;
165  }
166
167  /**
168   * Returns the integer (positive) divisor for this rational converter.
169   *
170   * @return this converter divisor.
171   */
172  public BigInteger getDivisor() {
173    return divisor;
174  }
175
176  @Override
177  public double convert(double value) {
178    return value * toDouble(dividend) / toDouble(divisor);
179  }
180
181  // Optimization of BigInteger.doubleValue() (implementation too
182  // inneficient).
183  private static double toDouble(BigInteger integer) {
184    return (integer.bitLength() < 64) ? integer.longValue() : integer.doubleValue();
185  }
186
187  @Override
188  public BigDecimal convert(BigDecimal value, MathContext ctx) throws ArithmeticException {
189    BigDecimal decimalDividend = new BigDecimal(dividend, 0);
190    BigDecimal decimalDivisor = new BigDecimal(divisor, 0);
191    return value.multiply(decimalDividend, ctx).divide(decimalDivisor, ctx);
192  }
193
194  @Override
195  public UnitConverter concatenate(UnitConverter converter) {
196    if (!(converter instanceof RationalConverter))
197      return super.concatenate(converter);
198    RationalConverter that = (RationalConverter) converter;
199    BigInteger newDividend = this.getDividend().multiply(that.getDividend());
200    BigInteger newDivisor = this.getDivisor().multiply(that.getDivisor());
201    BigInteger gcd = newDividend.gcd(newDivisor);
202    newDividend = newDividend.divide(gcd);
203    newDivisor = newDivisor.divide(gcd);
204    return (newDividend.equals(BigInteger.ONE) && newDivisor.equals(BigInteger.ONE)) ? IDENTITY : new RationalConverter(newDividend, newDivisor);
205  }
206
207  @Override
208  public RationalConverter inverse() {
209    return dividend.signum() == -1 ? new RationalConverter(getDivisor().negate(), getDividend().negate()) : new RationalConverter(getDivisor(),
210        getDividend());
211  }
212
213  @Override
214  public final String toString() {
215    return "RationalConverter(" + dividend + "," + divisor + ")";
216  }
217
218  @Override
219  public boolean equals(Object obj) {
220    if (this == obj) {
221      return true;
222    }
223    if (obj instanceof RationalConverter) {
224
225      RationalConverter that = (RationalConverter) obj;
226      return Objects.equals(dividend, that.dividend) && Objects.equals(divisor, that.divisor);
227    }
228    return false;
229  }
230
231  @Override
232  public int hashCode() {
233    return Objects.hash(dividend, divisor);
234  }
235
236  @Override
237  public boolean isLinear() {
238    return true;
239  }
240
241  @Override
242  public Double getValue() {
243    return getAsDouble();
244  }
245
246  @Override
247  public double getAsDouble() {
248    return toDouble(dividend) / toDouble(divisor);
249  }
250
251  @Override
252  public Double get() {
253    return getValue();
254  }
255}