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 <= 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 <= 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 <= 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 <= 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 <= 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}