001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.imaging.common; 018 019import java.text.NumberFormat; 020 021/** 022 * Rational number, as used by the TIFF image format. 023 */ 024public class RationalNumber extends Number { 025 026 private static final long serialVersionUID = -8412262656468158691L; 027 028 // int-precision tolerance 029 private static final double TOLERANCE = 1E-8; 030 031 public final int numerator; 032 public final int divisor; 033 034 public RationalNumber(final int numerator, final int divisor) { 035 this.numerator = numerator; 036 this.divisor = divisor; 037 } 038 039 static RationalNumber factoryMethod(long n, long d) { 040 // safer than constructor - handles values outside min/max range. 041 // also does some simple finding of common denominators. 042 043 if (n > Integer.MAX_VALUE || n < Integer.MIN_VALUE 044 || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE) { 045 while ((n > Integer.MAX_VALUE || n < Integer.MIN_VALUE 046 || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE) 047 && (Math.abs(n) > 1) && (Math.abs(d) > 1)) { 048 // brutal, imprecise truncation =( 049 // use the sign-preserving right shift operator. 050 n >>= 1; 051 d >>= 1; 052 } 053 054 if (d == 0) { 055 throw new NumberFormatException("Invalid value, numerator: " + n + ", divisor: " + d); 056 } 057 } 058 059 final long gcd = gcd(n, d); 060 d = d / gcd; 061 n = n / gcd; 062 063 return new RationalNumber((int) n, (int) d); 064 } 065 066 /** 067 * Return the greatest common divisor 068 */ 069 private static long gcd(final long a, final long b) { 070 if (b == 0) { 071 return a; 072 } else { 073 return gcd(b, a % b); 074 } 075 } 076 077 public RationalNumber negate() { 078 return new RationalNumber(-numerator, divisor); 079 } 080 081 @Override 082 public double doubleValue() { 083 return (double) numerator / (double) divisor; 084 } 085 086 @Override 087 public float floatValue() { 088 return (float) numerator / (float) divisor; 089 } 090 091 @Override 092 public int intValue() { 093 return numerator / divisor; 094 } 095 096 @Override 097 public long longValue() { 098 return (long) numerator / (long) divisor; 099 } 100 101 @Override 102 public String toString() { 103 if (divisor == 0) { 104 return "Invalid rational (" + numerator + "/" + divisor + ")"; 105 } 106 final NumberFormat nf = NumberFormat.getInstance(); 107 108 if ((numerator % divisor) == 0) { 109 return nf.format(numerator / divisor); 110 } 111 return numerator + "/" + divisor + " (" + nf.format((double) numerator / divisor) + ")"; 112 } 113 114 public String toDisplayString() { 115 if ((numerator % divisor) == 0) { 116 return Integer.toString(numerator / divisor); 117 } 118 final NumberFormat nf = NumberFormat.getInstance(); 119 nf.setMaximumFractionDigits(3); 120 return nf.format((double) numerator / (double) divisor); 121 } 122 123 private static final class Option { 124 public final RationalNumber rationalNumber; 125 public final double error; 126 127 private Option(final RationalNumber rationalNumber, final double error) { 128 this.rationalNumber = rationalNumber; 129 this.error = error; 130 } 131 132 public static Option factory(final RationalNumber rationalNumber, final double value) { 133 return new Option(rationalNumber, Math.abs(rationalNumber .doubleValue() - value)); 134 } 135 136 @Override 137 public String toString() { 138 return rationalNumber.toString(); 139 } 140 } 141 142 /** 143 * Calculate rational number using successive approximations. 144 * 145 * @param value rational number double value 146 * @return the RationalNumber representation of the double value 147 */ 148 public static RationalNumber valueOf(double value) { 149 if (value >= Integer.MAX_VALUE) { 150 return new RationalNumber(Integer.MAX_VALUE, 1); 151 } else if (value <= -Integer.MAX_VALUE) { 152 return new RationalNumber(-Integer.MAX_VALUE, 1); 153 } 154 155 boolean negative = false; 156 if (value < 0) { 157 negative = true; 158 value = Math.abs(value); 159 } 160 161 RationalNumber l; 162 RationalNumber h; 163 164 if (value == 0) { 165 return new RationalNumber(0, 1); 166 } else if (value >= 1) { 167 final int approx = (int) value; 168 if (approx < value) { 169 l = new RationalNumber(approx, 1); 170 h = new RationalNumber(approx + 1, 1); 171 } else { 172 l = new RationalNumber(approx - 1, 1); 173 h = new RationalNumber(approx, 1); 174 } 175 } else { 176 final int approx = (int) (1.0 / value); 177 if ((1.0 / approx) < value) { 178 l = new RationalNumber(1, approx); 179 h = new RationalNumber(1, approx - 1); 180 } else { 181 l = new RationalNumber(1, approx + 1); 182 h = new RationalNumber(1, approx); 183 } 184 } 185 Option low = Option.factory(l, value); 186 Option high = Option.factory(h, value); 187 188 Option bestOption = (low.error < high.error) ? low : high; 189 190 final int maxIterations = 100; // value is quite high, actually. 191 // shouldn't matter. 192 for (int count = 0; bestOption.error > TOLERANCE 193 && count < maxIterations; count++) { 194 final RationalNumber mediant = RationalNumber.factoryMethod( 195 (long) low.rationalNumber.numerator 196 + (long) high.rationalNumber.numerator, 197 (long) low.rationalNumber.divisor 198 + (long) high.rationalNumber.divisor); 199 final Option mediantOption = Option.factory(mediant, value); 200 201 if (value < mediant.doubleValue()) { 202 if (high.error <= mediantOption.error) { 203 break; 204 } 205 206 high = mediantOption; 207 } else { 208 if (low.error <= mediantOption.error) { 209 break; 210 } 211 212 low = mediantOption; 213 } 214 215 if (mediantOption.error < bestOption.error) { 216 bestOption = mediantOption; 217 } 218 } 219 220 return negative ? bestOption.rationalNumber.negate() 221 : bestOption.rationalNumber; 222 } 223 224}