sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1764996 - in /sis/branches/JDK8/core/sis-utility/src: main/java/org/apache/sis/internal/converter/ main/java/org/apache/sis/math/ main/java/org/apache/sis/measure/ main/resources/META-INF/services/ test/java/org/apache/sis/math/ test/java/...
Date Fri, 14 Oct 2016 22:47:14 GMT
Author: desruisseaux
Date: Fri Oct 14 22:47:14 2016
New Revision: 1764996

URL: http://svn.apache.org/viewvc?rev=1764996&view=rev
Log:
Begin custom implementation of JSR-363 (Units of measurement).

Added:
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java   (with props)
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java   (with props)
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java   (with props)
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java   (with props)
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java   (with props)
Modified:
    sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java

Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java?rev=1764996&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java (added)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java [UTF-8] Fri Oct 14 22:47:14 2016
@@ -0,0 +1,126 @@
+/*
+ * 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.internal.converter;
+
+import java.util.Set;
+import org.apache.sis.math.Fraction;
+import org.apache.sis.math.FunctionProperty;
+import org.apache.sis.util.ObjectConverter;
+import org.apache.sis.util.UnconvertibleObjectException;
+import org.apache.sis.util.resources.Errors;
+
+
+/**
+ * Handles conversions from {@link Fraction} to other kind of numbers.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+public final class FractionConverter extends SystemConverter<Fraction,Integer> {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 862676960870569201L;
+
+    /**
+     * The unique instance of this converter.
+     */
+    public static final FractionConverter INSTANCE = new FractionConverter();
+
+    /**
+     * Creates a new converter. Only one instance is enough, but this constructor
+     * needs to be public for allowing invocation by {@link java.util.ServiceLoader}.
+     */
+    public FractionConverter() {
+        super(Fraction.class, Integer.class);
+    }
+
+    /**
+     * Returns the unique instance of this converter.
+     *
+     * @return the unique instance of this converter.
+     */
+    @Override
+    public ObjectConverter<Fraction,Integer> unique() {
+        return INSTANCE;
+    }
+
+    /**
+     * Declares that this converter is injective, surjective, invertible and preserve order.
+     *
+     * @return the properties of this bijective converter.
+     */
+    @Override
+    public Set<FunctionProperty> properties() {
+        return bijective();
+    }
+
+    /**
+     * Converts the given fraction to an integer.
+     *
+     * @param  value  the fraction to convert.
+     * @return the given fraction as an integer.
+     * @throws UnconvertibleObjectException if the given fraction is not an integer.
+     */
+    @Override
+    public Integer apply(final Fraction value) throws UnconvertibleObjectException {
+        if ((value.numerator % value.denominator) == 0) {
+            return value.numerator / value.denominator;
+        }
+        throw new UnconvertibleObjectException(Errors.format(Errors.Keys.CanNotConvertValue_2, value, Integer.class));
+    }
+
+    /**
+     * Returns the converter from integers to fractions.
+     *
+     * @return the inverse converter.
+     */
+    @Override
+    public ObjectConverter<Integer,Fraction> inverse() {
+        return FromInteger.INSTANCE;
+    }
+
+    /**
+     * The inverse of the {@link FractionConverter}.
+     */
+    public static final class FromInteger extends SystemConverter<Integer,Fraction> {
+        /**
+         * For cross-version compatibility.
+         */
+        private static final long serialVersionUID = 7411811921783941007L;
+
+        /**
+         * The unique instance of this converter.
+         */
+        public static final FromInteger INSTANCE = new FromInteger();
+
+        /**
+         * Creates a new converter. Only one instance is enough, but this constructor
+         * needs to be public for allowing invocation by {@link java.util.ServiceLoader}.
+         */
+        public FromInteger() {
+            super(Integer.class, Fraction.class);
+        }
+
+        @Override public ObjectConverter<Integer,Fraction> unique()  {return INSTANCE;}
+        @Override public ObjectConverter<Fraction,Integer> inverse() {return FractionConverter.INSTANCE;}
+        @Override public Set<FunctionProperty> properties()          {return bijective();}
+        @Override public Fraction apply(Integer value)               {return new Fraction(value, 1);}
+    }
+}

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/converter/FractionConverter.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java?rev=1764996&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java (added)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java [UTF-8] Fri Oct 14 22:47:14 2016
@@ -0,0 +1,436 @@
+/*
+ * 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.math;
+
+import java.io.Serializable;
+import org.apache.sis.util.collection.WeakHashSet;
+
+
+/**
+ * A value class for rational numbers. {@code Fraction} objects are represented by a {@linkplain #numerator} and
+ * a {@linkplain #denominator} stored at 32 bits integers. Fractions can be {@linkplain #simplify() simplified}.
+ * All {@code Fraction} instances are immutable and thus inherently thread-safe.
+ *
+ * @author  Martin Desruisseaux (MPO, Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+public final class Fraction extends Number implements Comparable<Fraction>, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = -4501644254763471216L;
+
+    /**
+     * Pool of fractions for which the {@link #unique()} method has been invoked.
+     */
+    private static final WeakHashSet<Fraction> POOL = new WeakHashSet<>(Fraction.class);
+
+    /**
+     * The <var>a</var> term in the <var>a</var>/<var>b</var> fraction.
+     * Can be positive, negative or zero.
+     *
+     * @see #doubleValue()
+     */
+    public final int numerator;
+
+    /**
+     * The <var>b</var> term in the <var>a</var>/<var>b</var> fraction.
+     * Can be positive, negative or zero. If zero, then the fraction {@linkplain #doubleValue() floating point}
+     * value will be positive infinity, negative infinity or NaN depending on the {@linkplain #numerator} value.
+     *
+     * @see #doubleValue()
+     */
+    public final int denominator;
+
+    /**
+     * Creates a new fraction. This constructor stores the fraction exactly as specified; it does not simplify it.
+     * The fraction can be simplified after construction by a call to {@link #simplify()}.
+     *
+     * @param numerator    the <var>a</var> term in the <var>a</var>/<var>b</var> fraction.
+     * @param denominator  the <var>b</var> term in the <var>a</var>/<var>b</var> fraction.
+     */
+    public Fraction(final int numerator, final int denominator) {
+        this.numerator   = numerator;
+        this.denominator = denominator;
+    }
+
+    /**
+     * 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
+     * method adds this fraction to the pool of fractions that may be returned in next {@code unique()} invocations,
+     * then returns {@code this}.
+     *
+     * <p>This method is useful for saving memory when a potentially large amount of {@code Fraction} instances will
+     * be kept for a long time and many instances are likely to have the same values.
+     * It is usually not worth to invoke this method for short-lived instances.</p>
+     *
+     * @return a unique instance of a fraction equals to {@code this}.
+     */
+    public Fraction unique() {
+        return POOL.unique(this);
+    }
+
+    /**
+     * Returns a fraction equivalent to {@code this} but represented by the smallest possible numerator
+     * and denominator values. If this fraction can not be simplified, then this method returns {@code this}.
+     *
+     * @return the simplest fraction equivalent to this fraction.
+     */
+    public Fraction simplify() {
+        return simplify(numerator, denominator);
+    }
+
+    /**
+     * Returns a fraction equivalent to {@code num} / {@code den} after simplification.
+     * If the simplified fraction is equals to {@code this}, then this method returns {@code this}.
+     *
+     * <p>The arguments given to this method are the results of multiplications and additions of {@code int} values.
+     * This method fails if any argument value is {@link Long#MIN_VALUE} because that value can not be made positive.
+     * However it should never happen. Even in the worst scenario:</p>
+     *
+     * {@prefomat java
+     *     long n = Integer.MIN_VALUE * (long) Integer.MAX_VALUE;
+     *     n += n;
+     * }
+     *
+     * Above result still slightly smaller in magnitude than {@code Long.MIN_VALUE}.
+     */
+    private Fraction simplify(long num, long den) {
+        assert (num != Long.MIN_VALUE) && (den != Long.MIN_VALUE) : this;
+        if (num == 0) {
+            den = Long.signum(den);             // Simplify  0/x  as  0/±1 or 0/0.
+        } else if (den == 0) {
+            num = Long.signum(num);             // Simplify  x/0  as  ±1/0
+        } else if (den % num == 0) {
+            den /= num;                         // Simplify  x/xy  as  1/y
+            if (den < 0) {
+                den = -den;                     // Math.negateExact(long) not needed - see javadoc.
+                num = -1;
+            } else {
+                num = 1;
+            }
+        } else {
+            long a   = Math.abs(num);
+            long gcd = Math.abs(den);
+            long remainder = a % gcd;
+            if (remainder == 0) {
+                num /= den;                     // Simplify  xy/x  as  y/1
+                den = 1;
+            } else {
+                do {                            // Search for greater common divisor with Euclid's algorithm.
+                    a   = gcd;
+                    gcd = remainder;
+                    remainder = a % gcd;
+                } while (remainder != 0);
+                num /= gcd;
+                den /= gcd;
+                if (den < 0) {
+                    num = -num;                 // Math.negateExact(long) not needed - see javadoc.
+                    den = -den;
+                }
+            }
+        }
+        return (num == numerator && den == denominator) ? this
+               : new Fraction(Math.toIntExact(num), Math.toIntExact(den));
+    }
+
+    /**
+     * Returns the simplified result of adding the given fraction to this fraction.
+     *
+     * @param  other  the fraction to add to this fraction.
+     * @return the simplified result of {@code this} + {@code other}.
+     * @throws ArithmeticException if the result overflows.
+     */
+    public Fraction add(final Fraction other) {
+        // 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);
+    }
+
+    /**
+     * Returns the simplified result of subtracting the given fraction from this fraction.
+     *
+     * @param  other  the fraction to subtract from this fraction.
+     * @return the simplified result of {@code this} - {@code other}.
+     * @throws ArithmeticException if the result overflows.
+     */
+    public Fraction subtract(final Fraction other) {
+        // 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);
+    }
+
+    /**
+     * Returns the simplified result of multiplying the given fraction with this fraction.
+     *
+     * @param  other  the fraction to multiply with this fraction.
+     * @return the simplified result of {@code this} × {@code other}.
+     * @throws ArithmeticException if the result overflows.
+     */
+    public Fraction multiply(final Fraction other) {
+        return simplify(numerator   * (long) other.numerator,
+                        denominator * (long) other.denominator);
+    }
+
+    /**
+     * Returns the simplified result of dividing this fraction by the given fraction.
+     *
+     * @param  other  the fraction by which to divide this fraction.
+     * @return the simplified result of {@code this} ÷ {@code other}.
+     * @throws ArithmeticException if the result overflows.
+     */
+    public Fraction divide(final Fraction other) {
+        return simplify(numerator   * (long) other.denominator,
+                        denominator * (long) other.numerator);
+    }
+
+    /**
+     * Returns this fraction rounded toward nearest integer. If the result is located
+     * at equal distance from the two nearest integers, then rounds to the even one.
+     *
+     * @return {@link #numerator} / {@link denominator} rounded toward nearest integer.
+     */
+    public int round() {
+        if (denominator == Integer.MIN_VALUE) {
+            if (numerator < (Integer.MIN_VALUE / +2)) return +1;
+            if (numerator > (Integer.MIN_VALUE / -2)) return -1;
+            return 0;
+        }
+        int n = numerator / denominator;
+        int r = numerator % denominator;
+        if (r != 0) {
+            r = Math.abs(r << 1);
+            final int d = Math.abs(denominator);
+            if (r > d || (r == d && (n & 1) != 0)) {
+                if ((numerator ^ denominator) >= 0) {
+                    n++;
+                } else {
+                    n--;
+                }
+            }
+        }
+        return n;
+    }
+
+    /**
+     * Returns this fraction rounded toward negative infinity.
+     * This is different from the default operation on primitive types, which rounds toward zero.
+     *
+     * <p><b>Tip:</b> if the numerator and the denominator are both positive or both negative,
+     * then the result is positive and identical to {@code numerator / denominator}.</p>
+     *
+     * @return {@link #numerator} / {@link denominator} rounded toward negative infinity.
+     */
+    public int floor() {
+        int n = numerator / denominator;
+        if ((numerator ^ denominator) < 0 && (numerator % denominator) != 0) {
+            n--;
+        }
+        return n;
+    }
+
+    /**
+     * Returns this fraction rounded toward positive infinity.
+     * This is different from the default operation on primitive types, which rounds toward zero.
+     *
+     * @return {@link #numerator} / {@link denominator} rounded toward positive infinity.
+     */
+    public int ceil() {
+        int n = numerator / denominator;
+        if ((numerator ^ denominator) >= 0 && (numerator % denominator) != 0) {
+            n++;
+        }
+        return n;
+    }
+
+    /**
+     * Returns the fraction as a double-precision floating point number.
+     * Special cases:
+     *
+     * <ul>
+     *   <li>If the {@linkplain #numerator} and the {@linkplain #denominator} are both 0,
+     *       then this method returns {@link Double#NaN}.</li>
+     *   <li>If only the {@linkplain #denominator} is zero, then this method returns
+     *       {@linkplain Double#POSITIVE_INFINITY positive infinity} or
+     *       {@linkplain Double#NEGATIVE_INFINITY negative infinity} accordingly the {@linkplain #numerator} sign.</li>
+     * </ul>
+     *
+     * @return this fraction as a floating point number.
+     */
+    @Override
+    public double doubleValue() {
+        return numerator / (double) denominator;
+    }
+
+    /**
+     * Returns the fraction as a single-precision floating point number.
+     * Special cases:
+     *
+     * <ul>
+     *   <li>If the {@linkplain #numerator} and the {@linkplain #denominator} are both 0,
+     *       then this method returns {@link Float#NaN}.</li>
+     *   <li>If only the {@linkplain #denominator} is zero, then this method returns
+     *       {@linkplain Float#POSITIVE_INFINITY positive infinity} or
+     *       {@linkplain Float#NEGATIVE_INFINITY negative infinity} accordingly the {@linkplain #numerator} sign.</li>
+     * </ul>
+     *
+     * @return this fraction as a floating point number.
+     */
+    @Override
+    public float floatValue() {
+        return (float) doubleValue();
+    }
+
+    /**
+     * Returns this fraction rounded toward zero.
+     *
+     * @return this fraction rounded toward zero.
+     */
+    @Override
+    public long longValue() {
+        return intValue();
+    }
+
+    /**
+     * Returns this fraction rounded toward zero.
+     *
+     * @return {@link #numerator} / {@link denominator} rounded toward zero.
+     *
+     * @see #round()
+     * @see #floor()
+     * @see #ceil()
+     */
+    @Override
+    public int intValue() {
+        return numerator / denominator;
+    }
+
+    /**
+     * Returns this fraction rounded toward zero, if the result can be represented as a short integer.
+     *
+     * @return this fraction rounded toward zero.
+     * @throws ArithmeticException if the result can not be represented as a short integer.
+     */
+    @Override
+    public short shortValue() {
+        final int n = intValue();
+        if ((n & ~0xFFFF) == 0) return (short) n;
+        throw new ArithmeticException();
+    }
+
+    /**
+     * Returns this fraction rounded toward zero, if the result can be represented as a signed byte.
+     *
+     * @return this fraction rounded toward zero.
+     * @throws ArithmeticException if the result can not be represented as a signed byte.
+     */
+    @Override
+    public byte byteValue() {
+        final int n = intValue();
+        if ((n & ~0xFF) == 0) return (byte) n;
+        throw new ArithmeticException();
+    }
+
+    /**
+     * Compares this fraction with the given one for order.
+     *
+     * @param  other  the fraction to compare to this fraction for ordering.
+     * @return a negative number if this fraction is smaller than the given fraction,
+     *         a positive number if greater, or 0 if equals.
+     */
+    @Override
+    public int compareTo(final Fraction other) {
+        return Long.signum(numerator * (long) other.denominator - other.numerator * (long) denominator);
+    }
+
+    /**
+     * Compares this fraction with the given object for equality. This method returns {@code true} only if
+     * the two objects are fractions with same {@linkplain #numerator} and {@linkplain #denominator} values.
+     * Fractions with different values are not considered equal even if the two fraction are equivalent.
+     *
+     * @param  other  the object to compare with this fraction for equality.
+     * @return {@code true} if the given object is an other fraction with the same numerator and denominator values.
+     */
+    @Override
+    public boolean equals(final Object other) {
+        if (other == this) {
+            return true;
+        }
+        if (other instanceof Fraction) {
+            final Fraction that = (Fraction) other;
+            return numerator   == that.numerator &&
+                   denominator == that.denominator;
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash code value for this fraction.
+     */
+    @Override
+    public int hashCode() {
+        return (numerator + 31 * denominator) ^ (int) serialVersionUID;
+    }
+
+    /**
+     * The matrix of Unicode symbols available for some fractions. Each row contains all symbols for the same numerator.
+     * For example the first row contains the symbol of all fractions of the form 0/x, the second row all fractions of
+     * the form 1/x, <i>etc.</i>. In each row, the character at column <var>i</var> is for the fraction having the
+     * denominator i + (row index) + 1.
+     */
+    private static final char[][] UNICODES = {
+        {0,  0,  '↉'},
+        {   '½', '⅓', '¼', '⅕', '⅙', '⅐', '⅛', '⅑', '⅒'},
+        {        '⅔',  0,  '⅖'},
+        {             '¾', '⅗',  0,   0,  '⅜'},
+        {                  '⅘'},
+        {                       '⅚',  0,  '⅝'},
+        {},
+        {                                 '⅞'}
+    };
+
+    /**
+     * Returns a string representation of this fraction.
+     * This method returns Unicode symbol if possible.
+     *
+     * @return a string representation of this fraction.
+     */
+    @Override
+    public String toString() {
+        if (numerator >= 0 && numerator < UNICODES.length) {
+            final int d = denominator - numerator - 1;
+            if (d >= 0) {
+                final char[] r = UNICODES[numerator];
+                if (d < r.length) {
+                    final char c = r[d];
+                    if (c != 0) {
+                        return String.valueOf(c);
+                    }
+                }
+            }
+        }
+        if (denominator == 0 && numerator != 0) {
+            return (numerator >= 0) ? "∞" : "−∞";
+        }
+        return new StringBuilder().append(numerator).append('⁄').append(denominator).toString();
+    }
+}

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java?rev=1764996&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java (added)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java [UTF-8] Fri Oct 14 22:47:14 2016
@@ -0,0 +1,227 @@
+/*
+ * 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.measure;
+
+import java.io.Serializable;
+import java.util.Objects;
+import javax.measure.Unit;
+import javax.measure.Quantity;
+import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.collection.WeakHashSet;
+
+
+/**
+ * Base class of all unit implementations. All unit instances shall be immutable.
+ *
+ * <div class="section">Immutability and thread safety</div>
+ * This base class is immutable and thus inherently thread-safe.
+ * All subclasses shall be immutable too.
+ *
+ * @param  <Q>  the kind of quantity to be measured using this units.
+ *
+ * @author  Martin Desruisseaux (MPO, Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, Serializable {
+    /**
+     * Pool of all {@code AbstractUnit} instances created up to date.
+     */
+    @SuppressWarnings("rawtypes")
+    private static final WeakHashSet<AbstractUnit> POOL = new WeakHashSet<>(AbstractUnit.class);
+
+    /**
+     * The unit symbol, or {@code null} if this unit has no specific symbol. If {@code null},
+     * then the {@link #toString()} method is responsible for creating a representation on the fly.
+     *
+     * <p>Users can override this symbol by call to {@link UnitFormat#label(Unit, String)},
+     * but such overriding applies only to the target {@code UnitFormat} instance.</p>
+     *
+     * @see #getSymbol()
+     */
+    private final String symbol;
+
+    /**
+     * The unit name, or {@code null} if this unit has no specific name. If this unit exists
+     * in the EPSG database, then the value should be the name as specified in the database.
+     *
+     * @see #getName()
+     */
+    private final String name;
+
+    /**
+     * The EPSG code, or 0 if this unit has no EPSG code.
+     */
+    final short epsg;
+
+    /**
+     * Creates a new unit having the given symbol and EPSG code.
+     *
+     * @param  name    the unit name,   or {@code null} if this unit has no specific name.
+     * @param  symbol  the unit symbol, or {@code null} if this unit has no specific symbol.
+     * @param  epsg    the EPSG code,   or 0 if this unit has no EPSG code.
+     */
+    AbstractUnit(final String name, final String symbol, final short epsg) {
+        this.name   = name;
+        this.symbol = symbol;
+        this.epsg   = epsg;
+    }
+
+    /**
+     * Returns the symbol (if any) of this unit. A unit may have no symbol, in which case
+     * the {@link #toString()} method is responsible for creating a string representation.
+     *
+     * @return the unit symbol, or {@code null} if this unit has no specific symbol.
+     *
+     * @see #toString()
+     */
+    @Override
+    public final String getSymbol() {
+        return symbol;
+    }
+
+    /**
+     * Returns the name (if any) of this unit. For example {@link Units#METRE} has the "m" symbol and the "metre" name.
+     * If this unit exists in the EPSG database, then this method should return the name as specified in the database.
+     *
+     * @return the unit name, or {@code null} if this unit has no specific name.
+     */
+    @Override
+    public final String getName() {
+        return name;
+    }
+
+    /**
+     * Indicates if this unit is compatible with the given unit.
+     * This implementation delegates to:
+     *
+     * {@preformat java
+     *   return getDimension().equals(that.getDimension());
+     * }
+     *
+     * @param  that the other unit to compare for compatibility.
+     * @return {@code true} if the given unit is compatible with this unit.
+     *
+     * @see #getDimension()
+     */
+    @Override
+    public final boolean isCompatible(final Unit<?> that) {
+        ArgumentChecks.ensureNonNull("that", that);
+        return getDimension().equals(that.getDimension());
+    }
+
+    /**
+     * Returns a unique instance of this unit. An initially empty pool of {@code AbstractUnit}
+     * instances is maintained. When invoked, this method first checks if an instance equals
+     * to this unit exists in the pool. If such instance is found, then it is returned.
+     * Otherwise this instance is added in the pool using weak references and returned.
+     */
+    final AbstractUnit<Q> intern() {
+        return POOL.unique(this);
+    }
+
+    /**
+     * Returns an instance equals to this unit, ignoring the symbol.
+     * If such instance exists in the pool of existing units, it is
+     * returned. Otherwise this method returns {@code this}.
+     */
+    final AbstractUnit<Q> internIgnoreSymbol() {
+        @SuppressWarnings("unchecked")
+        AbstractUnit<Q> unit = POOL.get(new Unamed(this));
+        if (unit == null) {
+            unit = this;
+        }
+        return unit;
+    }
+
+    /**
+     * Compares this unit with the given unit, ignoring symbol.
+     * Implementations shall check the {@code obj} type.
+     *
+     * @param  obj The object to compare with this unit, or {@code null}.
+     * @return {@code true} If the given unit is equals to this unit, ignoring symbol.
+     */
+    protected abstract boolean equalsIgnoreSymbol(Object obj);
+
+    /**
+     * Compares this unit with the given object for equality.
+     *
+     * @param  other The other object to compares with this unit, or {@code null}.
+     * @return {@code true} if the given object is equals to this unit.
+     */
+    @Override
+    public final boolean equals(Object other) {
+        if (other instanceof Unamed) {
+            other = ((Unamed) other).unit;
+        }
+        if (equalsIgnoreSymbol(other)) {
+            return Objects.equals(symbol, ((AbstractUnit<?>) other).symbol);
+        }
+        return false;
+    }
+
+    /**
+     * A temporary proxy used by {@link #internIgnoreSymbol()} for finding an existing units,
+     * ignoring the symbol.
+     */
+    private static final class Unamed {
+        final AbstractUnit<?> unit;
+
+        Unamed(final AbstractUnit<?> unit)          {this.unit = unit;}
+        @Override public int hashCode()             {return unit.hashCode();}
+        @Override public boolean equals(Object obj) {return unit.equalsIgnoreSymbol(obj);}
+    }
+
+    /**
+     * Returns a hash code value for this unit, ignoring symbol.
+     */
+    @Override
+    public abstract int hashCode();
+
+    /**
+     * Replaces the deserialized unit instance by a unique instance, if any.
+     *
+     * @return The unique unit instance.
+     */
+    protected final Object readResolve() {
+        return intern();
+    }
+
+    /**
+     * Returns the exception to throw when the given unit arguments are illegal
+     * for the operation to perform.
+     */
+    final ArithmeticException illegalUnitOperation(final String operation, final AbstractUnit<?> that) {
+        return new ArithmeticException(); // TODO: provide a message.
+    }
+
+    /**
+     * Returns the unlocalized string representation of this unit, either as a single symbol
+     * or a product of symbols or scale factors, eventually with an offset.
+     *
+     * @see #getSymbol()
+     */
+    @Override
+    public final String toString() {
+        if (symbol != null) {
+            return symbol;
+        } else {
+            return UnitFormat.INSTANCE.format(this);
+        }
+    }
+}

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Added: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java?rev=1764996&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java (added)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java [UTF-8] Fri Oct 14 22:47:14 2016
@@ -0,0 +1,282 @@
+/*
+ * 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.measure;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.LinkedHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiFunction;
+import java.io.Serializable;
+import javax.measure.Dimension;
+import org.apache.sis.math.Fraction;
+import org.apache.sis.util.ObjectConverters;
+import org.apache.sis.util.resources.Errors;
+import org.apache.sis.util.UnsupportedImplementationException;
+import org.apache.sis.internal.converter.FractionConverter;
+
+
+/**
+ * Dimension (length, mass, time, <i>etc.</i>) of a unit of measurement.
+ * Only two kind of dimensions are defined in Apache SIS:
+ *
+ * <ul>
+ *   <li>Base dimensions are the 7 base dimensions specified by the SI system.</li>
+ *   <li>Derived dimensions are products of base dimensions raised to some power.</li>
+ * </ul>
+ *
+ * The powers should be integers, but this implementation nevertheless accepts fractional power of dimensions.
+ * While quantities with dimension such as √M makes no sense physically, on a pragmatic point of view it is easier
+ * to write programs in which such units appear in intermediate calculations but become integers in the final result.
+ * Furthermore, some dimensions with fractional power actually exist. Examples:
+ *
+ * <ul>
+ *   <li>Voltage noise density measured per √(Hz).</li>
+ *   <li><a href="http://en.wikipedia.org/wiki/Specific_detectivity">Specific detectivity</a>
+ *       as T^2.5 / (M⋅L) dimension.</li>
+ * </ul>
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+final class UnitDimension implements Dimension, Serializable {
+    /**
+     * For cross-version compatibility.
+     */
+    private static final long serialVersionUID = 2568769237612674235L;
+
+    /**
+     * Pool of all {@code UnitDimension} instances created up to date.
+     * Keys are the same map than {@link UnitDimension#components}, which shall be immutable.
+     * We hold the dimensions by strong reference on the assumption that we will not create many of them.
+     */
+    private static final ConcurrentMap<Map<UnitDimension,Fraction>, UnitDimension> POOL = new ConcurrentHashMap<>();
+
+    // The POOL map must be created before the following constants.
+
+    /**
+     * Pseudo-dimension for dimensionless units.
+     */
+    private static final UnitDimension NONE = new UnitDimension(Collections.emptyMap());
+
+    /**
+     * The product of base dimensions that make this dimension. All keys in this map shall be base dimensions
+     * (base dimensions are identified by non-zero {@link #symbol}). If this {@code UnitDimension} is itself a
+     * base dimension, then the map contains {@code this} raised to power 1. The map shall never be {@code null}.
+     *
+     * @see #getBaseDimensions()
+     */
+    private final Map<UnitDimension,Fraction> components;
+
+    /**
+     * If this {@code UnitDimension} is a base dimension, its symbol (not to be confused with unit symbol).
+     * Otherwise (i.e. if this {@code UnitDimension} is a derived dimension), zero.
+     */
+    private final char symbol;
+
+    /**
+     * Creates a new base dimension with the given symbol, which shall not be zero.
+     * This constructor shall be invoked only during construction of {@link Units} constants.
+     *
+     * @param  symbol  the symbol of this base dimension (not to be confused with unit symbol).
+     */
+    @SuppressWarnings("ThisEscapedInObjectConstruction")    // Safe because this class is final.
+    UnitDimension(final char symbol) {
+        this.symbol = symbol;
+        components  = Collections.singletonMap(this, new Fraction(1,1).unique());
+        if (POOL.putIfAbsent(components, this) != null) {
+            throw new AssertionError(this);
+        }
+    }
+
+    /**
+     * Creates a new derived dimension. This constructor shall never be invoked directly;
+     * use {@link #create(Map)} instead.
+     *
+     * @param  components  the product of base dimensions together with their power.
+     */
+    private UnitDimension(final Map<UnitDimension,Fraction> components) {
+        this.components = components;
+        this.symbol     = 0;
+    }
+
+    /**
+     * Creates a new derived dimension from the given product of base dimensions with their power.
+     * This method returns a shared instance if possible.
+     *
+     * @param  components  the product of base dimensions together with their power.
+     */
+    private static UnitDimension create(final Map<UnitDimension,Fraction> components) {
+        final Map<UnitDimension,Fraction> unmodifiable;     // Unmodifiable view of 'components'.
+        final boolean share;
+        switch (components.size()) {
+            case 0: return NONE;
+            case 1: {
+                final Map.Entry<UnitDimension,Fraction> entry = components.entrySet().iterator().next();
+                final UnitDimension base = entry.getKey();
+                final Fraction power = entry.getValue();
+                if (power.numerator == 1 && power.denominator == 1) {
+                    return base;
+                }
+                unmodifiable = Collections.singletonMap(base, power.unique());
+                share = false;      // Because we already invoked 'unique()'.
+                break;
+            }
+            default: {
+                unmodifiable = Collections.unmodifiableMap(components);
+                share = true;
+                break;
+            }
+        }
+        return POOL.computeIfAbsent(unmodifiable, (key) -> {
+            if (share) {
+                components.replaceAll((c, power) -> power.unique());
+            }
+            return new UnitDimension(key);
+        });
+    }
+
+
+    /**
+     * Returns the (fundamental) base dimensions and their exponent whose product is this dimension,
+     * or null if this dimension is a base dimension.
+     */
+    @Override
+    public Map<? extends Dimension, Integer> getBaseDimensions() {
+        if (symbol != 0) {
+            return null;
+        }
+        return ObjectConverters.derivedValues(components, UnitDimension.class, FractionConverter.INSTANCE);
+    }
+
+    /**
+     * Returns the base dimensions and their exponents whose product make the given dimension.
+     * If the given dimension is a base dimension, then this method returns {@code this} raised
+     * to power 1. This method never returns {@code null}.
+     */
+    @SuppressWarnings("ReturnOfCollectionOrArrayField")     // Safe because the map is unmodifiable.
+    private static Map<? extends Dimension, Fraction> getBaseDimensions(final Dimension dimension) {
+        if (dimension instanceof UnitDimension) {
+            return ((UnitDimension) dimension).components;
+        }
+        /*
+         * Fallback for non-SIS implementations. The cast from <? extends Dimension> to <Dimension>
+         * is safe if we use the 'components' map as a read-only map (no put operation allowed).
+         */
+        @SuppressWarnings("unchecked")
+        Map<Dimension,Integer> components = (Map<Dimension,Integer>) dimension.getBaseDimensions();
+        if (components == null) {
+            return Collections.singletonMap(dimension, new Fraction(1,1));
+        }
+        return ObjectConverters.derivedValues(components, Dimension.class, FractionConverter.FromInteger.INSTANCE);
+    }
+
+    /**
+     * Returns the quotient of this dimension with the one specified.
+     *
+     * @param  divisor  the dimension by which to divide this dimension.
+     * @return {@code this} / {@code divisor}
+     */
+    @Override
+    public Dimension divide(final Dimension divisor) {
+        return multiply(divisor, -1);
+    }
+
+    /**
+     * Returns the product of this dimension with the one specified.
+     *
+     * @param  multiplicand  the dimension by which to multiply this dimension.
+     * @return {@code this} × {@code multiplicand}
+     */
+    @Override
+    public Dimension multiply(final Dimension multiplicand) {
+        return multiply(multiplicand, +1);
+    }
+
+    /**
+     * Returns the product of this dimension with the specified one raised to the given power.
+     *
+     * @param  multiplicand  the dimension by which to multiply this dimension.
+     * @param  power         the power at which to raise {@code multiplicand}.
+     * @return the product of this dimension by the given dimension raised to the given power.
+     */
+    private Dimension multiply(final Dimension multiplicand, final int power) {
+        final BiFunction<Fraction, Fraction, Fraction> mapping = (sum, toAdd) -> {
+            if (power != 1) {
+                toAdd = new Fraction(toAdd.numerator * power, toAdd.denominator);
+            }
+            sum = sum.add(toAdd);
+            return (sum.numerator != 0) ? sum : null;
+        };
+        final Map<UnitDimension,Fraction> product = new LinkedHashMap<>(components);
+        for (final Map.Entry<? extends Dimension, Fraction> entry : getBaseDimensions(multiplicand).entrySet()) {
+            final Dimension dim = entry.getKey();
+            final Fraction p = entry.getValue();
+            if (dim instanceof UnitDimension) {
+                product.merge((UnitDimension) dim, p, mapping);
+            } else if (p.numerator != 0) {
+                throw new UnsupportedImplementationException(Errors.format(Errors.Keys.UnsupportedImplementation_1, dim.getClass()));
+            }
+        }
+        return create(product);
+    }
+
+    /**
+     * Returns this dimension raised to an exponent.
+     *
+     * @param  n  power to raise this dimension to (can be negative).
+     * @return {@code this}ⁿ
+     */
+    private Dimension pow(final Fraction n) {
+        final Map<UnitDimension,Fraction> product = new LinkedHashMap<>(components);
+        product.replaceAll((dim, power) -> power.multiply(n));
+        return create(product);
+    }
+
+    /**
+     * Returns this dimension raised to an exponent.
+     *
+     * @param  n  power to raise this dimension to (can be negative).
+     * @return {@code this}ⁿ
+     */
+    @Override
+    public Dimension pow(final int n) {
+        switch (n) {
+            case 0:  return NONE;
+            case 1:  return this;
+            default: return pow(new Fraction(n,1));
+        }
+    }
+
+    /**
+     * Returns the given root of this dimension.
+     *
+     * @param  n  the root's order.
+     * @return {@code this} raised to power 1/n.
+     */
+    @Override
+    public Dimension root(final int n) {
+        switch (n) {
+            case 0:  throw new ArithmeticException(Errors.format(Errors.Keys.IllegalArgumentValue_2, "n", 0));
+            case 1:  return this;
+            default: return pow(new Fraction(1,n));
+        }
+    }
+}

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter?rev=1764996&r1=1764995&r2=1764996&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/resources/META-INF/services/org.apache.sis.util.ObjectConverter [UTF-8] Fri Oct 14 22:47:14 2016
@@ -38,3 +38,5 @@ org.apache.sis.internal.converter.DateCo
 org.apache.sis.internal.converter.DateConverter$Timestamp
 org.apache.sis.internal.converter.CollectionConverter$List
 org.apache.sis.internal.converter.CollectionConverter$Set
+org.apache.sis.internal.converter.FractionConverter
+org.apache.sis.internal.converter.FractionConverter$FromInteger

Added: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java?rev=1764996&view=auto
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java (added)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java [UTF-8] Fri Oct 14 22:47:14 2016
@@ -0,0 +1,130 @@
+/*
+ * 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.math;
+
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.apache.sis.test.Assert.*;
+
+
+/**
+ * Tests the {@link Fraction} class.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @since   0.8
+ * @version 0.8
+ * @module
+ */
+public final strictfp class FractionTest extends TestCase {
+    /**
+     * Tests the {@link Fraction#floor()} method.
+     */
+    @Test
+    public void testFloor() {
+        final int[] numerators   = { 0,  1,  2,  3,  9, 10, 11, 12};
+        final int[] denominators = { 3,  3,  3,  3,  3,  3,  3,  3};
+        final int[] positives    = { 0,  0,  0,  1,  3,  3,  3,  4};
+        final int[] negatives    = {-0, -1, -1, -1, -3, -4, -4, -4};
+        for (int i=0; i<numerators.length; i++) {
+            for (int s=0; s<4; s++) {
+                int numerator   = numerators  [i];
+                int denominator = denominators[i];
+                if ((s & 1) != 0) numerator   = -numerator;
+                if ((s & 2) != 0) denominator = -denominator;
+                final int[] expected = (numerator * denominator >= 0) ? positives : negatives;
+                final String label = "floor(" + numerator + '/' + denominator + ')';
+                assertEquals(label, expected[i], new Fraction(numerator, denominator).floor());
+            }
+        }
+    }
+
+    /**
+     * Tests the {@link Fraction#ceil()} method.
+     */
+    @Test
+    public void testCeil() {
+        final int[] numerators   = { 0,  1,  2,  3,  9, 10, 11, 12};
+        final int[] denominators = { 3,  3,  3,  3,  3,  3,  3,  3};
+        final int[] positives    = { 0,  1,  1,  1,  3,  4,  4,  4};
+        final int[] negatives    = {-0, -0, -0, -1, -3, -3, -3, -4};
+        for (int i=0; i<numerators.length; i++) {
+            for (int s=0; s<4; s++) {
+                int numerator   = numerators  [i];
+                int denominator = denominators[i];
+                if ((s & 1) != 0) numerator   = -numerator;
+                if ((s & 2) != 0) denominator = -denominator;
+                final int[] expected = (numerator * denominator >= 0) ? positives : negatives;
+                final String label = "ceil(" + numerator + '/' + denominator + ')';
+                assertEquals(label, expected[i], new Fraction(numerator, denominator).ceil());
+            }
+        }
+    }
+
+    /**
+     * Tests the {@link Fraction#round()} method.
+     */
+    @Test
+    public void testRoundFraction() {
+        final int[] numerators   = { 0,  1,  2,  3,  9, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19};
+        final int[] denominators = { 3,  3,  3,  3,  3,  3,  3,  3,  4,  4,  4,  4,  4,  4,  4,  4};
+        final int[] results      = { 0,  0,  1,  1,  3,  3,  4,  4,  3,  3,  4,  4,  4,  4,  4,  5};
+        for (int i=10; i<numerators.length; i++) {
+            for (int s=0; s<4; s++) {
+                int numerator   = numerators  [i];
+                int denominator = denominators[i];
+                int expected    = results     [i];
+                if ((s & 1) != 0) numerator   = -numerator;
+                if ((s & 2) != 0) denominator = -denominator;
+                if (numerator * denominator < 0) expected = -expected;
+                final String label = "even(" + numerator + '/' + denominator + ')';
+                assertEquals(label, expected, new Fraction(numerator, denominator).round());
+            }
+        }
+    }
+
+    /**
+     * Tests the {@link Fraction#simplify()} method.
+     */
+    @Test
+    public void testSimplify() {
+        Fraction fraction = new Fraction(4, 7).simplify();
+        assertEquals(4, fraction.numerator);
+        assertEquals(7, fraction.denominator);
+
+        fraction = new Fraction(4, 8).simplify();
+        assertEquals(1, fraction.numerator);
+        assertEquals(2, fraction.denominator);
+
+        fraction = new Fraction(48, 18).simplify();
+        assertEquals(8, fraction.numerator);
+        assertEquals(3, fraction.denominator);
+
+        fraction = new Fraction(17*21, 31*21).simplify();
+        assertEquals(17, fraction.numerator);
+        assertEquals(31, fraction.denominator);
+    }
+
+    /**
+     * Tests serialization.
+     */
+    @Test
+    public void testSerialization() {
+        final Fraction local = new Fraction(5, 7);
+        assertNotSame(local, assertSerializedEquals(local));
+    }
+}

Propchange: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain;charset=UTF-8

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java?rev=1764996&r1=1764995&r2=1764996&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java [UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/test/suite/UtilityTestSuite.java [UTF-8] Fri Oct 14 22:47:14 2016
@@ -56,6 +56,7 @@ import org.junit.BeforeClass;
     org.apache.sis.util.logging.WarningListenersTest.class,
     org.apache.sis.util.logging.MonolineFormatterTest.class,
     org.apache.sis.util.logging.LoggerAdapterTest.class,
+    org.apache.sis.math.FractionTest.class,
     org.apache.sis.math.VectorTest.class,
     org.apache.sis.math.MathFunctionsTest.class,
     org.apache.sis.math.DecimalFunctionsTest.class,



Mime
View raw message