org.apache.commons.imaging.common.RationalNumber Maven / Gradle / Ivy
Show all versions of commons-imaging Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.imaging.common;
import java.text.NumberFormat;
/**
* Rational number, as used by the TIFF image format.
*
* The TIFF format specifies two data types for rational numbers based on a pair of 32-bit integers. Rational is based on unsigned 32-bit integers and SRational
* is based on signed 32-bit integers. This treatment is problematic in Java because Java does not support unsigned types. To address this challenge, this class
* stores the numerator and divisor in long (64-bit) integers, applying masks as necessary for the unsigned type.
*/
public class RationalNumber extends Number {
private static final class Option {
public static Option factory(final RationalNumber rationalNumber, final double value) {
return new Option(rationalNumber, Math.abs(rationalNumber.doubleValue() - value));
}
public final RationalNumber rationalNumber;
public final double error;
private Option(final RationalNumber rationalNumber, final double error) {
this.rationalNumber = rationalNumber;
this.error = error;
}
@Override
public String toString() {
return rationalNumber.toString();
}
}
private static final long serialVersionUID = -1;
// int-precision tolerance
private static final double TOLERANCE = 1E-8;
public static final int SHALLOW_SIZE = 32;
static RationalNumber factoryMethod(long n, long d) {
// safer than constructor - handles values outside min/max range.
// also does some simple finding of common denominators.
if (n > Integer.MAX_VALUE || n < Integer.MIN_VALUE || d > Integer.MAX_VALUE || d < Integer.MIN_VALUE) {
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) {
// brutal, imprecise truncation =(
// use the sign-preserving right shift operator.
n >>= 1;
d >>= 1;
}
if (d == 0) {
throw new NumberFormatException("Invalid value, numerator: " + n + ", divisor: " + d);
}
}
final long gcd = gcd(n, d);
d /= gcd;
n /= gcd;
return new RationalNumber((int) n, (int) d);
}
/**
* Gets the greatest common divisor
*/
private static long gcd(final long a, final long b) {
if (b == 0) {
return a;
}
return gcd(b, a % b);
}
/**
* Calculate rational number using successive approximations.
*
* @param value rational number double value
* @return the RationalNumber representation of the double value
*/
public static RationalNumber valueOf(double value) {
if (value >= Integer.MAX_VALUE) {
return new RationalNumber(Integer.MAX_VALUE, 1);
}
if (value <= -Integer.MAX_VALUE) {
return new RationalNumber(-Integer.MAX_VALUE, 1);
}
boolean negative = false;
if (value < 0) {
negative = true;
value = Math.abs(value);
}
RationalNumber l;
RationalNumber h;
if (value == 0) {
return new RationalNumber(0, 1);
}
if (value >= 1) {
final int approx = (int) value;
if (approx < value) {
l = new RationalNumber(approx, 1);
h = new RationalNumber(approx + 1, 1);
} else {
l = new RationalNumber(approx - 1, 1);
h = new RationalNumber(approx, 1);
}
} else {
final int approx = (int) (1.0 / value);
if (1.0 / approx < value) {
l = new RationalNumber(1, approx);
h = new RationalNumber(1, approx - 1);
} else {
l = new RationalNumber(1, approx + 1);
h = new RationalNumber(1, approx);
}
}
Option low = Option.factory(l, value);
Option high = Option.factory(h, value);
Option bestOption = low.error < high.error ? low : high;
final int maxIterations = 100; // value is quite high, actually.
// shouldn't matter.
for (int count = 0; bestOption.error > TOLERANCE && count < maxIterations; count++) {
final RationalNumber mediant = RationalNumber.factoryMethod(low.rationalNumber.numerator + high.rationalNumber.numerator,
low.rationalNumber.divisor + high.rationalNumber.divisor);
final Option mediantOption = Option.factory(mediant, value);
if (value < mediant.doubleValue()) {
if (high.error <= mediantOption.error) {
break;
}
high = mediantOption;
} else {
if (low.error <= mediantOption.error) {
break;
}
low = mediantOption;
}
if (mediantOption.error < bestOption.error) {
bestOption = mediantOption;
}
}
return negative ? bestOption.rationalNumber.negate() : bestOption.rationalNumber;
}
// The TIFF and EXIF specifications call for the use
// of 32 bit unsigned integers. Since Java does not have an
// unsigned type, this class widens the type to long in order
// to avoid unintended negative numbers.
public final long numerator;
public final long divisor;
public final boolean unsignedType;
/**
* Constructs an instance based on signed integers
*
* @param numerator a 32-bit signed integer
* @param divisor a non-zero 32-bit signed integer
*/
public RationalNumber(final int numerator, final int divisor) {
this.numerator = numerator;
this.divisor = divisor;
this.unsignedType = false;
}
/**
* Constructs an instance supports either signed or unsigned integers.
*
* @param numerator a numerator in the indicated form (signed or unsigned)
* @param divisor a non-zero divisor in the indicated form (signed or unsigned)
* @param unsignedType indicates whether the input values are to be treated as unsigned.
*/
public RationalNumber(final int numerator, final int divisor, final boolean unsignedType) {
this.unsignedType = unsignedType;
if (unsignedType) {
this.numerator = numerator & 0xffffffffL;
this.divisor = divisor & 0xffffffffL;
} else {
this.numerator = numerator;
this.divisor = divisor;
}
}
/**
* A private constructor for methods such as negate() that create instances of this class using the content of the current instance.
*
* @param numerator a valid numerator
* @param divisor a valid denominator
* @param unsignedType indicates how numerator and divisor values are to be interpreted.
*/
private RationalNumber(final long numerator, final long divisor, final boolean unsignedType) {
this.numerator = numerator;
this.divisor = divisor;
this.unsignedType = unsignedType;
}
@Override
public double doubleValue() {
return (double) numerator / (double) divisor;
}
@Override
public float floatValue() {
// The computation uses double value in order to preserve
// as much of the precision of the source numerator and denominator
// as possible. Note that the expression
// return (float)numerator/(float) denominator
// could lose precision since a Java float only carries 24 bits
// of precision while an integer carries 32.
return (float) doubleValue();
}
@Override
public int intValue() {
return (int) (numerator / divisor);
}
@Override
public long longValue() {
return numerator / divisor;
}
/**
* Negates the value of the RationalNumber. If the numerator of this instance has its high-order bit set, then its value is too large to be treated as a
* Java 32-bit signed integer. In such a case, the only way that a RationalNumber instance can be negated is to divide both terms by a common divisor, if a
* non-zero common divisor exists. However, if no such divisor exists, there is no numerically correct way to perform the negation. When a negation cannot
* be performed correctly, this method throws an unchecked exception.
*
* @return a valid instance with a negated value.
*/
public RationalNumber negate() {
long n = numerator;
long d = divisor;
if (unsignedType) {
// An instance of an unsigned type can be negated if and only if
// its high-order bit (the sign bit) is clear. If the bit is set,
// the value will be too large to convert to a signed type.
// In such a case it is necessary to adjust the numerator and denominator
// by their greatest common divisor (gcd), if one exists.
// If no non-zero common divisor exists, an exception is thrown.
if (n >> 31 == 1) {
// the unsigned value is so large that the high-order bit is set
// it cannot be converted to a negative number. Check to see
// whether there is an option to reduce its magnitude.
final long g = gcd(numerator, divisor);
if (g != 0) {
n /= g;
d /= g;
}
if (n >> 31 == 1) {
throw new NumberFormatException("Unsigned numerator is too large to negate " + numerator);
}
}
}
return new RationalNumber(-n, d, false);
}
public String toDisplayString() {
if (numerator % divisor == 0) {
return Long.toString(numerator / divisor);
}
final NumberFormat nf = NumberFormat.getInstance();
nf.setMaximumFractionDigits(3);
return nf.format((double) numerator / (double) divisor);
}
@Override
public String toString() {
if (divisor == 0) {
return "Invalid rational (" + numerator + "/" + divisor + ")";
}
final NumberFormat nf = NumberFormat.getInstance();
if (numerator % divisor == 0) {
return nf.format(numerator / divisor);
}
return numerator + "/" + divisor + " (" + nf.format((double) numerator / divisor) + ")";
}
}