This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new eebcfeeaab Refactor `org.apache.sis.filter.ArithmeticFunction` for 
skipping the verifications of operand types when those types are known in 
advance.
eebcfeeaab is described below

commit eebcfeeaabc3dcc41e9b6dc850d0317eac36b887
Author: Martin Desruisseaux <[email protected]>
AuthorDate: Mon Jan 5 17:31:36 2026 +0100

    Refactor `org.apache.sis.filter.ArithmeticFunction` for skipping the
    verifications of operand types when those types are known in advance.
---
 .../org/apache/sis/coverage/CoverageCombiner.java  |   2 +-
 .../org/apache/sis/filter/ComparisonFilter.java    |   6 +-
 .../apache/sis/filter/DefaultFilterFactory.java    |   1 +
 .../org/apache/sis/filter/IdentifierFilter.java    |   2 +-
 .../main/org/apache/sis/filter/PropertyValue.java  |  10 +-
 .../org/apache/sis/filter/base/BinaryFunction.java | 146 +------
 .../sis/filter/base/BinaryFunctionWidening.java    | 419 +++++++++++++++++++++
 .../apache/sis/filter/base/ConvertFunction.java    |   9 +-
 .../main/org/apache/sis/filter/base/Node.java      |  12 +
 .../sis/filter/{ => math}/ArithmeticFunction.java  | 221 +++++++----
 .../org/apache/sis/filter/math/BinaryOperator.java |  10 +-
 .../sis/filter/sqlmm/GeometryConstructor.java      |   2 +-
 .../apache/sis/filter/sqlmm/GeometryParser.java    |   2 +-
 .../org/apache/sis/filter/sqlmm/OneGeometry.java   |   6 +-
 .../main/org/apache/sis/filter/sqlmm/ST_Point.java |   2 +-
 .../org/apache/sis/filter/sqlmm/ST_Transform.java  |   4 +-
 .../org/apache/sis/filter/sqlmm/TwoGeometries.java |   4 +-
 .../org/apache/sis/filter/LogicalFilterTest.java   |  12 +-
 .../main/org/apache/sis/storage/gpx/Metadata.java  |   2 +-
 .../main/org/apache/sis/math/ArrayVector.java      |   2 +-
 .../main/org/apache/sis/math/Statistics.java       |   2 +-
 21 files changed, 644 insertions(+), 232 deletions(-)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
index 7e414f32cb..e9aa6ea06c 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/coverage/CoverageCombiner.java
@@ -252,7 +252,7 @@ next:   for (int j=0; j<sources.length; j++) {
             final long[]        max      = new long[dimension];
             for (int i=0; i<dimension; i++) {
                 /*
-                 * The conversion from `double` to `long` may loose precision. 
The -1 (for making the maximum value
+                 * The conversion from `double` to `long` may loss precision. 
The -1 (for making the maximum value
                  * inclusive) is done on the floating point value instead of 
the integer value in order to have the
                  * same rounding when the minimum and maximum values are close 
to each other.
                  * The goal is to avoid spurious "disjoint extent" exceptions.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
index 40837252b4..1f07dff723 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ComparisonFilter.java
@@ -37,7 +37,7 @@ import java.time.temporal.ChronoField;
 import java.time.temporal.Temporal;
 import org.apache.sis.math.Fraction;
 import org.apache.sis.filter.base.Node;
-import org.apache.sis.filter.base.BinaryFunction;
+import org.apache.sis.filter.base.BinaryFunctionWidening;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.filter.Filter;
@@ -74,7 +74,7 @@ import org.opengis.filter.BetweenComparisonOperator;
  *
  * @param  <R>  the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
  */
-abstract class ComparisonFilter<R> extends BinaryFunction<R,Object,Object>
+abstract class ComparisonFilter<R> extends BinaryFunctionWidening<R, Object, 
Object>
         implements BinaryComparisonOperator<R>, Optimization.OnFilter<R>
 {
     /**
@@ -158,7 +158,7 @@ abstract class ComparisonFilter<R> extends 
BinaryFunction<R,Object,Object>
     @Override
     public final boolean equals(final Object obj) {
         if (super.equals(obj)) {
-            final ComparisonFilter<?> other = (ComparisonFilter<?>) obj;
+            final var other = (ComparisonFilter<?>) obj;
             return other.isMatchingCase == isMatchingCase && 
matchAction.equals(other.matchAction);
         }
         return false;
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
index efe4a01e24..7b173219e8 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/DefaultFilterFactory.java
@@ -27,6 +27,7 @@ import org.apache.sis.geometry.WraparoundMethod;
 import org.apache.sis.geometry.wrapper.Geometries;
 import org.apache.sis.feature.internal.Resources;
 import org.apache.sis.filter.base.UnaryFunction;
+import org.apache.sis.filter.math.ArithmeticFunction;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.iso.AbstractFactory;
 import org.apache.sis.util.resources.Errors;
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
index 72c7c1dc24..817d61d711 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/IdentifierFilter.java
@@ -150,7 +150,7 @@ final class IdentifierFilter extends Node
             Object id = object.getPropertyValue(property);
             if (id != null) return identifier.equals(id.toString());
         } catch (PropertyNotFoundException e) {
-            warning(e, false);
+            warning(e);
         }
         return false;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
index 8120298a2a..c6d049952e 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/PropertyValue.java
@@ -191,7 +191,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
     @SuppressWarnings("unchecked")
     public final <N> PropertyValue<N> toValueType(final Class<N> target) {
         // `getResultClass()` should never return null with our subtypes of 
`PropertyValue`.
-        if (target == getResultClass()) {
+        if (target.isAssignableFrom(getResultClass())) {
             return (PropertyValue<N>) this;
         } else if (target == Object.class) {
             return (PropertyValue<N>) new AsObject(name, isVirtual);
@@ -249,7 +249,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
             if (instance != null) try {
                 return instance.getPropertyValue(name);
             } catch (PropertyNotFoundException e) {
-                warning(e, false);
+                warning(e);
             }
             return null;
         }
@@ -326,7 +326,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
             if (instance != null) try {
                 return 
ObjectConverters.convert(instance.getPropertyValue(name), type);
             } catch (PropertyNotFoundException | UnconvertibleObjectException 
e) {
-                warning(e, false);
+                warning(e);
             }
             return null;
         }
@@ -490,7 +490,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
             if (instance != null) try {
                 return 
converter.apply(source.cast(instance.getPropertyValue(name)));
             } catch (PropertyNotFoundException | ClassCastException | 
UnconvertibleObjectException e) {
-                warning(e, false);
+                warning(e);
             }
             return null;
         }
@@ -542,7 +542,7 @@ abstract class PropertyValue<V> extends 
LeafExpression<Feature,V>
             if (instance != null) try {
                 return (V) instance.getPropertyValue(name);
             } catch (PropertyNotFoundException e) {
-                warning(e, false);
+                warning(e);
             }
             return null;
         }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
index 2c14cb2542..0501c7c14b 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunction.java
@@ -19,11 +19,6 @@ package org.apache.sis.filter.base;
 import java.util.List;
 import java.util.Collection;
 import java.util.Objects;
-import java.math.BigInteger;
-import java.math.BigDecimal;
-import org.apache.sis.util.Numbers;
-import org.apache.sis.math.Fraction;
-import org.apache.sis.math.DecimalFunctions;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.filter.Filter;
@@ -34,16 +29,20 @@ import org.opengis.filter.Expression;
  * Base class for expressions, comparators or filters performing operations on 
two expressions.
  * The nature of the operation depends on the subclass. If operands are 
numerical values, they
  * may be converted to a common type before the operation is performed. That 
operation is not
- * necessarily an arithmetic operation; it may be a comparison for example.
+ * necessarily an arithmetic operation, it may also be a comparison for 
example.
+ *
+ * <h2>Terminology</h2>
+ * "Binary function" takes two inputs from possibly different sets. This is 
more general than
+ * "binary operator", which takes inputs and produce an output of the same set.
  *
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
  * @param  <R>   the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
- * @param  <V1>  the type of value computed by the first expression.
- * @param  <V2>  the type of value computed by the second expression.
+ * @param  <A1>  the type of value computed by the first expression (left 
operand).
+ * @param  <A2>  the type of value computed by the second expression (right 
operand).
  */
-public abstract class BinaryFunction<R,V1,V2> extends Node {
+public abstract class BinaryFunction<R, A1, A2> extends Node {
     /**
      * For cross-version compatibility.
      */
@@ -55,7 +54,7 @@ public abstract class BinaryFunction<R,V1,V2> extends Node {
      * @see #getExpression1()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are 
serializable.
-    protected final Expression<R, ? extends V1> expression1;
+    protected final Expression<R, ? extends A1> expression1;
 
     /**
      * The second of the two expressions to be used by this function.
@@ -63,7 +62,7 @@ public abstract class BinaryFunction<R,V1,V2> extends Node {
      * @see #getExpression2()
      */
     @SuppressWarnings("serial")         // Most SIS implementations are 
serializable.
-    protected final Expression<R, ? extends V2> expression2;
+    protected final Expression<R, ? extends A2> expression2;
 
     /**
      * Creates a new binary function.
@@ -71,8 +70,8 @@ public abstract class BinaryFunction<R,V1,V2> extends Node {
      * @param  expression1  the first of the two expressions to be used by 
this function.
      * @param  expression2  the second of the two expressions to be used by 
this function.
      */
-    protected BinaryFunction(final Expression<R, ? extends V1> expression1,
-                             final Expression<R, ? extends V2> expression2)
+    protected BinaryFunction(final Expression<R, ? extends A1> expression1,
+                             final Expression<R, ? extends A2> expression2)
     {
         this.expression1 = Objects.requireNonNull(expression1);
         this.expression2 = Objects.requireNonNull(expression2);
@@ -117,125 +116,4 @@ public abstract class BinaryFunction<R,V1,V2> extends 
Node {
     protected final Collection<?> getChildren() {
         return getExpressions();
     }
-
-    /**
-     * Evaluates the expression for producing a result of numeric type.
-     * This method delegates to one of the {@code applyAs(…)} methods.
-     * If no {@code applyAs(…)} implementations can return null values,
-     * this this method never return {@code null}.
-     *
-     * @param  left   the left operand. Cannot be null.
-     * @param  right  the right operand. Cannot be null.
-     * @return result of this function applied on the two given operands.
-     *         May be {@code null} only if an {@code applyAs(…)} 
implementation returned a null value.
-     */
-    protected final Number apply(final Number left, final Number right) {
-        final int type = Math.max(Numbers.getEnumConstant(left.getClass()),
-                                  Numbers.getEnumConstant(right.getClass()));
-        try {
-            switch (type) {
-                case Numbers.BIG_DECIMAL: {
-                    return applyAsDecimal(Numbers.cast(left,  
BigDecimal.class),
-                                          Numbers.cast(right, 
BigDecimal.class));
-                }
-                case Numbers.BIG_INTEGER: {
-                    return applyAsInteger(Numbers.cast(left,  
BigInteger.class),
-                                          Numbers.cast(right, 
BigInteger.class));
-                }
-                case Numbers.FRACTION: {
-                    return applyAsFraction(Numbers.cast(left,  Fraction.class),
-                                           Numbers.cast(right, 
Fraction.class));
-                }
-                case Numbers.LONG:
-                case Numbers.INTEGER:
-                case Numbers.SHORT:
-                case Numbers.BYTE: {
-                    return applyAsLong(left.longValue(), right.longValue());
-                }
-            }
-        } catch (IllegalArgumentException | ArithmeticException e) {
-            /*
-             * Integer overflow, or division by zero, or attempt to convert 
NaN or infinity
-             * to `BigDecimal`, or division does not have a terminating 
decimal expansion.
-             * This is a recoverable because we can fallback on floating point 
arithmetic.
-             */
-            warning(e, true);
-        }
-        return applyAsDouble((left  instanceof Float) ? 
DecimalFunctions.floatToDouble((Float) left)  : left.doubleValue(),
-                             (right instanceof Float) ? 
DecimalFunctions.floatToDouble((Float) right) : right.doubleValue());
-    }
-
-    /**
-     * Calculates this function using given operands of {@code long} primitive 
type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
-     * Otherwise the result is usually a {@link Long}, except for division 
which may produce other types.
-     * This method may return {@code null} if the operation cannot apply on 
numbers.
-     *
-     * @param  left   the first operand.
-     * @param  right  the second operand.
-     * @return the result of applying the function on the given operands.
-     * @throws ArithmeticException if the operation overflows or if there is a 
division by zero.
-     */
-    protected Number applyAsLong(long left, long right) {
-        return null;
-    }
-
-    /**
-     * Calculates this function using given operands of {@code double} 
primitive type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
-     * Otherwise the result is usually a {@link Double}.
-     * This method may return {@code null} if the operation cannot apply on 
numbers.
-     *
-     * @param  left   the first operand.
-     * @param  right  the second operand.
-     * @return the result of applying the function on the given operands.
-     */
-    protected Number applyAsDouble(double left, double right) {
-        return null;
-    }
-
-    /**
-     * Calculates this function using given operands of {@code Fraction} type. 
If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
-     * Otherwise the result is usually a {@link Fraction}.
-     * This method may return {@code null} if the operation cannot apply on 
numbers.
-     *
-     * @param  left   the first operand.
-     * @param  right  the second operand.
-     * @return the result of applying the function on the given operands.
-     * @throws ArithmeticException if the operation overflows or if there is a 
division by zero.
-     */
-    protected Number applyAsFraction(Fraction left, Fraction right) {
-        return null;
-    }
-
-    /**
-     * Calculates this function using given operands of {@code BigInteger} 
type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
-     * Otherwise the result is usually a {@link BigInteger}, except for 
division which may produce other types.
-     * This method may return {@code null} if the operation cannot apply on 
numbers.
-     *
-     * @param  left   the first operand.
-     * @param  right  the second operand.
-     * @return the result of applying the function on the given operands.
-     * @throws ArithmeticException if there is a division by zero.
-     */
-    protected Number applyAsInteger(BigInteger left, BigInteger right) {
-        return null;
-    }
-
-    /**
-     * Calculates this function using given operands of {@code BigDecimal} 
type. If this function is a filter,
-     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
-     * Otherwise the result is usually a {@link BigDecimal}.
-     * This method may return {@code null} if the operation cannot apply on 
numbers.
-     *
-     * @param  left   the first operand.
-     * @param  right  the second operand.
-     * @return the result of applying the function on the given operands.
-     * @throws ArithmeticException if a division does not have a terminating 
decimal expansion.
-     */
-    protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {
-        return null;
-    }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java
new file mode 100644
index 0000000000..e3e69504f3
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/BinaryFunctionWidening.java
@@ -0,0 +1,419 @@
+/*
+ * 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.filter.base;
+
+import java.util.List;
+import java.util.Collection;
+import java.math.BigInteger;
+import java.math.BigDecimal;
+import org.apache.sis.util.ConditionallySafe;
+import org.apache.sis.math.Fraction;
+import org.apache.sis.math.NumberType;
+import org.apache.sis.math.DecimalFunctions;
+import org.apache.sis.feature.internal.shared.FeatureExpression;
+import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
+import org.apache.sis.filter.Optimization;
+
+// Specific to the geoapi-3.1 and geoapi-4.0 branches:
+import org.opengis.filter.Expression;
+import org.opengis.util.ScopedName;
+
+
+/**
+ * Expression performing an operation on two expressions with values 
convertible to {@code Number}.
+ * Each {@link Number} instance will be converted to {@code long}, {@code 
double}, {@link Fraction},
+ * {@link BigInteger} or {@link BigDecimal} before the operation is applied. 
This class is used for
+ * operations where specialized methods exist for each of above-cited types.
+ *
+ * <p>The inputs are not necessarily of the same class, but typically need to 
be promoted to the widest type
+ * before the operation is executed. The result may be of a type different to 
all input types. For example,
+ * a division of two {@link Integer} values may produce a {@link Fraction}, 
and a multiplication of the same
+ * {@link Integer} values may produce a {@link Long}.</p>
+ *
+ * <p>The current version does not provide optimization for every cases. It is 
not clear that it is worth
+ * to optimize the {@link Fraction}, {@link BigInteger} and {@link BigDecimal} 
cases.</p>
+ *
+ * <h2>Requirement</h2>
+ * If a subclass implements {@link Expression}, then it shall also implement 
{@link FeatureExpression}
+ * and the type parameters <strong>must</strong> be {@code Expression<R, 
Number>}. That subclass shall
+ * also implement {@link Optimization.OnExpression}.
+ *
+ * @author  Johann Sorel (Geomatys)
+ * @author  Martin Desruisseaux (Geomatys)
+ *
+ * @param  <R>   the type of resources (e.g. {@code Feature}) used as inputs.
+ * @param  <A1>  the type of value computed by the first expression (left 
operand).
+ * @param  <A2>  the type of value computed by the second expression (right 
operand).
+ */
+public abstract class BinaryFunctionWidening<R, A1, A2> extends 
BinaryFunction<R, A1, A2> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -2515131813531876123L;
+
+    /**
+     * Creates a new binary function.
+     *
+     * @param  expression1  the first of the two expressions to be used by 
this function.
+     * @param  expression2  the second of the two expressions to be used by 
this function.
+     */
+    protected BinaryFunctionWidening(final Expression<R, ? extends A1> 
expression1,
+                                     final Expression<R, ? extends A2> 
expression2)
+    {
+        super(expression1, expression2);
+    }
+
+    /**
+     * Tries to return an expression which will invoke more directly an {@code 
applyAsXXX(…)} method,
+     * without the need to inspect the argument type. The returned expression, 
if non-null, should be
+     * more efficient than {@link #apply(Number, Number)}.
+     *
+     * @return the simplified or optimized function, or {@code null} if no 
optimization has been applied.
+     */
+    @SuppressWarnings("unchecked")
+    protected final Expression<R, ? extends Number> specialize() {
+        switch (effective(widestOperandType())) {
+            case LONG:   return new Longs<>  ((BinaryFunctionWidening<R, ? 
extends Number, ? extends Number>) this);
+            case DOUBLE: return new Doubles<>((BinaryFunctionWidening<R, ? 
extends Number, ? extends Number>) this);
+            default:     return null;
+        }
+    }
+
+    /**
+     * Returns the type of values computed by this expression.
+     * In case of doubt, this method returns the {@link Number} base class.
+     *
+     * @return the type of values computed by this expression.
+     *
+     * @see #widestOperandType()
+     * @see #effective(NumberType)
+     */
+    protected Class<? extends Number> getResultClass() {
+        return Number.class;
+    }
+
+    /**
+     * Returns an enumeration value identifying the type of return value of 
the given expression.
+     * If the expression result cannot be mapped to a number type, returns 
{@link NumberType#NULL}.
+     *
+     * @param  expression  the expression for which to identifying the type of 
return value.
+     * @return type of numbers computed by the given expression.
+     *
+     * @see FeatureExpression#getResultClass()
+     */
+    private static NumberType getNumberType(final Expression<?,?> expression) {
+        return (expression instanceof FeatureExpression<?,?>)
+                ? NumberType.forClass(((FeatureExpression<?,?>) 
expression).getResultClass()).orElse(NumberType.NULL) : NumberType.NULL;
+    }
+
+    /**
+     * Returns the widest type of the given arguments, or {@link 
NumberType#NULL} if none.
+     * Note that conversions to the returned type are not guaranteed to be 
lossless.
+     * For example, conversions from {@code long} to {@code double} may loss 
accuracy.
+     *
+     * <p>Conversion from {@code float} to {@code double} is disallowed 
because the
+     * {@link #apply(Number, Number)} method handles the decimal 
representation.</p>
+     */
+    private static NumberType widest(final NumberType t1, final NumberType t2) 
{
+        if (t1 == t2) return t1;
+        if (t1.isWiderThan(t2)) {
+            if (t2 != NumberType.FLOAT || t1 == NumberType.BIG_DECIMAL) return 
t1;
+        } else if (t2.isWiderThan(t1)) {
+            if (t1 != NumberType.FLOAT || t2 == NumberType.BIG_DECIMAL) return 
t2;
+        }
+        return NumberType.NULL;
+    }
+
+    /**
+     * Returns the widest operand type, or {@link NumberType#NULL} if it 
cannot be determined.
+     * Note that conversions to the returned type are not guaranteed to be 
lossless.
+     * For example, conversions from {@code long} to {@code double} may loss 
accuracy.
+     *
+     * @return the widest operand type, or {@link NumberType#NULL}.
+     *
+     * @see FeatureExpression#getResultClass()
+     */
+    protected final NumberType widestOperandType() {
+        return widest(getNumberType(expression1), getNumberType(expression2));
+    }
+
+    /**
+     * Simplifies the given type to one of the types handled as a special case 
in this class.
+     * The {@code switch} statement in this method's body shall be consistent 
with the switch
+     * statement in {@link #apply(Number, Number)}, with the addition of the 
{@code NULL} and
+     * {@code NUMBER} cases.
+     *
+     * @param  type  a number type.
+     * @return one of {@code LONG}, {@code DOUBLE}, {@code FRACTION}, {@code 
BIG_INTEGER},
+     *         {@code BIG_DECIMAL}, {@code NUMBER} or {@code NULL}.
+     */
+    protected static NumberType effective(final NumberType type) {
+        switch (type) {
+            case NULL:      // Case of expressions without 
`FeatureExpression.getResultType()`.
+            case NUMBER:    // Case of expressions that declare only the 
generic `Number` class.
+            case FRACTION:
+            case BIG_INTEGER:
+            case BIG_DECIMAL: return type;
+            case BYTE:
+            case SHORT:
+            case INTEGER:
+            case LONG: return NumberType.LONG;
+            default: return NumberType.DOUBLE;  // The fallback used for 
unrecognized types.
+        }
+    }
+
+    /**
+     * Evaluates the expression for producing a result of numeric type.
+     * This method delegates to one of the {@code applyAs(…)} methods.
+     * If no {@code applyAs(…)} implementations can return null values,
+     * this this method never return {@code null}.
+     *
+     * @param  left   the left operand. Cannot be null.
+     * @param  right  the right operand. Cannot be null.
+     * @return result of this function applied on the two given operands.
+     *         May be {@code null} only if an {@code applyAs(…)} 
implementation returned a null value.
+     */
+    protected final Number apply(final Number left, final Number right) {
+        final NumberType type = widest(
+                NumberType.forNumberClass(left.getClass()),
+                NumberType.forNumberClass(right.getClass()));
+        try {
+            switch (type) {
+                case FRACTION: {
+                    return applyAsFraction((Fraction) type.cast(left),
+                                           (Fraction) type.cast(right));
+                }
+                case BIG_INTEGER: {
+                    return applyAsInteger((BigInteger) type.cast(left),
+                                          (BigInteger) type.cast(right));
+                }
+                case BIG_DECIMAL: {
+                    return applyAsDecimal((BigDecimal) type.cast(left),
+                                          (BigDecimal) type.cast(right));
+                }
+                case BYTE:
+                case SHORT:
+                case INTEGER:
+                case LONG: {
+                    return applyAsLong(left.longValue(), right.longValue());
+                }
+            }
+        } catch (IllegalArgumentException | ArithmeticException e) {
+            /*
+             * Integer overflow, or division by zero, or attempt to convert 
NaN or infinity
+             * to `BigDecimal`, or division does not have a terminating 
decimal expansion.
+             * This is recoverable because we can fallback on floating point 
arithmetic.
+             */
+            warning(e, true);
+        }
+        return applyAsDouble((left  instanceof Float) ? 
DecimalFunctions.floatToDouble((Float) left)  : left.doubleValue(),
+                             (right instanceof Float) ? 
DecimalFunctions.floatToDouble((Float) right) : right.doubleValue());
+    }
+
+    /**
+     * Calculates this function using given operands of {@code long} primitive 
type. If this function is a filter,
+     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
+     * Otherwise the result is usually a {@link Long}, except for division 
which may produce other types.
+     * This method may return {@code null} if the operation cannot apply on 
numbers.
+     *
+     * @param  left   the first operand.
+     * @param  right  the second operand.
+     * @return the result of applying the function on the given operands.
+     * @throws ArithmeticException if the operation overflows or if there is a 
division by zero.
+     */
+    protected abstract Number applyAsLong(long left, long right);
+
+    /**
+     * Calculates this function using given operands of {@code double} 
primitive type. If this function is a filter,
+     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
+     * Otherwise the result is usually a {@link Double}.
+     * This method may return {@code null} if the operation cannot apply on 
numbers.
+     *
+     * @param  left   the first operand.
+     * @param  right  the second operand.
+     * @return the result of applying the function on the given operands.
+     */
+    protected abstract Number applyAsDouble(double left, double right);
+
+    /**
+     * Calculates this function using given operands of {@code Fraction} type. 
If this function is a filter,
+     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
+     * Otherwise the result is usually a {@link Fraction}.
+     * This method may return {@code null} if the operation cannot apply on 
numbers.
+     *
+     * @param  left   the first operand.
+     * @param  right  the second operand.
+     * @return the result of applying the function on the given operands.
+     * @throws ArithmeticException if the operation overflows or if there is a 
division by zero.
+     */
+    protected abstract Number applyAsFraction(Fraction left, Fraction right);
+
+    /**
+     * Calculates this function using given operands of {@code BigInteger} 
type. If this function is a filter,
+     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
+     * Otherwise the result is usually a {@link BigInteger}, except for 
division which may produce other types.
+     * This method may return {@code null} if the operation cannot apply on 
numbers.
+     *
+     * @param  left   the first operand.
+     * @param  right  the second operand.
+     * @return the result of applying the function on the given operands.
+     * @throws ArithmeticException if there is a division by zero.
+     */
+    protected abstract Number applyAsInteger(BigInteger left, BigInteger 
right);
+
+    /**
+     * Calculates this function using given operands of {@code BigDecimal} 
type. If this function is a filter,
+     * then this method should returns an {@link Integer} value 0 or 1 for 
false or true respectively.
+     * Otherwise the result is usually a {@link BigDecimal}.
+     * This method may return {@code null} if the operation cannot apply on 
numbers.
+     *
+     * @param  left   the first operand.
+     * @param  right  the second operand.
+     * @return the result of applying the function on the given operands.
+     * @throws ArithmeticException if a division does not have a terminating 
decimal expansion.
+     */
+    protected abstract Number applyAsDecimal(BigDecimal left, BigDecimal 
right);
+
+
+
+
+    /**
+     * An expression which will invoke more directly an {@code applyAsXXX(…)} 
method,
+     * without the need to inspect the argument type.
+     *
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     * @param  <A>  the type of value computed by the two expressions used as 
inputs.
+     */
+    private static abstract class Specialization<R, A extends Number> extends 
Node
+            implements FeatureExpression<R, Number>, 
Optimization.OnExpression<R, Number>
+    {
+        /** For cross-version compatibility during (de)serialization. */
+        private static final long serialVersionUID = -6902891170861955149L;
+
+        /** The implementation of the function. */
+        protected final BinaryFunctionWidening<R, ? extends A, ? extends A> 
delegate;
+
+        /** Creates a new specialization which will delegate the work to the 
given implementation. */
+        protected Specialization(final BinaryFunctionWidening<R, ? extends A, 
? extends A> delegate) {
+            this.delegate = delegate;
+        }
+
+        /** Delegates to the function. */
+        @Override public    final ScopedName            getFunctionName()  
{return ((Expression<?,?>) delegate).getFunctionName();}
+        @Override public    final Class<? super R>      getResourceClass() 
{return delegate.getResourceClass();}
+        @Override public    final List<Expression<R,?>> getParameters()    
{return delegate.getParameters();}
+        @Override protected final Collection<?>         getChildren()      
{return delegate.getChildren();}
+
+        /** Returns the type of values computed by this expression. */
+        @Override public final Class<? extends Number> getResultClass() {
+            return delegate.getResultClass();
+        }
+
+        /**
+         * Provides the type of results computed by the implementation of the 
function.
+         * The value type is declared as the generic {@link Number} type 
rather than {@code <V>},
+         * but this is desired as the result of division is not always of type 
{@code <V>}.
+         */
+        @Override public final FeatureProjectionBuilder.Item 
expectedType(FeatureProjectionBuilder addTo) {
+            return ((FeatureExpression<?,?>) delegate).expectedType(addTo);
+        }
+
+        /**
+         * Delegates the optimization to the implementation and checks if the 
result is the same.
+         * This method performs a cast which is safe only if the requirement 
documented in the
+         * {@link BinaryFunctionWidening} javadoc is true.
+         */
+        @Override public final Expression<R, ? extends Number> optimize(final 
Optimization optimization) {
+            @ConditionallySafe
+            @SuppressWarnings("unchecked")  // See Javadoc
+            final Expression<R, ? extends Number> result = 
((Optimization.OnExpression<R, Number>) delegate).optimize(optimization);
+            if (result.getClass() == getClass() && ((Specialization<?,?>) 
result).delegate == delegate) {
+                return this;
+            }
+            return result;
+        }
+    }
+
+
+
+
+    /**
+     * An expression which will invoke more directly the {@code 
applyAsLong(…)} method.
+     * This implementation can be used with operands of type {@link Byte}, 
{@link Short},
+     * {@link Integer} and {@link Long}.
+     *
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     */
+    private static final class Longs<R> extends Specialization<R, Number> {
+        /** For cross-version compatibility during (de)serialization. */
+        private static final long serialVersionUID = 8799719407972742175L;
+
+        /** Creates a new specialization for integers. */
+        Longs(BinaryFunctionWidening<R, ? extends Number, ? extends Number> 
delegate) {
+            super(delegate);
+        }
+
+        /** Executes the operation with the assumption that values are 
convertible to {@code long}. */
+        @Override public Number apply(final R feature) {
+            final Number left  = delegate.expression1.apply(feature);
+            if (left != null) {
+                final Number right = delegate.expression2.apply(feature);
+                if (right != null) try {
+                    return delegate.applyAsLong(left.longValue(), 
right.longValue());
+                } catch (IllegalArgumentException | ArithmeticException e) {
+                    warning(e, true);
+                    return delegate.applyAsDouble(left.doubleValue(), 
right.doubleValue());
+                }
+            }
+            return null;
+        }
+    }
+
+
+
+
+    /**
+     * An expression which will invoke more directly the {@code 
applyAsDouble(…)} method.
+     * This implementation can be used with operands of type {@link Byte}, 
{@link Short},
+     * {@link Integer}, {@link Long}, {@link Fraction} and {@link Double}. 
Note that the
+     * {@link Float} type is excluded because we use a conversion that tries 
to preserve
+     * the decimal representation.
+     *
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     */
+    private static final class Doubles<R> extends Specialization<R, Number> {
+        /** For cross-version compatibility during (de)serialization. */
+        private static final long serialVersionUID = -1962350161229383018L;
+
+        /** Creates a new specialization for integers. */
+        Doubles(BinaryFunctionWidening<R, ? extends Number, ? extends Number> 
delegate) {
+            super(delegate);
+        }
+
+        /** Executes the operation with the assumption that values are 
convertible to {@code long}. */
+        @Override public Number apply(final R feature) {
+            final Number left  = delegate.expression1.apply(feature);
+            if (left != null) {
+                final Number right = delegate.expression2.apply(feature);
+                if (right != null) {
+                    return delegate.applyAsDouble(left.doubleValue(), 
right.doubleValue());
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
index 802f293207..10ef96a8e9 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/ConvertFunction.java
@@ -113,6 +113,8 @@ public final class ConvertFunction<R,S,V> extends 
UnaryFunction<R,S>
 
     /**
      * Returns an identification of this operation.
+     *
+     * @return "Convert".
      */
     @Override
     public ScopedName getFunctionName() {
@@ -130,8 +132,9 @@ public final class ConvertFunction<R,S,V> extends 
UnaryFunction<R,S>
     }
 
     /**
-     * Returns the singleton expression tested by this operator
-     * together with the source and target classes.
+     * Returns the singleton expression tested by this operator together with 
the source and target classes.
+     *
+     * @return expression, source, target.
      */
     @Override
     protected Collection<?> getChildren() {
@@ -153,7 +156,7 @@ public final class ConvertFunction<R,S,V> extends 
UnaryFunction<R,S>
         try {
             return converter.apply(value);
         } catch (UnconvertibleObjectException e) {
-            warning(e, false);
+            warning(e);
             return null;
         }
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
index 527aa60048..660e8988e8 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/base/Node.java
@@ -358,6 +358,8 @@ public abstract class Node implements Serializable {
 
     /**
      * Returns a hash code value computed from the class and the children.
+     *
+     * @return a hash code value.
      */
     @Override
     public int hashCode() {
@@ -378,6 +380,16 @@ public abstract class Node implements Serializable {
         return false;
     }
 
+    /**
+     * Reports that an operation failed because of the given exception, 
resulting in a null value.
+     * This method assumes that the warning occurred in a {@code test(…)} or 
{@code apply(…)} method.
+     *
+     * @param  exception  the exception that occurred.
+     */
+    protected final void warning(final Exception exception) {
+        warning(exception, false);
+    }
+
     /**
      * Reports that an operation failed because of the given exception.
      * This method assumes that the warning occurred in a {@code test(…)} or 
{@code apply(…)} method.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java
similarity index 56%
rename from 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
rename to 
endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java
index 00491c90ac..a0e6f9c8eb 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/ArithmeticFunction.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/ArithmeticFunction.java
@@ -14,16 +14,18 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.sis.filter;
+package org.apache.sis.filter.math;
 
 import java.math.BigDecimal;
 import java.math.BigInteger;
 import org.opengis.util.ScopedName;
 import org.apache.sis.feature.internal.shared.FeatureExpression;
 import org.apache.sis.feature.internal.shared.FeatureProjectionBuilder;
-import org.apache.sis.filter.base.BinaryFunction;
+import org.apache.sis.filter.Optimization;
 import org.apache.sis.filter.visitor.FunctionNames;
+import org.apache.sis.filter.base.BinaryFunctionWidening;
 import org.apache.sis.math.Fraction;
+import org.apache.sis.math.NumberType;
 
 // Specific to the geoapi-3.1 and geoapi-4.0 branches:
 import org.opengis.feature.AttributeType;
@@ -37,9 +39,10 @@ import org.opengis.filter.Expression;
  * @author  Johann Sorel (Geomatys)
  * @author  Martin Desruisseaux (Geomatys)
  *
- * @param  <R>  the type of resources (e.g. {@link 
org.opengis.feature.Feature}) used as inputs.
+ * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+ * @param  <A>  the type of value computed by the two expressions used as 
inputs.
  */
-abstract class ArithmeticFunction<R> extends BinaryFunction<R, Number, Number>
+public abstract class ArithmeticFunction<R, A extends Number> extends 
BinaryFunctionWidening<R, A, A>
         implements FeatureExpression<R, Number>, Optimization.OnExpression<R, 
Number>
 {
     /**
@@ -49,24 +52,42 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
 
     /**
      * Creates a new arithmetic function.
+     *
+     * @param  expression1  the first of the two expressions to be used by 
this function.
+     * @param  expression2  the second of the two expressions to be used by 
this function.
      */
-    ArithmeticFunction(final Expression<R, ? extends Number> expression1,
-                       final Expression<R, ? extends Number> expression2)
+    protected ArithmeticFunction(final Expression<R, ? extends A> expression1,
+                                 final Expression<R, ? extends A> expression2)
     {
         super(expression1, expression2);
     }
 
     /**
      * Returns the type of values computed by this expression.
+     * It should be {@code Long} if we are certain that all results will be of 
that type.
+     * The default implementation is suitable to addition, subtraction and 
multiplication.
+     * Other operations should override this method.
      */
     @Override
-    public final Class<Number> getResultClass() {
+    public Class<? extends Number> getResultClass() {
+        NumberType type = widestOperandType();
+        switch (type) {
+            case BYTE:    return Short.class;
+            case SHORT:   return Integer.class;
+            case INTEGER: return Long.class;
+            case LONG:    return Number.class;  // Because of the possibility 
of overflow.
+        }
+        type = effective(type);
+        if (type.isReal()) {
+            return type.classOfValues(false).asSubclass(Number.class);
+        }
         return Number.class;
     }
 
     /**
      * Creates an attribute type for numeric values of the given name.
      * The attribute is mandatory, unbounded and has no default value.
+     * This is used for implementations of {@link #expectedType()}.
      *
      * @param  name  name of the attribute to create.
      * @return an attribute of the given name for numbers.
@@ -77,6 +98,18 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
 
     /**
      * Returns the type of results computed by this arithmetic function.
+     * It should be a constant of the following form:
+     *
+     * {@snippet lang="java" :
+     *     private static final AttributeType<Number> TYPE = 
createNumericType("Add");
+     *
+     *     @Override
+     *     protected AttributeType<Number> expectedType() {
+     *         return TYPE;
+     *     }
+     * }
+     *
+     * @return the type of result computed by this arithmetic function.
      */
     protected abstract AttributeType<Number> expectedType();
 
@@ -89,11 +122,40 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
         return addTo.addSourceProperty(expectedType(), false);
     }
 
+    /**
+     * Tries to optimize this function. Fist, this method applies the 
optimization documented
+     * in the {@linkplain Optimization.OnExpression#optimize default method 
impmementation}.
+     * Then, if it is possible to avoid to inspect the number types every time 
that the function
+     * is evaluated, this method returns a more direct implementation.
+     *
+     * @param  optimization  the simplifications or optimizations to apply on 
this arithmetic function.
+     * @return the simplified or optimized function, or {@code this} if no 
optimization has been applied.
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public final Expression<R, ? extends Number> optimize(final Optimization 
optimization) {
+        final Expression<R, ? extends Number> result = 
Optimization.OnExpression.super.optimize(optimization);
+        if (result instanceof ArithmeticFunction<?,?>) {
+            final var optimized = ((ArithmeticFunction<R,?>) 
result).specialize();
+            if (optimized != null) {
+                return optimized;
+            }
+        }
+        return result;
+    }
+
     /**
      * Evaluates the expression for producing a result of numeric type.
      * This method delegates to one of the {@code applyAs(…)} methods.
-     * If no {@code applyAs(…)} implementations can return null values,
-     * this this method never return {@code null}.
+     * Thus is the default implementation used when no specialization
+     * was found by the {@link #optimize(Optimization)} method.
+     *
+     * <h4>Null values</h4>
+     * This method returns {@code null} if at least one operand evaluated to 
{@code null}.
+     * Otherwise, this method never return {@code null} if no {@code 
applyAs(…)} implementation can return null.
+     *
+     * @param  feature  the resource (usually a feature instance) from which 
to take the inputs.
+     * @return result of the arithmetic operation, or {@code null} if at least 
one operand is null.
      */
     @Override
     public final Number apply(final R feature) {
@@ -110,33 +172,39 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
     /**
      * The "Add" (+) expression.
      *
-     * @param  <R>  the type of resources used as inputs.
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     * @param  <V>  the type of value computed by the two expressions used as 
inputs.
      */
-    static final class Add<R> extends ArithmeticFunction<R> {
+    public static final class Add<R, V extends Number> extends 
ArithmeticFunction<R,V> {
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = 5445433312445869201L;
 
-        /** Description of results of the {@code "Add"} expression. */
-        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Add);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@code "Add"} operation. */
-        Add(final Expression<R, ? extends Number> expression1,
-            final Expression<R, ? extends Number> expression2)
+        /**
+         * Creates a new expression for the {@code "Add"} operation.
+         *
+         * @param  expression1  the first of the two expressions to be used by 
this operation.
+         * @param  expression2  the second of the two expressions to be used 
by this operation.
+         */
+        public Add(final Expression<R, ? extends V> expression1,
+                   final Expression<R, ? extends V> expression2)
         {
             super(expression1, expression2);
         }
 
-        /** Creates a new expression of the same type but different 
parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<R,?>[] 
effective) {
+        /** Creates a new arithmetic function for the same operation but using 
different parameters. */
+        @Override public Expression<R, Number> recreate(final 
Expression<R,?>[] effective) {
             return new Add<>(effective[0].toValueType(Number.class),
                              effective[1].toValueType(Number.class));
         }
 
-        /** Identification of the {@code "Add"} operation. */
-        private static final ScopedName NAME = createName(FunctionNames.Add);
-        @Override public ScopedName getFunctionName() {return NAME;}
+        /** Description of results of the {@code "Add"} expression. */
+        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Add);
+
+        /** Representation of the {@code "Add"} operation. */
         @Override protected char symbol() {return '+';}
+        @Override public ScopedName getFunctionName() {return NAME;}
+        private static final ScopedName NAME = createName(FunctionNames.Add);
 
         /** Applies this expression to the given operands. */
         @Override protected Number applyAsDouble  (double     left, double     
right) {return left + right;}
@@ -150,33 +218,39 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
     /**
      * The "Subtract" (−) expression.
      *
-     * @param  <R>  the type of resources used as inputs.
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     * @param  <V>  the type of value computed by the two expressions used as 
inputs.
      */
-    static final class Subtract<R> extends ArithmeticFunction<R> {
+    public static final class Subtract<R, V extends Number> extends 
ArithmeticFunction<R,V> {
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = 3048878022726271508L;
 
-        /** Description of results of the {@code "Subtract"} expression. */
-        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Subtract);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@code "Subtract"} operation. */
-        Subtract(final Expression<R, ? extends Number> expression1,
-                 final Expression<R, ? extends Number> expression2)
+        /**
+         * Creates a new expression for the {@code "Subtract"} operation.
+         *
+         * @param  expression1  the first of the two expressions to be used by 
this operation.
+         * @param  expression2  the second of the two expressions to be used 
by this operation.
+         */
+        public Subtract(final Expression<R, ? extends V> expression1,
+                        final Expression<R, ? extends V> expression2)
         {
             super(expression1, expression2);
         }
 
-        /** Creates a new expression of the same type but different 
parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<R,?>[] 
effective) {
+        /** Creates a new arithmetic function for the same operation but using 
different parameters. */
+        @Override public Expression<R, Number> recreate(final 
Expression<R,?>[] effective) {
             return new Subtract<>(effective[0].toValueType(Number.class),
                                   effective[1].toValueType(Number.class));
         }
 
-        /** Identification of the {@code "Subtract"} operation. */
-        private static final ScopedName NAME = 
createName(FunctionNames.Subtract);
-        @Override public ScopedName getFunctionName() {return NAME;}
+        /** Description of results of the {@code "Subtract"} expression. */
+        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Subtract);
+
+        /** Representation of the {@code "Subtract"} operation. */
         @Override protected char symbol() {return '−';}
+        @Override public ScopedName getFunctionName() {return NAME;}
+        private static final ScopedName NAME = 
createName(FunctionNames.Subtract);
 
         /** Applies this expression to the given operands. */
         @Override protected Number applyAsDouble  (double     left, double     
right) {return left - right;}
@@ -190,33 +264,39 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
     /**
      * The "Multiply" (×) expression.
      *
-     * @param  <R>  the type of resources used as inputs.
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     * @param  <V>  the type of value computed by the two expressions used as 
inputs.
      */
-    static final class Multiply<R> extends ArithmeticFunction<R> {
+    public static final class Multiply<R, V extends Number> extends 
ArithmeticFunction<R,V> {
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = -1300022614832645625L;
 
-        /** Description of results of the {@code "Multiply"} expression. */
-        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Multiply);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@code "Multiply"} operation. */
-        Multiply(final Expression<R, ? extends Number> expression1,
-                 final Expression<R, ? extends Number> expression2)
+        /**
+         * Creates a new expression for the {@code "Multiply"} operation.
+         *
+         * @param  expression1  the first of the two expressions to be used by 
this operation.
+         * @param  expression2  the second of the two expressions to be used 
by this operation.
+         */
+        public Multiply(final Expression<R, ? extends V> expression1,
+                        final Expression<R, ? extends V> expression2)
         {
             super(expression1, expression2);
         }
 
-        /** Creates a new expression of the same type but different 
parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<R,?>[] 
effective) {
+        /** Creates a new arithmetic function for the same operation but using 
different parameters. */
+        @Override public Expression<R, Number> recreate(final 
Expression<R,?>[] effective) {
             return new Multiply<>(effective[0].toValueType(Number.class),
                                   effective[1].toValueType(Number.class));
         }
 
-        /** Identification of the {@code "Multiply"} operation. */
-        private static final ScopedName NAME = 
createName(FunctionNames.Multiply);
-        @Override public ScopedName getFunctionName() {return NAME;}
+        /** Description of results of the {@code "Multiply"} expression. */
+        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Multiply);
+
+        /** Representation of the {@code "Multiply"} operation. */
         @Override protected char symbol() {return '×';}
+        @Override public ScopedName getFunctionName() {return NAME;}
+        private static final ScopedName NAME = 
createName(FunctionNames.Multiply);
 
         /** Applies this expression to the given operands. */
         @Override protected Number applyAsDouble  (double     left, double     
right) {return left * right;}
@@ -230,33 +310,39 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
     /**
      * The "Divide" (÷) expression.
      *
-     * @param  <R>  the type of resources used as inputs.
+     * @param  <R>  the type of resources (typically {@code Feature}) used as 
inputs.
+     * @param  <V>  the type of value computed by the two expressions used as 
inputs.
      */
-    static final class Divide<R> extends ArithmeticFunction<R> {
+    public static final class Divide<R, V extends Number> extends 
ArithmeticFunction<R,V> {
         /** For cross-version compatibility during (de)serialization. */
         private static final long serialVersionUID = -7709291845568648891L;
 
-        /** Description of results of the {@code "Divide"} expression. */
-        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Divide);
-        @Override protected AttributeType<Number> expectedType() {return TYPE;}
-
-        /** Creates a new expression for the {@code "Divide"} operation. */
-        Divide(final Expression<R, ? extends Number> expression1,
-               final Expression<R, ? extends Number> expression2)
+        /**
+         * Creates a new expression for the {@code "Divide"} operation.
+         *
+         * @param  expression1  the first of the two expressions to be used by 
this operation.
+         * @param  expression2  the second of the two expressions to be used 
by this operation.
+         */
+        public Divide(final Expression<R, ? extends V> expression1,
+                      final Expression<R, ? extends V> expression2)
         {
             super(expression1, expression2);
         }
 
-        /** Creates a new expression of the same type but different 
parameters. */
-        @Override public Expression<R,Number> recreate(final Expression<R,?>[] 
effective) {
+        /** Creates a new arithmetic function for the same operation but using 
different parameters. */
+        @Override public Expression<R, Number> recreate(final 
Expression<R,?>[] effective) {
             return new Divide<>(effective[0].toValueType(Number.class),
                                 effective[1].toValueType(Number.class));
         }
 
-        /** Identification of the {@code "Divide"} operation. */
-        private static final ScopedName NAME = 
createName(FunctionNames.Divide);
-        @Override public ScopedName getFunctionName() {return NAME;}
+        /** Description of results of the {@code "Divide"} expression. */
+        @Override protected AttributeType<Number> expectedType() {return TYPE;}
+        private static final AttributeType<Number> TYPE = 
createNumericType(FunctionNames.Divide);
+
+        /** Representation of the {@code "Divide"} operation. */
         @Override protected char symbol() {return '÷';}
+        @Override public ScopedName getFunctionName() {return NAME;}
+        private static final ScopedName NAME = 
createName(FunctionNames.Divide);
 
         /** Divides the given integers, changing the type if the result is not 
an integer. */
         @Override protected Number applyAsDouble  (double     left, double     
right) {return left / right;}
@@ -281,5 +367,10 @@ abstract class ArithmeticFunction<R> extends 
BinaryFunction<R, Number, Number>
                 return Fraction.valueOf(left, right);
             }
         }
+
+        /** {@return {@code Number} by default because the result may change 
the class} */
+        @Override public Class<? extends Number> getResultClass() {
+            return effective(widestOperandType()) == NumberType.DOUBLE ? 
Double.class : Number.class;
+        }
     }
 }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
index b4cd3e2d3a..a39dd543e6 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/math/BinaryOperator.java
@@ -30,7 +30,13 @@ import org.opengis.filter.Expression;
 
 
 /**
- * An operation upon two operands.
+ * An operation upon two numerical operands. Inputs are {@link Number} 
instances
+ * which will be converted to {@link Double}. Output are {@link Double}.
+ *
+ * <h2>Terminology</h2>
+ * "Binary operator" is a specialization of "binary function" in that it 
restricts
+ * the inputs and the output to the same set, which is the set of 
double-precision
+ * floating point numbers.
  *
  * @author  Martin Desruisseaux (Geomatys)
  *
@@ -45,7 +51,7 @@ final class BinaryOperator<R> extends BinaryFunction<R, 
Number, Number>
     private static final long serialVersionUID = 8021641013005967925L;
 
     /**
-     * The function to apply.
+     * Description of the function to apply.
      */
     private final Function function;
 
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
index 299fe553f8..5ecaab95f0 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryConstructor.java
@@ -135,7 +135,7 @@ class GeometryConstructor<R,G> extends FunctionWithSRID<R> {
             }
             return geomImpl;
         } catch (Exception e) {
-            warning(e, false);
+            warning(e);
         }
         return null;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
index 210d419f2e..d08942812a 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/GeometryParser.java
@@ -113,7 +113,7 @@ abstract class GeometryParser<R,G> extends 
GeometryConstructor<R,G> {
             }
             return library.getGeometry(result);
         } catch (Exception e) {
-            warning(Exceptions.unwrap(e), false);
+            warning(Exceptions.unwrap(e));
         }
         return null;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
index 2ca9c78bee..657fa28971 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/OneGeometry.java
@@ -25,7 +25,7 @@ import org.opengis.filter.Expression;
 
 
 /**
- * SQLMM spatial functions taking a single geometry operand.
+ * <abbr>SQLMM</abbr> spatial functions taking a single geometry operand.
  * This base class assumes that the geometry is the only parameter.
  * Subclasses may add other kind of parameters.
  *
@@ -96,7 +96,7 @@ class OneGeometry<R> extends SpatialFunction<R> {
         if (value != null) try {
             return value.operation(operation);
         } catch (RuntimeException e) {
-            warning(e, false);
+            warning(e);
         }
         return null;
     }
@@ -159,7 +159,7 @@ class OneGeometry<R> extends SpatialFunction<R> {
             if (value != null) try {
                 return value.operationWithArgument(operation, 
argument.apply(input));
             } catch (RuntimeException e) {
-                warning(e, false);
+                warning(e);
             }
             return null;
         }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
index a6142a1883..9479c2e213 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Point.java
@@ -254,7 +254,7 @@ final class ST_Point<R> extends FunctionWithSRID<R> {
                 }
             }
         } catch (Exception e) {
-            warning(e, false);
+            warning(e);
             return null;
         }
         if (crs != null) {
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
index 0a3b7c8847..2f01362a58 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/ST_Transform.java
@@ -133,9 +133,9 @@ final class ST_Transform<R> extends FunctionWithSRID<R> {
             return 
getGeometryLibrary().getGeometry(value.transform(getTargetCRS(input)));
         } catch (BackingStoreException e) {
             final Throwable cause = e.getCause();
-            warning((cause instanceof Exception) ? (Exception) cause : e, 
false);
+            warning((cause instanceof Exception) ? (Exception) cause : e);
         } catch (UnsupportedOperationException | FactoryException | 
TransformException e) {
-            warning(e, false);
+            warning(e);
         }
         return null;
     }
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
index 667c91b096..a42060b489 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/filter/sqlmm/TwoGeometries.java
@@ -127,7 +127,7 @@ class TwoGeometries<R> extends SpatialFunction<R> {
             if (other != null) try {
                 return value.operation(operation, other);
             } catch (TransformException | RuntimeException e) {
-                warning(e, false);
+                warning(e);
             }
         }
         return null;
@@ -194,7 +194,7 @@ class TwoGeometries<R> extends SpatialFunction<R> {
                 if (other != null) try {
                     return value.operationWithArgument(operation, other, 
argument.apply(input));
                 } catch (TransformException | RuntimeException e) {
-                    warning(e, false);
+                    warning(e);
                 }
             }
             return null;
diff --git 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
index e1bae3fb01..3d2169b8dc 100644
--- 
a/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
+++ 
b/endorsed/src/org.apache.sis.feature/test/org/apache/sis/filter/LogicalFilterTest.java
@@ -247,20 +247,22 @@ public final class LogicalFilterTest extends TestCase {
          */
         final var expression = factory.divide(factory.property(attribute, 
Integer.class), factory.literal(5));
         final var optimization = new Optimization();
-        assertInstanceOf(DynamicOptimization.class, 
optimization.apply(expression));
+        var optimized = optimization.apply(expression);
+        assertInstanceOf(DynamicOptimization.class, optimized);
         assertEquals(200, expression.apply(instance).intValue());
+        assertEquals(200,  optimized.apply(instance).intValue());
         /*
          * Notify the optimizer that property values will be of `String` type.
          * The optimizer should compute an `ObjectConverter` in advance.
          */
         optimization.setFinalFeatureType(feature);
-        final var optimized = optimization.apply(expression);
-        assertEquals(200, expression.apply(instance).intValue());
+        optimized = optimization.apply(expression);
         assertNotSame(expression, optimized);
+        assertEquals(200, optimized.apply(instance).intValue());
 
         final var property = assertInstanceOf(PropertyValue.class, 
optimized.getParameters().get(0));
-        assertEquals(String.class, property.getSourceClass());
-        assertEquals(Number.class, property.getResultClass());
+        assertEquals(String.class,  property.getSourceClass());
+        assertEquals(Integer.class, property.getResultClass());
     }
 
     /**
diff --git 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
index 9c6a3eeba5..309f516221 100644
--- 
a/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
+++ 
b/endorsed/src/org.apache.sis.storage.xml/main/org/apache/sis/storage/gpx/Metadata.java
@@ -316,7 +316,7 @@ public final class Metadata extends SimpleMetadata {
     public Collection<Responsibility> getPointOfContacts() {
         if (creator != null) {
             final var p = new Person(creator);
-            return (author != null) ? List.of(p, author) : List.of(author);
+            return (author != null) ? List.of(p, author) : List.of(p);
         }
         return (author != null) ? List.of(author) : List.of();
     }
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
index f6218e723c..baea507adc 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/ArrayVector.java
@@ -238,7 +238,7 @@ abstract class ArrayVector<E extends Number> extends Vector 
implements CheckedCo
         }
 
         /**
-         * Returns the value cast as a {@code float}, since we may loose 
precision but the
+         * Returns the value cast as a {@code float}, since we may loss 
precision but the
          * result of the cast is not completely wrong (at worst we get zero of 
infinity values
          * if the magnitude of the {@code double} value was too small or too 
large).
          */
diff --git 
a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java 
b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
index 677458deaf..6cb292ca30 100644
--- a/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
+++ b/endorsed/src/org.apache.sis.util/main/org/apache/sis/math/Statistics.java
@@ -722,7 +722,7 @@ public class Statistics implements DoubleConsumer, 
LongConsumer, Cloneable, Seri
             if (last == (double) lastAsLong) {
                 /*
                  * 'lastAsLong' may have more precision than 'last' since the 
cast to the
-                 * 'double' type may loose some digits. Invoke the 
'delta.accept(long)' version.
+                 * 'double' type may loss some digits. Invoke the 
'delta.accept(long)' version.
                  */
                 delta.accept(sample - lastAsLong);
             } else {

Reply via email to