sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] 01/02: Support limited conversions between floating point numbers and fractions.
Date Mon, 10 Jun 2019 15:01:33 GMT
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

commit d30bbbf090a564f1ea3f53dae847c3dfa92d52c1
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Jun 10 15:22:02 2019 +0200

    Support limited conversions between floating point numbers and fractions.
---
 .../org/apache/sis/filter/ArithmeticFunction.java  |  37 +++---
 .../java/org/apache/sis/filter/BinaryFunction.java |  12 ++
 .../org/apache/sis/filter/ComparisonFunction.java  |   6 +-
 .../sis/internal/converter/NumberConverter.java    |   7 +-
 .../main/java/org/apache/sis/math/Fraction.java    | 126 +++++++++++++++++--
 .../java/org/apache/sis/math/MathFunctions.java    |  72 ++++++++---
 .../src/main/java/org/apache/sis/util/Numbers.java | 139 +++++++++++----------
 .../java/org/apache/sis/math/FractionTest.java     |  18 +++
 .../org/apache/sis/math/MathFunctionsTest.java     |  15 +++
 .../test/java/org/apache/sis/util/NumbersTest.java |  71 ++++++-----
 .../apache/sis/internal/netcdf/RasterResource.java |   2 +-
 11 files changed, 362 insertions(+), 143 deletions(-)

diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
index 1188df8..598ba82 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ArithmeticFunction.java
@@ -21,6 +21,9 @@ import java.math.BigInteger;
 import org.apache.sis.util.Numbers;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.internal.feature.FeatureExpression;
+import org.apache.sis.math.Fraction;
+
+// Branch-dependent imports
 import org.opengis.feature.AttributeType;
 import org.opengis.feature.FeatureType;
 import org.opengis.feature.PropertyType;
@@ -125,10 +128,11 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
         @Override protected char symbol() {return '+';}
 
         /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble (double     left, double     right) {return left + right;}
-        @Override protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {return left.add(right);}
-        @Override protected Number applyAsInteger(BigInteger left, BigInteger right) {return left.add(right);}
-        @Override protected Number applyAsLong   (long       left, long       right) {return Math.addExact(left, right);}
+        @Override protected Number applyAsDouble  (double     left, double     right) {return left + right;}
+        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.add(right);}
+        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.add(right);}
+        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.add(right);}
+        @Override protected Number applyAsLong    (long       left, long       right) {return Math.addExact(left, right);}
 
         /** Implementation of the visitor pattern. */
         @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
@@ -158,10 +162,11 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
         @Override protected char symbol() {return '−';}
 
         /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble (double     left, double     right) {return left - right;}
-        @Override protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {return left.subtract(right);}
-        @Override protected Number applyAsInteger(BigInteger left, BigInteger right) {return left.subtract(right);}
-        @Override protected Number applyAsLong   (long       left, long       right) {return Math.subtractExact(left, right);}
+        @Override protected Number applyAsDouble  (double     left, double     right) {return left - right;}
+        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.subtract(right);}
+        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.subtract(right);}
+        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.subtract(right);}
+        @Override protected Number applyAsLong    (long       left, long       right) {return Math.subtractExact(left, right);}
 
         /** Implementation of the visitor pattern. */
         @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
@@ -191,10 +196,11 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
         @Override protected char symbol() {return '×';}
 
         /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble (double     left, double     right) {return left * right;}
-        @Override protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {return left.multiply(right);}
-        @Override protected Number applyAsInteger(BigInteger left, BigInteger right) {return left.multiply(right);}
-        @Override protected Number applyAsLong   (long       left, long       right) {return Math.multiplyExact(left, right);}
+        @Override protected Number applyAsDouble  (double     left, double     right) {return left * right;}
+        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.multiply(right);}
+        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.multiply(right);}
+        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {return left.multiply(right);}
+        @Override protected Number applyAsLong    (long       left, long       right) {return Math.multiplyExact(left, right);}
 
         /** Implementation of the visitor pattern. */
         @Override public Object accept(ExpressionVisitor visitor, Object extraData) {
@@ -224,9 +230,10 @@ abstract class ArithmeticFunction extends BinaryFunction implements BinaryExpres
         @Override protected char symbol() {return '÷';}
 
         /** Applies this expression to the given operands. */
-        @Override protected Number applyAsDouble (double     left, double     right) {return left / right;}
-        @Override protected Number applyAsDecimal(BigDecimal left, BigDecimal right) {return left.divide(right);}
-        @Override protected Number applyAsInteger(BigInteger left, BigInteger right) {
+        @Override protected Number applyAsDouble  (double     left, double     right) {return left / right;}
+        @Override protected Number applyAsFraction(Fraction   left, Fraction   right) {return left.divide(right);}
+        @Override protected Number applyAsDecimal (BigDecimal left, BigDecimal right) {return left.divide(right);}
+        @Override protected Number applyAsInteger (BigInteger left, BigInteger right) {
             BigInteger[] r = left.divideAndRemainder(right);
             if (BigInteger.ZERO.equals(r[1])) {
                 return r[0];
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
index e119afe..d0e0b12 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/BinaryFunction.java
@@ -21,6 +21,7 @@ import java.util.Collection;
 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;
 import org.apache.sis.util.ArgumentChecks;
 
@@ -119,6 +120,10 @@ abstract class BinaryFunction extends Node {
                 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:
@@ -147,6 +152,13 @@ abstract class BinaryFunction extends Node {
     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}.
+     */
+    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}.
diff --git a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
index 0140c0d..2658729 100644
--- a/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
+++ b/core/sis-feature/src/main/java/org/apache/sis/filter/ComparisonFunction.java
@@ -23,6 +23,7 @@ import java.util.Calendar;
 import java.time.Instant;
 import java.time.OffsetDateTime;
 import java.time.chrono.ChronoLocalDate;
+import org.apache.sis.math.Fraction;
 import org.apache.sis.util.ArgumentChecks;
 
 // Branch-dependent imports
@@ -307,8 +308,9 @@ abstract class ComparisonFunction extends BinaryFunction implements BinaryCompar
     protected boolean compare(Instant left, Instant right) {return fromCompareTo(left.compareTo(right));}
 
     /** Delegates to {@link BigDecimal#compareTo(BigDecimal)} and interprets the result with {@link #fromCompareTo(int)}. */
-    @Override protected final Number applyAsDecimal(BigDecimal left, BigDecimal right) {return number(fromCompareTo(left.compareTo(right)));}
-    @Override protected final Number applyAsInteger(BigInteger left, BigInteger right) {return number(fromCompareTo(left.compareTo(right)));}
+    @Override protected final Number applyAsDecimal (BigDecimal left, BigDecimal right) {return number(fromCompareTo(left.compareTo(right)));}
+    @Override protected final Number applyAsInteger (BigInteger left, BigInteger right) {return number(fromCompareTo(left.compareTo(right)));}
+    @Override protected final Number applyAsFraction(Fraction   left, Fraction   right) {return number(fromCompareTo(left.compareTo(right)));}
 
 
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java b/core/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java
index d8b8ac7..023c6dc 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/internal/converter/NumberConverter.java
@@ -37,8 +37,9 @@ import org.apache.sis.util.resources.Errors;
  * value is converted. However performance is not the primary concern here, since those converters
  * will typically be used by code doing more costly work (e.g. the {@code sis-metadata} module
  * providing {@code Map} views using Java reflection). So we rather try to be more compact.
- * If nevertheless performance appears to be a problem, consider reverting to revision 1455255
- * of this class, which was using one subclass per target type as described above.
+ * If nevertheless performance appears to be a problem, consider reverting to revision
+ * {@code d73a10558dda4b41723d4f5652a792ae9c24f69e} (subversion: 1455255) of this class,
+ * which was using one subclass per target type as described above.
  *
  * <div class="section">Immutability and thread safety</div>
  * This class and all inner classes are immutable, and thus inherently thread-safe.
@@ -121,7 +122,7 @@ final class NumberConverter<S extends Number, T extends Number> extends SystemCo
              * in a (double → long) cast, in which case the difference should be smaller than 1.
              */
             final double delta = Math.abs(targetValue - sourceValue);
-            if (!(delta < 0.5)) { // Use '!' for catching NaN.
+            if (!(delta < 0.5)) {                                       // Use '!' for catching NaN.
                 if (delta < 1) {
                     target = Numbers.cast(Math.round(sourceValue), targetClass);
                 } else {
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java b/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
index e33eb30..e15e2e2 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
@@ -20,6 +20,7 @@ import java.io.Serializable;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.util.collection.WeakHashSet;
+import org.apache.sis.internal.util.Numerics;
 
 
 /**
@@ -73,6 +74,113 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
     }
 
     /**
+     * Converts the given IEEE 754 double-precision value to a fraction. If successful, this method returns a fraction
+     * such as {@link #doubleValue()} is equal to the given value in the sense of {@link Double#equals(Object)}:
+     * infinities, positive and negative zeros are preserved, but various NaN values are collapsed to a single NaN value.
+     *
+     * <div class="note"><b>Design note:</b>
+     * this method does not return approximated values because it is difficult to choose which fraction is best.
+     * For example choosing an approximated fraction for π value is quite arbitrary, and searching the fraction
+     * closer than any other fraction representable by this class is computationally expansive.
+     * Even with common fractions, the algorithm currently implemented in this class can detect that 1.6666666666666667
+     * {@linkplain Double#equals(Object) is equal to} 5⁄3 but can not detect easily that 1.66666666666666 (same number
+     * with two decimal digits dropped) is close to 5⁄3.</div>
+     *
+     * This method accepts only values between {@value Integer#MIN_VALUE} and {@value Integer#MAX_VALUE} inclusive,
+     * i.e. values in the range of 32-bits integers. If the given value has fraction digits, then the validity range
+     * will be smaller depending on the {@linkplain #denominator} required for representing that value.
+     *
+     * @param  value  the double-precision value to convert to a fraction.
+     * @return a fraction such as {@link #doubleValue()} is equals to the given value.
+     * @throws IllegalArgumentException if the given value can not be converted to a fraction.
+     *
+     * @since 1.0
+     */
+    public static Fraction valueOf(final double value) {
+        if (value == 0) {
+            return new Fraction(0, MathFunctions.isNegativeZero(value) ? -1 : +1);
+        }
+        if (!Double.isFinite(value)) {
+            return new Fraction(Double.isNaN(value) ? 0 : (value >= 0) ? 1 : -1, 0);
+        }
+        /*
+         * If the value has fraction digits, converting that value into a fraction requires that we assume a base,
+         * since the fraction denominator will be a multiple of that base. We will try base 2, 10, 3, 5, 7, 13, …,
+         * in that order. Base 2 is tried first because it is fast, produces exact results (if successful) and its
+         * implementation works with integer numbers too. Base 10 is tried next because it is likely to match user
+         * input. Then prime numbers are tried until `doubleValue()` produce a result identical to `value`. If no
+         * exact match is found, the best match will be taken.
+         */
+        long significand = Numerics.getSignificand(value);
+        int  exponent    = Math.getExponent(value) - Numerics.SIGNIFICAND_SIZE;                 // Power of 2.
+        int  shift       = Long.numberOfTrailingZeros(significand);
+        significand >>>= shift;
+        exponent      += shift;
+        if (exponent > -Integer.SIZE && exponent < Long.numberOfLeadingZeros(significand)) {
+            /*
+             * Build the fraction using arithmetic in base 2. This path is also executed for all integer values,
+             * because they have exact representation in base 2. We do not need to simplify the fraction because
+             * the denominator is always a power of 2 while the numerator is always odd; such fractions can not
+             * be simplified. Since we do not need to invoke `simplify(…)`, this is the fatest path.
+             */
+            final int den;
+            if (exponent >= 0) {
+                significand <<= exponent;
+                den = 1;
+            } else {
+                den = 1 << -exponent;
+            }
+            if ((significand & ~Integer.MAX_VALUE) == 0) {
+                if (value < 0) significand = -significand;
+                return new Fraction((int) significand, den);
+            }
+        } else {
+            /*
+             * Can not build the fraction using exact arithmetic in base 2. Try approximations using arithmetic in other bases,
+             * starting with base 10. We will multiply the numerator and denominator by the largest power of 10 (or other base)
+             * that can be used without causing an overflow, then simplify the fraction.
+             */
+            final double toMaximalSignificand = ((1L << Numerics.SIGNIFICAND_SIZE) - 1) / Math.ceil(Math.abs(value));
+            if (toMaximalSignificand > 1) {
+                exponent = Numerics.toExp10(Math.getExponent(toMaximalSignificand));                // Power of 10.
+                double factor = DecimalFunctions.pow10(exponent + 1);
+                if (factor > toMaximalSignificand) {
+                    factor = DecimalFunctions.pow10(exponent);
+                }
+                assert factor >= 1 && factor <= (1L << Numerics.SIGNIFICAND_SIZE) - 1 : factor;     // For use as denominator.
+                try {
+                    final Fraction f = simplify(null, Math.round(value * factor), Math.round(factor));
+                    if (f.doubleValue() == value) return f;
+                } catch (ArithmeticException e) {
+                    // Ignore. We will try other bases below.
+                }
+                /*
+                 * Arithmetic in base 10 failed too. Try prime numbers. This is the same approach
+                 * than the one we used for base 10 above, but slower because using more costly math.
+                 * The factor is:
+                 *                      factor = Bⁿ
+                 *
+                 * where n is the greatest integer such as factor ≦ toMaximalSignificand.
+                 */
+                final double logMaxFactor = Math.log(toMaximalSignificand);
+                for (int i=1; i<MathFunctions.PRIMES_LENGTH_16_BITS; i++) {
+                    final int base = MathFunctions.primeNumberAt(i);
+                    if (base > toMaximalSignificand) break;                             // Stop if exponent would be < 1.
+                    exponent = (int) (logMaxFactor / Math.log(base));                   // Power of base.
+                    final long den = MathFunctions.pow(base, exponent);
+                    try {
+                        final Fraction f = simplify(null, Math.round(value * den), den);
+                        if (f.doubleValue() == value) return f;
+                    } catch (ArithmeticException e) {
+                        // Ignore. More tries in the loop.
+                    }
+                }
+            }
+        }
+        throw new IllegalArgumentException(Errors.format(Errors.Keys.CanNotConvertValue_2, value, Fraction.class));
+    }
+
+    /**
      * Returns a unique fraction instance equals to {@code this}.
      * If this method has been invoked previously on another {@code Fraction} with the same value than {@code this},
      * then that previous instance is returned (provided that it has not yet been garbage collected). Otherwise this
@@ -96,7 +204,7 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
      * @return the simplest fraction equivalent to this fraction.
      */
     public Fraction simplify() {
-        return simplify(numerator, denominator);
+        return simplify(this, numerator, denominator);
     }
 
     /**
@@ -114,7 +222,7 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
      *
      * Above result still slightly smaller in magnitude than {@code Long.MIN_VALUE}.
      */
-    private Fraction simplify(long num, long den) {
+    private static Fraction simplify(final Fraction f, long num, long den) {
         if (num == Long.MIN_VALUE || den == Long.MIN_VALUE) {
             throw new ArithmeticException(Errors.format(Errors.Keys.IntegerOverflow_1, Long.SIZE));
         }
@@ -151,7 +259,7 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
                 }
             }
         }
-        return (num == numerator && den == denominator) ? this
+        return (f != null && num == f.numerator && den == f.denominator) ? f
                : new Fraction(Math.toIntExact(num), Math.toIntExact(den));
     }
 
@@ -186,7 +294,7 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
         // Intermediate result must be computed in a type wider that the 'numerator' and 'denominator' type.
         final long td = this .denominator;
         final long od = other.denominator;
-        return simplify(Math.addExact(od * numerator, td * other.numerator), od * td);
+        return simplify(this, Math.addExact(od * numerator, td * other.numerator), od * td);
     }
 
     /**
@@ -200,7 +308,7 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
         // Intermediate result must be computed in a type wider that the 'numerator' and 'denominator' type.
         final long td = this .denominator;
         final long od = other.denominator;
-        return simplify(Math.subtractExact(od * numerator, td * other.numerator), od * td);
+        return simplify(this, Math.subtractExact(od * numerator, td * other.numerator), od * td);
     }
 
     /**
@@ -211,8 +319,8 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
      * @throws ArithmeticException if the result overflows.
      */
     public Fraction multiply(final Fraction other) {
-        return simplify(numerator   * (long) other.numerator,
-                        denominator * (long) other.denominator);
+        return simplify(this, numerator   * (long) other.numerator,
+                              denominator * (long) other.denominator);
     }
 
     /**
@@ -223,8 +331,8 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
      * @throws ArithmeticException if the result overflows.
      */
     public Fraction divide(final Fraction other) {
-        return simplify(numerator   * (long) other.denominator,
-                        denominator * (long) other.numerator);
+        return simplify(this, numerator   * (long) other.denominator,
+                              denominator * (long) other.numerator);
     }
 
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
index 7b0e279..79ff346 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/MathFunctions.java
@@ -299,24 +299,47 @@ public final class MathFunctions extends Static {
     }
 
     /**
-     * Computes 10 raised to the power of <var>x</var>. Invoking this method is equivalent to invoking
-     * <code>{@linkplain Math#pow(double, double) Math.pow}(10, x)</code>, but is slightly more accurate
-     * in the special case where the given argument is an integer.
-     *
-     * @param  x  the exponent.
-     * @return 10 raised to the given exponent.
+     * Computes the result of {@code base} argument raised to the power given by {@code exponent} argument.
+     * This method computes the same value than {@link Math#pow(double, double)} but using only integer arithmetic.
+     * The result must be representable as a 64 bits integers ({@code long} primitive type),
+     * otherwise an {@link ArithmeticException} is thrown. The result is guaranteed exact,
+     * in contrast to results represented as {@code double} floating point values
+     * which may be approximate for magnitudes greater than 2<sup>52</sup>.
+     * This method may also be faster.
+     *
+     * <div class="note"><b>Implementation note:</b> this method uses
+     * <a href="https://en.wikipedia.org/wiki/Exponentiation_by_squaring">exponentiation by squaring</a> technic.</div>
+     *
+     * The type of the {@code base} argument is {@code long} for convenience, since this method is used in contexts
+     * where relatively large integers are handled. However any value greater than the capacity of {@code int} type
+     * is guaranteed to fail with {@link ArithmeticException} unless {@code exponent} is 0 or 1.
+     * Likewise any {@code exponent} value greater than 62 is guaranteed to fail unless {@code base} is 0 or 1.
+     *
+     * @param  base      the value to raise to an exponent.
+     * @param  exponent  the exponent, as zero or positive number.
+     * @return the value <var>base</var><sup><var>exponent</var></sup> as a 64 bits integer.
+     * @throws ArithmeticException if the given exponent is negative, or if the result overflow integer arithmetic.
      *
-     * @see #pow10(int)
      * @see Math#pow(double, double)
-     * @see Math#log10(double)
+     *
+     * @since 1.0
      */
-    public static double pow10(final double x) {
-        final int ix = (int) x;
-        if (ix == x) {
-            return DecimalFunctions.pow10(ix);
-        } else {
-            return Math.pow(10, x);
+    public static long pow(long base, int exponent) {
+        long result = 1;
+        if (exponent >= 1) {
+            if ((exponent & 1) != 0) {
+                result = base;
+            }
+            while ((exponent >>>= 1) != 0) {
+                base = Math.multiplyExact(base, base);
+                if ((exponent & 1) != 0) {
+                    result = Math.multiplyExact(result, base);
+                }
+            }
+        } else if (exponent < 0) {
+            throw new ArithmeticException(Errors.format(Errors.Keys.NegativeArgument_2, "exponent", exponent));
         }
+        return result;
     }
 
     /**
@@ -349,6 +372,27 @@ public final class MathFunctions extends Static {
     }
 
     /**
+     * Computes 10 raised to the power of <var>x</var>. Invoking this method is equivalent to invoking
+     * <code>{@linkplain Math#pow(double, double) Math.pow}(10, x)</code>, but is slightly more accurate
+     * in the special case where the given argument is an integer.
+     *
+     * @param  x  the exponent.
+     * @return 10 raised to the given exponent.
+     *
+     * @see #pow10(int)
+     * @see Math#pow(double, double)
+     * @see Math#log10(double)
+     */
+    public static double pow10(final double x) {
+        final int ix = (int) x;
+        if (ix == x) {
+            return DecimalFunctions.pow10(ix);
+        } else {
+            return Math.pow(10, x);
+        }
+    }
+
+    /**
      * Returns the inverse hyperbolic sine of the given value.
      * This is the inverse of the {@link Math#sinh(double)} method.
      *
diff --git a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
index 3030fae..eea7c88 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/util/Numbers.java
@@ -27,6 +27,7 @@ import java.util.Collections;
 import java.lang.reflect.Array;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import org.apache.sis.math.Fraction;
 import org.apache.sis.util.resources.Errors;
 import org.apache.sis.internal.util.Numerics;
 import org.apache.sis.internal.util.CollectionsExt;
@@ -38,7 +39,7 @@ import static java.lang.Double.doubleToLongBits;
  * Static methods working with {@link Number} objects, and a few primitive types by extension.
  *
  * @author  Martin Desruisseaux (IRD, Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see org.apache.sis.math.MathFunctions
  *
@@ -52,19 +53,21 @@ import static java.lang.Double.doubleToLongBits;
 public final class Numbers extends Static {
     /**
      * Constant of value {@value} used in {@code switch} statements or as index in arrays.
-     * This enumeration provides the following guarantees (some Apache SIS code rely on them):
+     * This enumeration provides the following guarantees (some Apache SIS codes rely on them):
      *
      * <ul>
      *   <li>{@code OTHER} value is 0.</li>
-     *   <li>Primitive types are enumerated in this exact order (from lower value to higher value):
+     *   <li>Primitive types are enumerated in this exact order
+     *       (from lower value to higher value, but not necessarily as consecutive values):
      *       {@code BYTE}, {@code SHORT}, {@code INTEGER}, {@code LONG}, {@code FLOAT}, {@code DOUBLE}.</li>
-     *   <li>{@link java.math} types of greater capacity that primitive types ({@code BIG_DECIMAL}
+     *   <li>{@link java.math} types of greater capacity than primitive types ({@code BIG_DECIMAL}
      *       and {@code BIG_INTEGER}) have higher enumeration values.</li>
+     *   <li>{@link Fraction} is considered as a kind of floating point value.</li>
      * </ul>
      */
     public static final byte
-            BIG_DECIMAL=10, BIG_INTEGER=9,
-            DOUBLE=8, FLOAT=7, LONG=6, INTEGER=5, SHORT=4, BYTE=3, CHARACTER=2, BOOLEAN=1, OTHER=0;
+            BIG_DECIMAL=11, BIG_INTEGER=10, FRACTION=7,
+            DOUBLE=9, FLOAT=8, LONG=6, INTEGER=5, SHORT=4, BYTE=3, CHARACTER=2, BOOLEAN=1, OTHER=0;
 
     /**
      * Mapping between a primitive type and its wrapper, if any.
@@ -74,12 +77,13 @@ public final class Numbers extends Static {
      * behavior since {@code Class} is final and does not override the {@code equals(Object)} and {@code hashCode()}
      * methods. The {@code IdentityHashMap} Javadoc claims that it is faster than the regular {@code HashMap}.
      * But maybe the most interesting property is that it allocates less objects since {@code IdentityHashMap}
-     * implementation doesn't need the chain of objects created by {@code HashMap}.</div>
+     * implementation does not need the chain of objects created by {@code HashMap}.</div>
      */
-    private static final Map<Class<?>,Numbers> MAPPING = new IdentityHashMap<>(11);
+    private static final Map<Class<?>,Numbers> MAPPING = new IdentityHashMap<>(12);
     static {
         new Numbers(BigDecimal.class, true, false, BIG_DECIMAL);
         new Numbers(BigInteger.class, false, true, BIG_INTEGER);
+        new Numbers(Fraction  .class, true, false, FRACTION);
         new Numbers(Double   .TYPE, Double   .class, true,  false, (byte) Double   .SIZE, DOUBLE,    'D', Numerics .valueOf(Double.NaN));
         new Numbers(Float    .TYPE, Float    .class, true,  false, (byte) Float    .SIZE, FLOAT,     'F', Float    .valueOf(Float .NaN));
         new Numbers(Long     .TYPE, Long     .class, false, true,  (byte) Long     .SIZE, LONG,      'J', Long     .valueOf(        0L));
@@ -110,10 +114,10 @@ public final class Numbers extends Static {
         this.isInteger = isInteger;
         this.size      = -1;
         this.ordinal   = ordinal;
-        this.internal  = 'L'; // Defined by Java, and tested elsewhere in this class.
+        this.internal  = 'L';                           // Defined by Java, and tested elsewhere in this class.
         this.nullValue = null;
         if (MAPPING.put(type, this) != null) {
-            throw new AssertionError(); // Should never happen.
+            throw new AssertionError();                 // Should never happen.
         }
     }
 
@@ -135,7 +139,7 @@ public final class Numbers extends Static {
         this.internal  = internal;
         this.nullValue = nullValue;
         if (MAPPING.put(primitive, this) != null || MAPPING.put(wrapper, this) != null) {
-            throw new AssertionError(); // Should never happen.
+            throw new AssertionError();                         // Should never happen.
         }
     }
 
@@ -149,6 +153,7 @@ public final class Numbers extends Static {
     /**
      * Returns {@code true} if the given {@code type} is a floating point type. The floating point types
      * are {@link Float}, {@code float}, {@link Double}, {@code double} and {@link BigDecimal}.
+     * {@link Fraction} is also considered as a kind of floating point values.
      *
      * @param  type  the primitive type or wrapper class to test (can be {@code null}).
      * @return {@code true} if {@code type} is one of the known types capable to represent floating point numbers.
@@ -181,7 +186,7 @@ public final class Numbers extends Static {
      *
      * @param  type  the primitive type (can be {@code null}).
      * @return the number of bits, or 0 if {@code type} is null.
-     * @throws IllegalArgumentException if the given type is unknown.
+     * @throws IllegalArgumentException if the given type is not one of the types supported by this {@code Numbers} class.
      */
     public static int primitiveBitCount(final Class<?> type) throws IllegalArgumentException {
         final Numbers mapping = MAPPING.get(type);
@@ -190,8 +195,7 @@ public final class Numbers extends Static {
             if (size >= 0) {
                 return size;
             }
-        }
-        if (type == null) {
+        } else if (type == null) {
             return 0;
         }
         throw unknownType(type);
@@ -228,46 +232,44 @@ public final class Numbers extends Static {
     /**
      * Returns the widest type of two numbers. Numbers {@code n1} and {@code n2} can be instance of
      * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double},
-     * {@link BigInteger} or {@link BigDecimal} types.
+     * {@link Fraction}, {@link BigInteger} or {@link BigDecimal} types.
      *
      * <p>If one of the given argument is null, then this method returns the class of the non-null argument.
      * If both arguments are null, then this method returns {@code null}.</p>
      *
      * @param  n1  the first number, or {@code null}.
      * @param  n2  the second number, or {@code null}.
-     * @return the widest type of the given numbers, or {@code null} if not {@code n1} and {@code n2} are null.
-     * @throws IllegalArgumentException if a number is not of a known type.
+     * @return the widest type of the given numbers, or {@code null} if both {@code n1} and {@code n2} are null.
+     * @throws IllegalArgumentException if a number is not an instance of a supported type.
      *
      * @see #widestClass(Number, Number)
      * @see #narrowestClass(Number, Number)
      */
-    public static Class<? extends Number> widestClass(final Number n1, final Number n2)
-            throws IllegalArgumentException
-    {
+    public static Class<? extends Number> widestClass(final Number n1, final Number n2) throws IllegalArgumentException {
         return widestClass((n1 != null) ? n1.getClass() : null,
                            (n2 != null) ? n2.getClass() : null);
     }
 
     /**
      * Returns the widest of the given types. Classes {@code c1} and {@code c2} can be
-     * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float},
-     * {@link Double}, {@link BigInteger} or {@link BigDecimal} types.
+     * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double},
+     * {@link Fraction}, {@link BigInteger} or {@link BigDecimal} types.
      *
      * <p>If one of the given argument is null, then this method returns the non-null argument.
      * If both arguments are null, then this method returns {@code null}.</p>
      *
-     * Example:
+     * <div class="note"><b>Example:</b>
+     * in the following code, {@code type} is set to {@code Long.class}:
      *
      * {@preformat java
-     *     widestClass(Short.class, Long.class);
+     *     Class<?> type = widestClass(Short.class, Long.class);
      * }
-     *
-     * returns {@code Long.class}.
+     * </div>
      *
      * @param  c1  the first number type, or {@code null}.
      * @param  c2  the second number type, or {@code null}.
      * @return the widest of the given types, or {@code null} if both {@code c1} and {@code c2} are null.
-     * @throws IllegalArgumentException if one of the given types is unknown.
+     * @throws IllegalArgumentException if one of the given types is not supported by this {@code Numbers} class.
      *
      * @see #widestClass(Class, Class)
      * @see #narrowestClass(Number, Number)
@@ -290,14 +292,14 @@ public final class Numbers extends Static {
     }
 
     /**
-     * Returns the narrowest type of two numbers. Numbers {@code n1} and {@code n2} must be instance
-     * of any of {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}
-     * {@link Double}, {@link BigInteger} or {@link BigDecimal} types.
+     * Returns the narrowest type of two numbers. Numbers {@code n1} and {@code n2} can be instance of
+     * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double},
+     * {@link Fraction}, {@link BigInteger} or {@link BigDecimal} types.
      *
-     * @param  n1  the first number.
-     * @param  n2  the second number.
-     * @return the narrowest type of the given numbers.
-     * @throws IllegalArgumentException if a number is not of a known type.
+     * @param  n1  the first number, or {@code null}.
+     * @param  n2  the second number, or {@code null}.
+     * @return the narrowest type of the given numbers, or {@code null} if both {@code n1} and {@code n2} are null.
+     * @throws IllegalArgumentException if a number is not an instance of a supported type.
      *
      * @see #narrowestClass(Class, Class)
      * @see #widestClass(Class, Class)
@@ -311,24 +313,24 @@ public final class Numbers extends Static {
 
     /**
      * Returns the narrowest of the given types. Classes {@code c1} and {@code c2} can be
-     * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float},
-     * {@link Double}, {@link BigInteger} or {@link BigDecimal} types.
+     * {@link Byte}, {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double},
+     * {@link Fraction}, {@link BigInteger} or {@link BigDecimal} types.
      *
      * <p>If one of the given argument is null, then this method returns the non-null argument.
      * If both arguments are null, then this method returns {@code null}.</p>
      *
-     * Example:
+     * <div class="note"><b>Example:</b>
+     * in the following code, {@code type} is set to {@code Short.class}:
      *
      * {@preformat java
-     *     narrowestClass(Short.class, Long.class);
+     *     Class<?> type = widestClass(Short.class, Long.class);
      * }
-     *
-     * returns {@code Short.class}.
+     * </div>
      *
      * @param  c1  the first number type, or {@code null}.
      * @param  c2  the second number type, or {@code null}.
      * @return the narrowest of the given types, or {@code null} if both {@code c1} and {@code c2} are null.
-     * @throws IllegalArgumentException if one of the given types is unknown.
+     * @throws IllegalArgumentException if one of the given types is not supported by this {@code Numbers} class.
      *
      * @see #narrowestClass(Number, Number)
      * @see #widestClass(Class, Class)
@@ -356,9 +358,9 @@ public final class Numbers extends Static {
      *
      * <ul>
      *   <li>If the given value is {@code null}, then this method returns {@code null}.</li>
-     *   <li>Otherwise if the given value can not be casted from {@code double} to an other type
+     *   <li>Otherwise if the given value can not be casted from {@code double} to another type
      *       without precision lost, return {@code Double.class}.</li>
-     *   <li>Otherwise if the given value can not be casted from {@code float} to an other type
+     *   <li>Otherwise if the given value can not be casted from {@code float} to another type
      *       without precision lost, return {@code Float.class}.</li>
      *   <li>Otherwise if the given value is between {@value java.lang.Byte#MIN_VALUE} and
      *       {@value java.lang.Byte#MAX_VALUE}, then this method returns {@code Byte.class};</li>
@@ -472,8 +474,10 @@ public final class Numbers extends Static {
      * @see #narrowestNumber(Number)
      */
     public static Number narrowestNumber(final String value) throws NumberFormatException {
-        // Do not trim whitespaces. It is up to the caller to do that if he wants.
-        // For such low level function, we are better to avoid hidden initiative.
+        /*
+         * Do not trim whitespaces. It is up to the caller to do that if he wants.
+         * For such low level function, we are better to avoid hidden initiative.
+         */
         final int length = value.length();
         for (int i=0; i<length; i++) {
             final char c = value.charAt(i);
@@ -487,7 +491,7 @@ public final class Numbers extends Static {
     /**
      * Casts a number to the specified type. The target type can be one of {@link Byte},
      * {@link Short}, {@link Integer}, {@link Long}, {@link Float}, {@link Double},
-     * {@link BigInteger} or {@link BigDecimal}.
+     * {@link Fraction}, {@link BigInteger} or {@link BigDecimal}.
      * This method makes the following choice:
      *
      * <ul>
@@ -509,22 +513,22 @@ public final class Numbers extends Static {
      * @param  number  the number to cast, or {@code null}.
      * @param  type    the destination type.
      * @return the number casted to the given type, or {@code null} if the given value was null.
-     * @throws IllegalArgumentException if the given type is not one of the primitive wrappers for numeric types.
+     * @throws IllegalArgumentException if the given type is not supported by this {@code Numbers} class,
+     *         or the type is {@link #FRACTION} and the given number can not be converted to that type.
      */
     @SuppressWarnings("unchecked")
-    public static <N extends Number> N cast(final Number number, final Class<N> type)
-            throws IllegalArgumentException
-    {
+    public static <N extends Number> N cast(final Number number, final Class<N> type) throws IllegalArgumentException {
         if (number == null || number.getClass() == type) {
             return (N) number;
         }
         switch (getEnumConstant(type)) {
-            case BYTE:    return (N) Byte    .valueOf(number.  byteValue());
-            case SHORT:   return (N) Short   .valueOf(number. shortValue());
-            case INTEGER: return (N) Integer .valueOf(number.   intValue());
-            case LONG:    return (N) Long    .valueOf(number.  longValue());
-            case FLOAT:   return (N) Float   .valueOf(number. floatValue());
-            case DOUBLE:  return (N) Numerics.valueOf(number.doubleValue());
+            case BYTE:     return (N) Byte    .valueOf(number.  byteValue());
+            case SHORT:    return (N) Short   .valueOf(number. shortValue());
+            case INTEGER:  return (N) Integer .valueOf(number.   intValue());
+            case LONG:     return (N) Long    .valueOf(number.  longValue());
+            case FLOAT:    return (N) Float   .valueOf(number. floatValue());
+            case DOUBLE:   return (N) Numerics.valueOf(number.doubleValue());
+            case FRACTION: return (N) Fraction.valueOf(number.doubleValue());
             case BIG_INTEGER: {
                 final BigInteger c;
                 if (number instanceof BigInteger) {
@@ -565,7 +569,7 @@ public final class Numbers extends Static {
     /**
      * Wraps the given floating-point value in a {@code Number} of the specified class.
      * The given type shall be one of {@link Byte}, {@link Short}, {@link Integer}, {@link Long},
-     * {@link Float}, {@link Double}, {@link BigInteger} and {@link BigDecimal} classes.
+     * {@link Float}, {@link Double}, {@link Fraction}, {@link BigInteger} and {@link BigDecimal} classes.
      * Furthermore, the given value shall be convertible to the given class without precision lost,
      * otherwise an {@link IllegalArgumentException} will be thrown.
      *
@@ -573,13 +577,11 @@ public final class Numbers extends Static {
      * @param  value  the value to wrap.
      * @param  type   the desired wrapper class.
      * @return the value wrapped in an object of the given class.
-     * @throws IllegalArgumentException if the given type is not one of the primitive wrappers for numeric types,
+     * @throws IllegalArgumentException if the given type is not supported by this {@code Numbers} class,
      *         or if the given value can not be wrapped in an instance of the given class without precision lost.
      */
     @SuppressWarnings("unchecked")
-    public static <N extends Number> N wrap(final double value, final Class<N> type)
-            throws IllegalArgumentException
-    {
+    public static <N extends Number> N wrap(final double value, final Class<N> type) throws IllegalArgumentException {
         final N number;
         switch (getEnumConstant(type)) {
             case BYTE:        number = (N) Byte      .valueOf((byte)  value); break;
@@ -588,6 +590,7 @@ public final class Numbers extends Static {
             case LONG:        number = (N) Long      .valueOf((long)  value); break;
             case FLOAT:       number = (N) Float     .valueOf((float) value); break;
             case DOUBLE:      return   (N) Numerics  .valueOf(value); // No need to verify.
+            case FRACTION:    return   (N) Fraction  .valueOf(value); // No need to verify.
             case BIG_INTEGER: number = (N) BigInteger.valueOf((long) value); break;
             case BIG_DECIMAL: return   (N) BigDecimal.valueOf(value); // No need to verify.
             default: throw unknownType(type);
@@ -601,7 +604,7 @@ public final class Numbers extends Static {
     /**
      * Wraps the given integer value in a {@code Number} of the specified class.
      * The given type shall be one of {@link Byte}, {@link Short}, {@link Integer}, {@link Long},
-     * {@link Float}, {@link Double}, {@link BigInteger} and {@link BigDecimal} classes.
+     * {@link Float}, {@link Double}, {@link Fraction}, {@link BigInteger} and {@link BigDecimal} classes.
      * Furthermore, the given value shall be convertible to the given class without precision lost,
      * otherwise an {@link IllegalArgumentException} will be thrown.
      *
@@ -609,15 +612,13 @@ public final class Numbers extends Static {
      * @param  value  the value to wrap.
      * @param  type   the desired wrapper class.
      * @return the value wrapped in an object of the given class.
-     * @throws IllegalArgumentException if the given type is not one of the primitive wrappers for numeric types,
+     * @throws IllegalArgumentException if the given type is not supported by this {@code Numbers} class,
      *         or if the given value can not be wrapped in an instance of the given class without precision lost.
      *
      * @since 0.8
      */
     @SuppressWarnings("unchecked")
-    public static <N extends Number> N wrap(final long value, final Class<N> type)
-            throws IllegalArgumentException
-    {
+    public static <N extends Number> N wrap(final long value, final Class<N> type) throws IllegalArgumentException {
         final N number;
         switch (getEnumConstant(type)) {
             case BYTE:        number = (N) Byte      .valueOf((byte)   value); break;
@@ -626,6 +627,7 @@ public final class Numbers extends Static {
             case LONG:        return   (N) Long      .valueOf(value);  // No need to verify.
             case FLOAT:       number = (N) Float     .valueOf((float)  value); break;
             case DOUBLE:      number = (N) Numerics  .valueOf((double) value); break;
+            case FRACTION:    number = (N) new Fraction      ((int) value, 1); break;
             case BIG_INTEGER: return   (N) BigInteger.valueOf(value);  // No need to verify.
             case BIG_DECIMAL: return   (N) BigDecimal.valueOf(value);  // No need to verify.
             default: throw unknownType(type);
@@ -638,7 +640,7 @@ public final class Numbers extends Static {
 
     /**
      * Converts the specified string into a value object.
-     * The value object can be an instance of {@link BigDecimal}, {@link BigInteger},
+     * The value object can be an instance of {@link BigDecimal}, {@link BigInteger}, {@link Fraction},
      * {@link Double}, {@link Float}, {@link Long}, {@link Integer}, {@link Short}, {@link Byte},
      * {@link Boolean}, {@link Character} or {@link String} according the specified type.
      * This method makes the following choice:
@@ -686,6 +688,7 @@ public final class Numbers extends Static {
             case LONG:        return (T) Long   .valueOf(value);
             case FLOAT:       return (T) Float  .valueOf(value);
             case DOUBLE:      return (T) Double .valueOf(value);
+            case FRACTION:    return (T) new Fraction  (value);
             case BIG_INTEGER: return (T) new BigInteger(value);
             case BIG_DECIMAL: return (T) new BigDecimal(value);
             default: throw unknownType(type);
@@ -752,7 +755,7 @@ public final class Numbers extends Static {
 
     /**
      * Returns a numeric constant for the given type.
-     * The constants are {@link #BIG_DECIMAL}, {@link #BIG_INTEGER},
+     * The constants are {@link #BIG_DECIMAL}, {@link #BIG_INTEGER}, {@link #FRACTION},
      * {@link #DOUBLE}, {@link #FLOAT}, {@link #LONG}, {@link #INTEGER},
      * {@link #SHORT}, {@link #BYTE}, {@link #CHARACTER}, {@link #BOOLEAN}, or {@link #OTHER}
      * constants for the given type. This is a commodity for usage in {@code switch} statements.
diff --git a/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java b/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
index 598f55e..242eb99 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
@@ -134,6 +134,24 @@ public final strictfp class FractionTest extends TestCase {
     }
 
     /**
+     * Tests the {@link Fraction#valueOf(double)} method.
+     */
+    @Test
+    public void testValueOfDouble() {
+        assertEquals(new Fraction(  0,  1), Fraction.valueOf(0));
+        assertEquals(new Fraction(  0, -1), Fraction.valueOf(-0d));
+        assertEquals(new Fraction(100,  1), Fraction.valueOf(100));
+        assertEquals(new Fraction(256,  1), Fraction.valueOf(256));
+        assertEquals(new Fraction(-27,  2), Fraction.valueOf(-13.5));
+        assertEquals(new Fraction(  1,  8), Fraction.valueOf(  0.125));
+        assertEquals(new Fraction( -1,  8), Fraction.valueOf( -0.125));
+        assertEquals(new Fraction(  1, 10), Fraction.valueOf(  0.1));
+        assertEquals(new Fraction( -1, 50), Fraction.valueOf( -0.02));
+        assertEquals(new Fraction( -5,  3), Fraction.valueOf(-5/3d));
+        assertEquals(new Fraction(  8, 27), Fraction.valueOf(8/27d));
+    }
+
+    /**
      * Tests the {@link Fraction#toString()} method.
      */
     @Test
diff --git a/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java b/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
index 5e6b35f..210e6f0 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/math/MathFunctionsTest.java
@@ -16,8 +16,10 @@
  */
 package org.apache.sis.math;
 
+import java.util.Random;
 import org.junit.Test;
 import org.apache.sis.test.TestCase;
+import org.apache.sis.test.TestUtilities;
 import org.apache.sis.test.DependsOn;
 import org.apache.sis.test.DependsOnMethod;
 
@@ -147,6 +149,19 @@ public final strictfp class MathFunctionsTest extends TestCase {
     }
 
     /**
+     * Tests the {@link MathFunctions#pow(long, int)} method.
+     */
+    @Test
+    public void testPowInteger() {
+        final Random r = TestUtilities.createRandomNumberGenerator();
+        for (int i=0; i<100; i++) {
+            final long base = r.nextInt(21) - 10;
+            final int exponent = r.nextInt(15);
+            assertEquals(StrictMath.pow(base, exponent), pow(base, exponent), STRICT);
+        }
+    }
+
+    /**
      * Tests the {@link MathFunctions#asinh(double)} method in the [-10 … +10] range.
      */
     @Test
diff --git a/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java b/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java
index 370c737..fcc73e9 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/util/NumbersTest.java
@@ -16,8 +16,11 @@
  */
 package org.apache.sis.util;
 
-import org.junit.Test;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import org.apache.sis.math.Fraction;
 import org.apache.sis.test.TestCase;
+import org.junit.Test;
 
 import static org.junit.Assert.*;
 import static org.apache.sis.util.Numbers.*;
@@ -27,7 +30,7 @@ import static org.apache.sis.util.Numbers.*;
  * Tests the {@link Numbers} static methods.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.3
+ * @version 1.0
  * @since   0.3
  * @module
  */
@@ -37,20 +40,23 @@ public final strictfp class NumbersTest extends TestCase {
      */
     @Test
     public void testIsInteger() {
-        assertTrue (isInteger(Byte     .TYPE));
-        assertTrue (isInteger(Short    .TYPE));
-        assertTrue (isInteger(Integer  .TYPE));
-        assertTrue (isInteger(Long     .TYPE));
-        assertFalse(isInteger(Float    .TYPE));
-        assertFalse(isInteger(Double   .TYPE));
-        assertTrue (isInteger(Byte     .class));
-        assertTrue (isInteger(Short    .class));
-        assertTrue (isInteger(Integer  .class));
-        assertTrue (isInteger(Long     .class));
-        assertFalse(isInteger(Float    .class));
-        assertFalse(isInteger(Double   .class));
-        assertFalse(isInteger(String   .class));
-        assertFalse(isInteger(Character.class));
+        assertTrue (isInteger(Byte      .TYPE));
+        assertTrue (isInteger(Short     .TYPE));
+        assertTrue (isInteger(Integer   .TYPE));
+        assertTrue (isInteger(Long      .TYPE));
+        assertFalse(isInteger(Float     .TYPE));
+        assertFalse(isInteger(Double    .TYPE));
+        assertTrue (isInteger(Byte      .class));
+        assertTrue (isInteger(Short     .class));
+        assertTrue (isInteger(Integer   .class));
+        assertTrue (isInteger(Long      .class));
+        assertFalse(isInteger(Float     .class));
+        assertFalse(isInteger(Double    .class));
+        assertFalse(isInteger(String    .class));
+        assertFalse(isInteger(Character .class));
+        assertFalse(isInteger(Fraction  .class));
+        assertTrue (isInteger(BigInteger.class));
+        assertFalse(isInteger(BigDecimal.class));
     }
 
     /**
@@ -58,20 +64,23 @@ public final strictfp class NumbersTest extends TestCase {
      */
     @Test
     public void testIsFloat() {
-        assertFalse(isFloat(Byte     .TYPE));
-        assertFalse(isFloat(Short    .TYPE));
-        assertFalse(isFloat(Integer  .TYPE));
-        assertFalse(isFloat(Long     .TYPE));
-        assertTrue (isFloat(Float    .TYPE));
-        assertTrue (isFloat(Double   .TYPE));
-        assertFalse(isFloat(Byte     .class));
-        assertFalse(isFloat(Short    .class));
-        assertFalse(isFloat(Integer  .class));
-        assertFalse(isFloat(Long     .class));
-        assertTrue (isFloat(Float    .class));
-        assertTrue (isFloat(Double   .class));
-        assertFalse(isFloat(String   .class));
-        assertFalse(isFloat(Character.class));
+        assertFalse(isFloat(Byte      .TYPE));
+        assertFalse(isFloat(Short     .TYPE));
+        assertFalse(isFloat(Integer   .TYPE));
+        assertFalse(isFloat(Long      .TYPE));
+        assertTrue (isFloat(Float     .TYPE));
+        assertTrue (isFloat(Double    .TYPE));
+        assertFalse(isFloat(Byte      .class));
+        assertFalse(isFloat(Short     .class));
+        assertFalse(isFloat(Integer   .class));
+        assertFalse(isFloat(Long      .class));
+        assertTrue (isFloat(Float     .class));
+        assertTrue (isFloat(Double    .class));
+        assertFalse(isFloat(String    .class));
+        assertFalse(isFloat(Character .class));
+        assertTrue (isFloat(Fraction  .class));
+        assertFalse(isFloat(BigInteger.class));
+        assertTrue (isFloat(BigDecimal.class));
     }
 
     /**
@@ -187,7 +196,7 @@ public final strictfp class NumbersTest extends TestCase {
      */
     @Test
     public void testCast() {
-        @SuppressWarnings("UnnecessaryBoxing")
+        @SuppressWarnings({"deprecation", "UnnecessaryBoxing"})
         final Integer value = new Integer(10); // Intentionally a new instance.
         assertEquals(Byte   .valueOf((byte)   10), cast(value, Byte   .class));
         assertEquals(Short  .valueOf((short)  10), cast(value, Short  .class));
diff --git a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
index d88adc0..55443ce 100644
--- a/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
+++ b/storage/sis-netcdf/src/main/java/org/apache/sis/internal/netcdf/RasterResource.java
@@ -423,7 +423,7 @@ public final class RasterResource extends AbstractGridResource implements Resour
                     isMaxIncluded = isMinIncluded;
                     isMinIncluded = sb;
                 }
-                if (band.getDataType().number < Numbers.FLOAT && minimum >= Long.MIN_VALUE && maximum <= Long.MAX_VALUE) {
+                if (band.getDataType().number <= Numbers.LONG && minimum >= Long.MIN_VALUE && maximum <= Long.MAX_VALUE) {
                     range = NumberRange.create(Math.round(minimum), isMinIncluded, Math.round(maximum), isMaxIncluded);
                 } else {
                     range = NumberRange.create(minimum, isMinIncluded, maximum, isMaxIncluded);


Mime
View raw message