1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.imaging.common;
18
19 import java.text.NumberFormat;
20
21
22
23
24
25
26
27
28 public class RationalNumber extends Number {
29
30 private static final class Option {
31 public static Option factory(final RationalNumber rationalNumber, final double value) {
32 return new Option(rationalNumber, Math.abs(rationalNumber.doubleValue() - value));
33 }
34
35 public final RationalNumber rationalNumber;
36
37 public final double error;
38
39 private Option(final RationalNumber rationalNumber, final double error) {
40 this.rationalNumber = rationalNumber;
41 this.error = error;
42 }
43
44 @Override
45 public String toString() {
46 return rationalNumber.toString();
47 }
48 }
49
50 private static final long serialVersionUID = -1;
51
52
53 private static final double TOLERANCE = 1E-8;
54 public static final int SHALLOW_SIZE = 32;
55
56 static RationalNumber factoryMethod(long n, long d) {
57
58
59
60 if (n > Integer.MAX_VALUE || n < Integer.MIN_VALUE || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE) {
61 while ((n > Integer.MAX_VALUE || n < Integer.MIN_VALUE || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE) && Math.abs(n) > 1 && Math.abs(d) > 1) {
62
63
64 n >>= 1;
65 d >>= 1;
66 }
67
68 if (d == 0) {
69 throw new NumberFormatException("Invalid value, numerator: " + n + ", divisor: " + d);
70 }
71 }
72
73 final long gcd = gcd(n, d);
74 d /= gcd;
75 n /= gcd;
76
77 return new RationalNumber((int) n, (int) d);
78 }
79
80
81
82
83 private static long gcd(final long a, final long b) {
84 if (b == 0) {
85 return a;
86 }
87 return gcd(b, a % b);
88 }
89
90
91
92
93
94
95
96 public static RationalNumber valueOf(double value) {
97 if (value >= Integer.MAX_VALUE) {
98 return new RationalNumber(Integer.MAX_VALUE, 1);
99 }
100 if (value <= -Integer.MAX_VALUE) {
101 return new RationalNumber(-Integer.MAX_VALUE, 1);
102 }
103
104 boolean negative = false;
105 if (value < 0) {
106 negative = true;
107 value = Math.abs(value);
108 }
109
110 RationalNumber l;
111 RationalNumber h;
112
113 if (value == 0) {
114 return new RationalNumber(0, 1);
115 }
116 if (value >= 1) {
117 final int approx = (int) value;
118 if (approx < value) {
119 l = new RationalNumber(approx, 1);
120 h = new RationalNumber(approx + 1, 1);
121 } else {
122 l = new RationalNumber(approx - 1, 1);
123 h = new RationalNumber(approx, 1);
124 }
125 } else {
126 final int approx = (int) (1.0 / value);
127 if (1.0 / approx < value) {
128 l = new RationalNumber(1, approx);
129 h = new RationalNumber(1, approx - 1);
130 } else {
131 l = new RationalNumber(1, approx + 1);
132 h = new RationalNumber(1, approx);
133 }
134 }
135 Option low = Option.factory(l, value);
136 Option high = Option.factory(h, value);
137
138 Option bestOption = low.error < high.error ? low : high;
139
140 final int maxIterations = 100;
141
142 for (int count = 0; bestOption.error > TOLERANCE && count < maxIterations; count++) {
143 final RationalNumber mediant = RationalNumber.factoryMethod(low.rationalNumber.numerator + high.rationalNumber.numerator,
144 low.rationalNumber.divisor + high.rationalNumber.divisor);
145 final Option mediantOption = Option.factory(mediant, value);
146
147 if (value < mediant.doubleValue()) {
148 if (high.error <= mediantOption.error) {
149 break;
150 }
151
152 high = mediantOption;
153 } else {
154 if (low.error <= mediantOption.error) {
155 break;
156 }
157
158 low = mediantOption;
159 }
160
161 if (mediantOption.error < bestOption.error) {
162 bestOption = mediantOption;
163 }
164 }
165
166 return negative ? bestOption.rationalNumber.negate() : bestOption.rationalNumber;
167 }
168
169
170
171
172
173 public final long numerator;
174
175 public final long divisor;
176
177 public final boolean unsignedType;
178
179
180
181
182
183
184
185 public RationalNumber(final int numerator, final int divisor) {
186 this.numerator = numerator;
187 this.divisor = divisor;
188 this.unsignedType = false;
189 }
190
191
192
193
194
195
196
197
198 public RationalNumber(final int numerator, final int divisor, final boolean unsignedType) {
199 this.unsignedType = unsignedType;
200 if (unsignedType) {
201 this.numerator = numerator & 0xffffffffL;
202 this.divisor = divisor & 0xffffffffL;
203 } else {
204 this.numerator = numerator;
205 this.divisor = divisor;
206 }
207 }
208
209
210
211
212
213
214
215
216 private RationalNumber(final long numerator, final long divisor, final boolean unsignedType) {
217 this.numerator = numerator;
218 this.divisor = divisor;
219 this.unsignedType = unsignedType;
220 }
221
222 @Override
223 public double doubleValue() {
224 return (double) numerator / (double) divisor;
225 }
226
227 @Override
228 public float floatValue() {
229
230
231
232
233
234
235 return (float) doubleValue();
236 }
237
238 @Override
239 public int intValue() {
240 return (int) (numerator / divisor);
241 }
242
243 @Override
244 public long longValue() {
245 return numerator / divisor;
246 }
247
248
249
250
251
252
253
254
255
256 public RationalNumber negate() {
257 long n = numerator;
258 long d = divisor;
259 if (unsignedType) {
260
261
262
263
264
265
266 if (n >> 31 == 1) {
267
268
269
270 final long g = gcd(numerator, divisor);
271 if (g != 0) {
272 n /= g;
273 d /= g;
274 }
275 if (n >> 31 == 1) {
276 throw new NumberFormatException("Unsigned numerator is too large to negate " + numerator);
277 }
278 }
279 }
280 return new RationalNumber(-n, d, false);
281 }
282
283 public String toDisplayString() {
284 if (numerator % divisor == 0) {
285 return Long.toString(numerator / divisor);
286 }
287 final NumberFormat nf = NumberFormat.getInstance();
288 nf.setMaximumFractionDigits(3);
289 return nf.format((double) numerator / (double) divisor);
290 }
291
292 @Override
293 public String toString() {
294 if (divisor == 0) {
295 return "Invalid rational (" + numerator + "/" + divisor + ")";
296 }
297 final NumberFormat nf = NumberFormat.getInstance();
298
299 if (numerator % divisor == 0) {
300 return nf.format(numerator / divisor);
301 }
302 return numerator + "/" + divisor + " (" + nf.format((double) numerator / divisor) + ")";
303 }
304
305 }