blob: eca65d6cc36b7043dddb876334d96030ee71991d [file] [log] [blame]
/*
* 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.sis.referencing.internal;
import java.util.function.BiFunction;
import java.util.function.BinaryOperator;
import org.opengis.referencing.operation.Matrix; // For javadoc.
import org.apache.sis.system.Configuration;
import org.apache.sis.util.privy.DoubleDouble;
import org.apache.sis.math.Fraction;
import org.apache.sis.referencing.privy.ExtendedPrecisionMatrix;
/**
* Apply arithmetic operations between number of arbitrary types.
* Null numbers are interpreted as zero.
*
* @author Martin Desruisseaux (IRD, Geomatys)
*/
public enum Arithmetic {
/**
* The addition operator.
*/
ADD(DoubleDouble::add, Fraction::add, Math::addExact),
/**
* The subtraction operator.
*/
SUBTRACT(DoubleDouble::subtract, Fraction::subtract, Math::subtractExact),
/**
* The multiplication operator.
*/
MULTIPLY(DoubleDouble::multiply, Fraction::multiply, Math::multiplyExact),
/**
* The division operator.
*/
DIVIDE(DoubleDouble::divide, Fraction::divide, Fraction::valueOf),
/**
* The inverse operation. Operand <var>b</var> is ignored and can be null.
*/
INVERSE((a,b) -> a.inverse(),
(a,b) -> a.inverse(),
(a,b) -> new Fraction(1, Math.toIntExact(a))),
/**
* The negation operation. Operand <var>b</var> is ignored and can be null.
*/
NEGATE((a,b) -> a.negate(),
(a,b) -> a.negate(),
(a,b) -> Math.negateExact(a)),
/**
* The square root operation. Operand <var>b</var> is ignored and can be null.
*/
SQRT((a,b) -> a.sqrt(),
(a,b) -> DoubleDouble.of(a, false).sqrt(),
(a,b) -> DoubleDouble.of(a).sqrt());
/**
* Whether to assume that {@code float} and {@code double} values
* were intended to be exact in base 10.
*/
@Configuration
public static final boolean DECIMAL = true;
/**
* The arithmetic operation applied with double-double arithmetic.
*/
private final BinaryOperator<DoubleDouble> onDoubleDouble;
/**
* The arithmetic operation applied on fractions.
* The operation may throw {@link ArithmeticException},
* in which case {@link #onDoubleDouble} will be used as fallback.
*/
private final BiFunction<Fraction,Fraction,Number> onFraction;
/**
* The arithmetic operation applied on long integers.
* The operation may throw {@link ArithmeticException},
* in which case {@link #onDoubleDouble} will be used as fallback.
*/
private final BiFunction<Long,Long,Number> onLong;
/**
* Creates a new arithmetic operator.
*/
private Arithmetic(final BinaryOperator<DoubleDouble> onDoubleDouble,
final BiFunction<Fraction,Fraction,Number> onFraction,
final BiFunction<Long,Long,Number> onLong)
{
this.onDoubleDouble = onDoubleDouble;
this.onFraction = onFraction;
this.onLong = onLong;
}
/**
* Returns the value of the given number as a long integer if possible.
* If the conversion is not exact, then this method returns {@code null}.
*
* @param element the value to return as a long integer, or {@code null} if zero.
* @return the value as a long integer, or {@code null} if it cannot be converted.
*/
private static Long tryLongValue(final Number element) {
if (element == null || element instanceof Long) {
return (Long) element;
}
final long a = element.longValue();
return (a == element.doubleValue()) ? a : null;
}
/**
* Applies the operation on the given number.
*
* @param a the first operand. Shall not be null.
* @param b the second operation. May be null if it does not apply.
*/
private Number apply(final Number a, final Number b) {
Number result = null;
try {
final Long aLong = tryLongValue(a);
final Long bLong = tryLongValue(b);
if (aLong != null && (bLong != null || b == null)) {
result = onLong.apply(aLong, bLong);
} else {
Fraction aFrac = (a instanceof Fraction) ? (Fraction) a : null;
Fraction bFrac = (b instanceof Fraction) ? (Fraction) b : null;
if (aFrac != null || bFrac != null) {
if (aFrac == null && aLong != null) aFrac = new Fraction(Math.toIntExact(aLong), 1);
if (bFrac == null && bLong != null) bFrac = new Fraction(Math.toIntExact(bLong), 1);
if (aFrac != null && (bFrac != null || b == null)) {
result = onFraction.apply(aFrac, bFrac);
}
}
}
} catch (ArithmeticException e) {
// Ignore and fallback on double-double precision.
}
if (result == null) {
result = onDoubleDouble.apply(DoubleDouble.of(a, DECIMAL),
DoubleDouble.of(b, DECIMAL));
}
if (ExtendedPrecisionMatrix.isZero(result)) return null;
if (result.equals(a)) return a;
if (result.equals(b)) return b;
final Number simplified = tryLongValue(result);
return (simplified != null) ? simplified : result;
}
/**
* Returns the sum of the given numbers.
*
* @param a the first number, or {@code null} for zero.
* @param b the second number, or {@code null} for zero.
* @return the addition result, or {@code null} for zero.
*/
public static Number add(final Number a, final Number b) {
if (a == null) return b;
if (b == null) return a;
return ADD.apply(a, b);
}
/**
* Returns the difference between the given numbers.
*
* @param a the first number, or {@code null} for zero.
* @param b the second number, or {@code null} for zero.
* @return the subtraction result, or {@code null} for zero.
*/
public static Number subtract(final Number a, final Number b) {
if (b == null) return a;
if (a == null) return NEGATE.apply(b, null);
return SUBTRACT.apply(a, b);
}
/**
* Returns the product of the given numbers. If any argument is null (zero),
* then this method returns {@code null} regardless the value of the other argument.
* In particular, 0 × NaN = 0 instead of NaN (contrarily to standard floating point).
* This is intentional and a strong requirement for supporting matrix multiplication
* and inversion where some dimensions have unknown (NaN) scale factor.
*
* @param a the first number, or {@code null} for zero.
* @param b the second number, or {@code null} for zero.
* @return the multiplication result, or {@code null} for zero.
*/
public static Number multiply(final Number a, final Number b) {
if (a == null || b == null) return null;
if (isOne(a)) return b; // Avoid changing the number type.
if (isOne(b)) return a;
return MULTIPLY.apply(a, b);
}
/**
* Returns the quotient of the given numbers.
* If the numerator is null (zero) and the denominator is non-null, then this method returns null (zero).
* In particular, 0 / NaN = 0 instead of NaN. See {@link #multiply(Number, Number)}
*
* @param a the first number, or {@code null} for zero.
* @param b the second number, or {@code null} for zero.
* @return the division result, or {@code null} for zero.
*/
public static Number divide(final Number a, final Number b) {
if (b != null) {
if (a == null || isOne(b)) return a;
if (isOne(a)) return INVERSE.apply(b, null); // Avoid changing the type.
}
return DIVIDE.apply(a, b);
}
/**
* Returns the inverse of the given number.
*
* @param a the number, or {@code null} for zero.
* @return the inverse result, or {@code null} for zero.
*/
public static Number inverse(final Number a) {
if (a == null) return Double.POSITIVE_INFINITY;
if (isOne(a)) return a;
return INVERSE.apply(a, null);
}
/**
* Returns the negative of the given number.
*
* @param a the number, or {@code null} for zero.
* @return the result, or {@code null} for zero.
*/
public static Number negate(final Number a) {
if (a == null) return null;
return NEGATE.apply(a, null);
}
/**
* Returns the square of the given number.
*
* @param a the number, or {@code null} for zero.
* @return the square result, or {@code null} for zero.
*/
public static Number square(final Number a) {
if (a == null || isOne(a)) return a;
return MULTIPLY.apply(a, a);
}
/**
* Returns the square root of the given number.
*
* @param a the number, or {@code null} for zero.
* @return the square root result, or {@code null} for zero.
*/
public static Number sqrt(final Number a) {
if (a == null || isOne(a)) return a;
return SQRT.apply(a, null);
}
/**
* Returns {@code true} if the given number is one, ignoring {@code DoubleDouble} error term.
* This method does not check the error terms because those terms are not visible to the user
* (they cannot appear in the value returned by {@link Matrix#getElement(int, int)},
* and are not shown by {@link #toString()}) — returning {@code false} while the matrix clearly
* looks like identity would be confusing for the user. Furthermore, the errors can be non-zero
* only on the diagonal, and those values are always smaller than 2.3E-16.
*
* <p>Another argument is that the extended precision is for reducing rounding errors during
* matrix arithmetic. But since the user provided the original data as {@code double} values,
* the extra precision may have no real meaning.</p>
*
* @param element the value to test (can be {@code null}).
* @return whether the given element is equal to one.
*
* @see org.apache.sis.referencing.privy.ExtendedPrecisionMatrix#isZero(Number)
*/
public static boolean isOne(final Number element) {
return (element != null) && element.doubleValue() == 1;
}
}