sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject svn commit: r1766601 - in /sis/branches/JDK8/core: sis-referencing/src/main/java/org/apache/sis/referencing/datum/ sis-utility/src/main/java/org/apache/sis/internal/util/ sis-utility/src/main/java/org/apache/sis/measure/ sis-utility/src/test/java/org/a...
Date Tue, 25 Oct 2016 21:16:55 GMT
Author: desruisseaux
Date: Tue Oct 25 21:16:55 2016
New Revision: 1766601

URL: http://svn.apache.org/viewvc?rev=1766601&view=rev
Log:
Implement the scale factor of LinearConverter as a ratio for more accurate inversion and concatenation.
Add JUnit tests.

Modified:
    sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
    sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
    sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java

Modified: sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java?rev=1766601&r1=1766600&r2=1766601&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/datum/BursaWolfParameters.java
[UTF-8] Tue Oct 25 21:16:55 2016
@@ -548,7 +548,7 @@ public class BursaWolfParameters extends
          * elements should have the same value, but we tolerate slight deviation
          * (this will be verified later).
          */
-        final DoubleDouble S = new DoubleDouble(getNumber(matrix, 0,0));
+        final DoubleDouble S = DoubleDouble.castOrCopy(getNumber(matrix, 0,0));
         S.add(getNumber(matrix, 1,1));
         S.add(getNumber(matrix, 2,2));
         S.divide(3, 0);

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java?rev=1766601&r1=1766600&r2=1766601&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/internal/util/DoubleDouble.java
[UTF-8] Tue Oct 25 21:16:55 2016
@@ -17,10 +17,12 @@
 package org.apache.sis.internal.util;
 
 import java.util.Arrays;
+import java.math.BigInteger;
+import java.math.BigDecimal;
+import java.math.MathContext;
 import org.apache.sis.math.Fraction;
 import org.apache.sis.math.MathFunctions;
 import org.apache.sis.math.DecimalFunctions;
-// No BigDecimal dependency - see class javadoc
 
 
 /**
@@ -44,8 +46,6 @@ import org.apache.sis.math.DecimalFuncti
  *     BigDecimal decimal = new BigDecimal(dd.value).add(new BigDecimal(dd.error));
  * }
  *
- * We do not provide convenience method for the above in order to avoid dependency to {@code
BigDecimal}.
- *
  * <div class="section">Impact of availability of FMA instructions</div>
  * If <cite>fused multiply-add</cite> (FMA) instruction are available in a future
Java version
  * (see <a href="https://issues.apache.org/jira/browse/SIS-136">SIS-136</a> on
Apache SIS JIRA),
@@ -201,17 +201,28 @@ public final class DoubleDouble extends
 
     /**
      * Creates a new value initialized to the given number. If the given number is an instance
of
-     * {@code DoubleDouble} or {@link Fraction}, then the error term will be taken in account.
+     * {@code DoubleDouble}, {@link BigDecimal}, {@link BigInteger} or {@link Fraction},
then the
+     * error term will be taken in account.
      *
      * @param  otherValue  the initial value.
      */
-    public DoubleDouble(final Number otherValue) {
+    public DoubleDouble(Number otherValue) {
         if (otherValue instanceof Fraction) {
             value = ((Fraction) otherValue).denominator;
             inverseDivide(((Fraction) otherValue).numerator, 0);
         } else {
+            if (otherValue instanceof BigInteger) {
+                otherValue = new BigDecimal((BigInteger) otherValue, MathContext.DECIMAL128);
+            }
             value = otherValue.doubleValue();
-            error = (otherValue instanceof DoubleDouble) ? ((DoubleDouble) otherValue).error
: errorForWellKnownValue(value);
+            if (otherValue instanceof DoubleDouble) {
+                error = ((DoubleDouble) otherValue).error;
+            } else if (otherValue instanceof BigDecimal) {
+                // Really need new BigDecimal(value) below, not BigDecimal.valueOf(value).
+                error = ((BigDecimal) otherValue).subtract(new BigDecimal(value), MathContext.DECIMAL64).doubleValue();
+            } else {
+                error = errorForWellKnownValue(value);
+            }
         }
     }
 
@@ -226,7 +237,7 @@ public final class DoubleDouble extends
      * @since 0.8
      */
     public static boolean shouldConvert(final Number value) {
-        return (value instanceof Fraction);
+        return (value instanceof Fraction) || (value instanceof BigInteger) || (value instanceof
BigDecimal);
     }
 
     /**

Modified: 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=1766601&r1=1766600&r2=1766601&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
[UTF-8] Tue Oct 25 21:16:55 2016
@@ -190,7 +190,7 @@ abstract class AbstractUnit<Q extends Qu
      */
     @Override
     public final Unit<Q> shift(final double offset) {
-        return transform(LinearConverter.create(1, offset));
+        return transform(LinearConverter.offset(offset, 1));
     }
 
     /**
@@ -202,7 +202,7 @@ abstract class AbstractUnit<Q extends Qu
      */
     @Override
     public final Unit<Q> multiply(final double multiplier) {
-        return transform(LinearConverter.create(multiplier, 0));
+        return transform(LinearConverter.scale(multiplier, 1));
     }
 
     /**
@@ -214,7 +214,7 @@ abstract class AbstractUnit<Q extends Qu
      */
     @Override
     public final Unit<Q> divide(final double divisor) {
-        return transform(LinearConverter.create(1/divisor, 0));
+        return transform(LinearConverter.scale(1, divisor));
     }
 
     /**

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java?rev=1766601&r1=1766600&r2=1766601&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
[UTF-8] Tue Oct 25 21:16:55 2016
@@ -20,11 +20,14 @@ import java.util.List;
 import java.util.Arrays;
 import java.util.Collections;
 import java.math.BigDecimal;
+import java.math.BigInteger;
 import javax.measure.UnitConverter;
 import org.apache.sis.util.Debug;
 import org.apache.sis.util.ArgumentChecks;
+import org.apache.sis.util.StringBuilders;
 import org.apache.sis.math.DecimalFunctions;
 import org.apache.sis.math.MathFunctions;
+import org.apache.sis.math.Fraction;
 import org.apache.sis.internal.util.Numerics;
 
 
@@ -74,50 +77,69 @@ final class LinearConverter extends Abst
     /**
      * The identity linear converter.
      */
-    static final LinearConverter IDENTITY = new LinearConverter(1, 0);
+    static final LinearConverter IDENTITY = new LinearConverter(1, 0, 1);
 
     /**
-     * The scale to apply for converting values.
+     * The scale to apply for converting values, before division by {@link #divisor}.
      */
     private final double scale;
 
     /**
-     * The offset to apply after the scale.
+     * The offset to apply after the scale, before division by {@link #divisor}.
      */
     private final double offset;
 
     /**
-     * The inverse of this unit converter. Computed when first needed and stored in
-     * order to avoid rounding error if the user asks for the inverse of the inverse.
+     * A divisor applied after the conversion.
+     * The complete formula used by Apache SIS is {@code y = (x*scale + offset) / divisor}.
+     * This division is mathematically unneeded since we could divide the offset and scale
factor directly,
+     * but we keep it for accuracy reasons because most unit conversion factors are defined
in base 10 and
+     * IEEE 754 can not represent fractional values in base 10 accurately.
      */
-    private transient LinearConverter inverse;
+    private final double divisor;
+
+    /**
+     * The scale and offset factors represented in base 10, computed when first needed.
+     * Those terms are pre-divided by the {@linkplain #divisor}.
+     */
+    private transient volatile BigDecimal scale10, offset10;
 
     /**
      * Creates a new linear converter for the given scale and offset.
+     * The complete formula applied is {@code y = (x*scale + offset) / divisor}.
      */
-    private LinearConverter(final double scale, final double offset) {
-        this.scale  = scale;
-        this.offset = offset;
+    private LinearConverter(final double scale, final double offset, final double divisor)
{
+        this.scale   = scale;
+        this.offset  = offset;
+        this.divisor = divisor;
     }
 
     /**
      * Returns a linear converter for the given scale and offset.
      */
-    static LinearConverter create(final double scale, final double offset) {
+    private static LinearConverter create(final double scale, final double offset, final
double divisor) {
         if (offset == 0) {
-            if (scale == 1) return IDENTITY;
+            if (scale == divisor) return IDENTITY;
         }
-        return new LinearConverter(scale, offset);
+        return new LinearConverter(scale, offset, divisor);
     }
 
     /**
      * Returns a linear converter for the given ratio. The scale factor is specified as a
ratio because
-     * the unit conversion factors are defined with a value which is exact in base 10.
-     *
-     * @todo modify the {@code LinearConverter} implementation for storing the ratio.
+     * the unit conversion factors are often defined with a value in base 10.  That value
is considered
+     * exact by definition, but IEEE 754 has no exact representation of decimal fraction
digits.
      */
     static LinearConverter scale(final double numerator, final double denominator) {
-        return new LinearConverter(numerator / denominator, 0);
+        return new LinearConverter(numerator, 0, denominator);
+    }
+
+    /**
+     * Returns a converter for the given shift. The translation is specified as a fraction
because the
+     * unit conversion terms are often defined with a value in base 10. That value is considered
exact
+     * by definition, but IEEE 754 has no exact representation of decimal fraction digits.
+     */
+    static LinearConverter offset(final double numerator, final double denominator) {
+        return new LinearConverter(denominator, numerator, denominator);
     }
 
     /**
@@ -150,7 +172,7 @@ final class LinearConverter extends Abst
 
     /**
      * Raises the given converter to the given power. This method assumes that the given
converter
-     * {@linkplain #isLinear() is linear} (this is not verified) and take only the scale
factor;
+     * {@linkplain #isLinear() is linear} (this is not verified) and takes only the scale
factor;
      * the offset (if any) is ignored.
      *
      * @param  converter  the converter to raise to the given power.
@@ -159,19 +181,35 @@ final class LinearConverter extends Abst
      * @return the converter raised to the given power.
      */
     static LinearConverter pow(final UnitConverter converter, final int n, final boolean
root) {
-        double scale = converter.convert(1.0) - converter.convert(0.0);
+        double numerator, denominator;
+        if (converter instanceof LinearConverter) {
+            final LinearConverter lc = (LinearConverter) converter;
+            numerator   = lc.scale;
+            denominator = lc.divisor;
+        } else {
+            // Subtraction by convert(0) is a paranoiac safety.
+            numerator   = converter.convert(1.0) - converter.convert(0.0);
+            denominator = 1;
+        }
         if (root) {
             switch (n) {
-                case 2:  scale = Math.sqrt(scale); break;
-                case 3:  scale = Math.cbrt(scale); break;
-                default: scale = Math.pow(scale, 1.0 / n); break;
+                case 1:  break;
+                case 2:  numerator   = Math.sqrt(numerator);
+                         denominator = Math.sqrt(denominator);
+                         break;
+                case 3:  numerator   = Math.cbrt(numerator);
+                         denominator = Math.cbrt(denominator);
+                         break;
+                default: final double r = 1.0 / n;
+                         numerator   = Math.pow(numerator,   r);
+                         denominator = Math.pow(denominator, r);
+                         break;
             }
-        } else if (scale == 10) {
-            scale = MathFunctions.pow10(n);
         } else {
-            scale = Math.pow(scale, n);
+            numerator   = (numerator   == 10) ? MathFunctions.pow10(n) : Math.pow(numerator,
  n);
+            denominator = (denominator == 10) ? MathFunctions.pow10(n) : Math.pow(denominator,
n);
         }
-        return create(scale, 0);
+        return scale(numerator, denominator);
     }
 
     /**
@@ -192,23 +230,30 @@ final class LinearConverter extends Abst
     }
 
     /**
-     * Returns {@code true} if the scale is 1 and the offset is zero.
+     * Returns {@code true} if the effective scale factor is 1 and the offset is zero.
      */
     @Override
     public boolean isIdentity() {
-        return scale == 1 && offset == 0;
+        return scale == divisor && offset == 0;
     }
 
     /**
      * Returns the inverse of this unit converter.
+     * Given that the formula applied by this converter is:
+     *
+     * {@preformat math
+     *    y = (x⋅scale + offset) ∕ divisor
+     * }
+     *
+     * the inverse formula is:
+     *
+     * {@preformat math
+     *    x = (y⋅divisor - offset) ∕ scale
+     * }
      */
     @Override
     public synchronized UnitConverter inverse() {
-        if (inverse == null) {
-            inverse = new LinearConverter(1/scale, -offset/scale);
-            inverse.inverse = this;
-        }
-        return inverse;
+        return isIdentity() ? this : new LinearConverter(divisor, -offset, scale);
     }
 
     /**
@@ -217,50 +262,72 @@ final class LinearConverter extends Abst
     @Override
     @SuppressWarnings("fallthrough")
     Number[] coefficients() {
-        final Number[] c = new Number[(scale != 1) ? 2 : (offset != 0) ? 1 : 0];
+        final Number[] c = new Number[(scale != divisor) ? 2 : (offset != 0) ? 1 : 0];
         switch (c.length) {
-            case 2: c[1] = scale;
-            case 1: c[0] = offset;
+            case 2: c[1] = ratio(scale,  divisor);
+            case 1: c[0] = ratio(offset, divisor);
             case 0: break;
         }
         return c;
     }
 
     /**
+     * Returns the given ratio as a {@link Fraction} if possible, or as a {@link Double}
otherwise.
+     * The use of {@link Fraction} allows the {@link org.apache.sis.referencing.operation.matrix}
+     * package to perform more accurate calculations.
+     */
+    private static Number ratio(final double value, final double divisor) {
+        final int numerator = (int) value;
+        if (numerator == value) {
+            final int denominator = (int) divisor;
+            if (denominator == divisor) {
+                return (denominator == 1) ? numerator : new Fraction(numerator, denominator);
+            }
+        }
+        return value / divisor;
+    }
+
+    /**
      * Applies the linear conversion on the given IEEE 754 floating-point value.
      */
     @Override
     public double convert(final double value) {
-        return value * scale + offset;
+        return (value * scale + offset) / divisor;
     }
 
     /**
      * Applies the linear conversion on the given value. This method uses {@link BigDecimal}
arithmetic if
      * the given value is an instance of {@code BigDecimal}, or IEEE 754 floating-point arithmetic
otherwise.
      *
-     * <p>This method is inefficient. Apache SIS rarely uses {@link BigDecimal} arithmetic,
so providing an
-     * efficient implementation of this method is currently not a goal (this decision may
be revisited in a
-     * future SIS version if the need for {@code BigDecimal} arithmetic increase).</p>
+     * <p>Apache SIS rarely uses {@link BigDecimal} arithmetic, so providing an efficient
implementation of
+     * this method is not a goal.</p>
      */
     @Override
     public Number convert(Number value) {
         ArgumentChecks.ensureNonNull("value", value);
-        if (value instanceof BigDecimal) {
-            if (scale != 1) {
-                value = ((BigDecimal) value).multiply(BigDecimal.valueOf(scale));
+        if (!isIdentity()) {
+            if (value instanceof BigInteger) {
+                value = new BigDecimal((BigInteger) value);
             }
-            if (offset != 0) {
-                value = ((BigDecimal) value).add(BigDecimal.valueOf(offset));
-            }
-        } else if (!isIdentity()) {
-            final double x;
-            if (value instanceof Float) {
-                // Because unit conversion factors are usually defined in base 10.
-                x = DecimalFunctions.floatToDouble((Float) value);
+            if (value instanceof BigDecimal) {
+                BigDecimal scale10  = this.scale10;
+                BigDecimal offset10 = this.offset10;
+                if (scale10 == null || offset10 == null) {
+                    final BigDecimal divisor = BigDecimal.valueOf(this.divisor);
+                    this.scale10  = scale10  = BigDecimal.valueOf(scale) .divide(divisor);
+                    this.offset10 = offset10 = BigDecimal.valueOf(offset).divide(divisor);
+                }
+                value = ((BigDecimal) value).multiply(scale10).add(offset10);
             } else {
-                x = value.doubleValue();
+                final double x;
+                if (value instanceof Float) {
+                    // Because unit conversion factors are usually defined in base 10.
+                    x = DecimalFunctions.floatToDouble((Float) value);
+                } else {
+                    x = value.doubleValue();
+                }
+                value = convert(x);
             }
-            value = convert(x);
         }
         return value;
     }
@@ -271,12 +338,24 @@ final class LinearConverter extends Abst
      */
     @Override
     public double derivative(double value) {
-        return scale;
+        return scale / divisor;
     }
 
     /**
      * Concatenates this converter with another converter. The resulting converter is equivalent
to first converting
-     * by the specified converter (right converter), and then converting by this converter
(left converter).
+     * by the specified converter (right converter), and then converting by this converter
(left converter).  In the
+     * following equations, the 1 subscript is for the specified converter and the 2 subscript
is for this converter:
+     *
+     * {@preformat math
+     *    t = (x⋅scale₁ + offset₁) ∕ divisor₁
+     *    y = (t⋅scale₂ + offset₂) ∕ divisor₂
+     * }
+     *
+     * We rewrite as:
+     *
+     * {@preformat math
+     *    y = (x⋅scale₁⋅scale₂ + offset₁⋅scale₂ + divisor₁⋅offset₂) ∕
(divisor₁⋅divisor₂)
+     * }
      */
     @Override
     public UnitConverter concatenate(final UnitConverter converter) {
@@ -287,22 +366,43 @@ final class LinearConverter extends Abst
         if (isIdentity()) {
             return converter;
         }
-        final double otherScale, otherOffset;
+        double otherScale, otherOffset, otherDivisor;
         if (converter instanceof LinearConverter) {
-            otherScale  = ((LinearConverter) converter).scale;
-            otherOffset = ((LinearConverter) converter).offset;
+            final LinearConverter lc = (LinearConverter) converter;
+            otherScale   = lc.scale;
+            otherOffset  = lc.offset;
+            otherDivisor = lc.divisor;
         } else if (converter.isLinear()) {
             /*
              * Fallback for foreigner implementations. Note that 'otherOffset' should be
restricted to zero
              * according JSR-363 definition of 'isLinear()', but let be safe; maybe we are
not the only one
              * to have a different interpretation about the meaning of "linear".
              */
-            otherOffset = converter.convert(0.0);
-            otherScale  = converter.convert(1.0) - otherOffset;
+            otherOffset  = converter.convert(0.0);
+            otherScale   = converter.convert(1.0) - otherOffset;
+            otherDivisor = 1;
         } else {
             return new ConcatenatedConverter(converter, this);
         }
-        return create(otherScale * scale, otherOffset * scale + offset);
+        otherScale   *= scale;
+        otherOffset   = otherOffset * scale + otherDivisor * offset;
+        otherDivisor *= divisor;
+        /*
+         * Following loop is a little bit similar to simplifying a fraction, but checking
only for the
+         * powers of 10 since unit conversions are often such values. Algorithm is not very
efficient,
+         * but the loop should not be executed often.
+         */
+        if (otherScale != 0 || otherOffset != 0 || otherDivisor != 0) {
+            double cf, f = 1;
+            do {
+                cf = f;
+                f *= 10;
+            } while (otherScale % f == 0 && otherOffset % f == 0 && otherDivisor
% f == 0);
+            otherScale   /= cf;
+            otherOffset  /= cf;
+            otherDivisor /= cf;
+        }
+        return create(otherScale, otherOffset, otherDivisor);
     }
 
     /**
@@ -318,7 +418,9 @@ final class LinearConverter extends Abst
      */
     @Override
     public int hashCode() {
-        return Numerics.hashCode((Double.doubleToLongBits(scale) + 31*Double.doubleToLongBits(offset))
^ serialVersionUID);
+        return Numerics.hashCode(Double.doubleToLongBits(scale)
+                         + 31 * (Double.doubleToLongBits(offset)
+                         + 37 *  Double.doubleToLongBits(divisor)));
     }
 
     /**
@@ -328,7 +430,9 @@ final class LinearConverter extends Abst
     public boolean equals(final Object other) {
         if (other instanceof LinearConverter) {
             final LinearConverter o = (LinearConverter) other;
-            return Numerics.equals(scale, o.scale) && Numerics.equals(offset, o.offset);
+            return Numerics.equals(scale,   o.scale)  &&
+                   Numerics.equals(offset,  o.offset) &&
+                   Numerics.equals(divisor, o.divisor);
         }
         return false;
     }
@@ -342,12 +446,20 @@ final class LinearConverter extends Abst
     @Override
     public String toString() {
         final StringBuilder buffer = new StringBuilder().append("y = ");
+        if (offset != 0) {
+            buffer.append('(');
+        }
         if (scale != 1) {
-            buffer.append(scale).append('⋅');
+            StringBuilders.trimFractionalPart(buffer.append(scale));
+            buffer.append('⋅');
         }
         buffer.append('x');
         if (offset != 0) {
-            buffer.append(" + ").append(offset);
+            StringBuilders.trimFractionalPart(buffer.append(" + ").append(offset));
+            buffer.append(')');
+        }
+        if (divisor != 1) {
+            StringBuilders.trimFractionalPart(buffer.append('∕').append(divisor));
         }
         return buffer.toString();
     }

Modified: sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java?rev=1766601&r1=1766600&r2=1766601&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8]
(original)
+++ sis/branches/JDK8/core/sis-utility/src/main/java/org/apache/sis/measure/Units.java [UTF-8]
Tue Oct 25 21:16:55 2016
@@ -717,16 +717,16 @@ public final class Units extends Static
         PASCAL              = Pa;
         SQUARE_METRE        = m2;
         METRES_PER_SECOND   = mps;
-        KILOGRAM            = add(Mass.class,      mass,                  "kg",   UnitRegistry.SI,
      (short) 0);
-        CUBIC_METRE         = add(Volume.class,    length.pow(3),         "m³",   UnitRegistry.SI,
      (short) 0);
-        NEWTON              = add(Force.class,     force,                 "N",    UnitRegistry.SI,
      (short) 0);
-        JOULE               = add(Energy.class,    energy,                "J",    UnitRegistry.SI,
      (short) 0);
-        WATT                = add(Power.class,     energy.divide(time),   "W",    UnitRegistry.SI,
      (short) 0);
-        HERTZ               = add(Frequency.class, time.pow(-1),          "Hz",   UnitRegistry.SI,
      (short) 0);
-        HECTOPASCAL         = add(Pa, hecto,                              "hPa",  UnitRegistry.SI,
      (short) 0);
-        HECTARE             = add(m2,  LinearConverter.scale(10000, 1),   "ha",   UnitRegistry.ACCEPTED,
(short) 0);
-        KILOMETRES_PER_HOUR = add(mps, LinearConverter.scale(6, 100),     "km∕h", UnitRegistry.ACCEPTED,
(short) 0);
-        CELSIUS             = add(K,   LinearConverter.create(1, 273.15), "°C",   UnitRegistry.SI,
      (short) 0);
+        KILOGRAM            = add(Mass.class,      mass,                   "kg",   UnitRegistry.SI,
      (short) 0);
+        CUBIC_METRE         = add(Volume.class,    length.pow(3),          "m³",   UnitRegistry.SI,
      (short) 0);
+        NEWTON              = add(Force.class,     force,                  "N",    UnitRegistry.SI,
      (short) 0);
+        JOULE               = add(Energy.class,    energy,                 "J",    UnitRegistry.SI,
      (short) 0);
+        WATT                = add(Power.class,     energy.divide(time),    "W",    UnitRegistry.SI,
      (short) 0);
+        HERTZ               = add(Frequency.class, time.pow(-1),           "Hz",   UnitRegistry.SI,
      (short) 0);
+        HECTOPASCAL         = add(Pa, hecto,                               "hPa",  UnitRegistry.SI,
      (short) 0);
+        HECTARE             = add(m2,  LinearConverter.scale(10000, 1),    "ha",   UnitRegistry.ACCEPTED,
(short) 0);
+        KILOMETRES_PER_HOUR = add(mps, LinearConverter.scale(6, 100),      "km∕h", UnitRegistry.ACCEPTED,
(short) 0);
+        CELSIUS             = add(K,   LinearConverter.offset(27315, 100), "°C",   UnitRegistry.SI,
      (short) 0);
         /*
          * All Unit<Dimensionless>
          */

Modified: sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
URL: http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java?rev=1766601&r1=1766600&r2=1766601&view=diff
==============================================================================
--- sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
[UTF-8] (original)
+++ sis/branches/JDK8/core/sis-utility/src/test/java/org/apache/sis/measure/LinearConverterTest.java
[UTF-8] Tue Oct 25 21:16:55 2016
@@ -16,12 +16,16 @@
  */
 package org.apache.sis.measure;
 
+import java.math.BigDecimal;
 import java.lang.reflect.Field;
-import org.apache.sis.test.TestCase;
+import javax.measure.UnitConverter;
+import org.apache.sis.math.Fraction;
 import org.apache.sis.util.ArraysExt;
+import org.apache.sis.test.TestCase;
+import org.apache.sis.test.DependsOnMethod;
 import org.junit.Test;
 
-import static org.junit.Assert.*;
+import static org.apache.sis.test.Assert.*;
 
 
 /**
@@ -34,6 +38,29 @@ import static org.junit.Assert.*;
  */
 public final strictfp class LinearConverterTest extends TestCase {
     /**
+     * Asserts that the given converter is a linear converter with the given scale factor
and no offset.
+     * The scale factor is given by the ratio of the given numerator and denominator.
+     *
+     * @param  numerator    the expected numerator in the conversion factor.
+     * @param  denominator  the expected denominator in the conversion factor.
+     * @param  converter    the converter to verify.
+     */
+    private static void assertScale(final int numerator, final int denominator, final LinearConverter
converter) {
+        final double derivative = numerator / (double) denominator;
+        final Number[] coefficients = converter.coefficients();
+        assertEquals("coefficients.length", 2, coefficients.length);
+        assertEquals("offset", 0, coefficients[0].doubleValue(), STRICT);
+        assertEquals("scale", derivative, coefficients[1].doubleValue(), STRICT);
+        if (denominator != 1) {
+            assertInstanceOf("coefficients[1]", Fraction.class, coefficients[1]);
+            final Fraction f = (Fraction) coefficients[1];
+            assertEquals("numerator",   numerator,   f.numerator);
+            assertEquals("denominator", denominator, f.denominator);
+        }
+        assertEquals("derivative", derivative, converter.derivative(0), STRICT);
+    }
+
+    /**
      * Ensures that the characters in the {@link LinearConverter#PREFIXES} array are in strictly
increasing order,
      * and that {@link LinearConverter#POWERS} has the same length.
      *
@@ -50,4 +77,183 @@ public final strictfp class LinearConver
         f.setAccessible(true);
         assertEquals("length", prefixes.length, ((byte[]) f.get(null)).length);
     }
+
+    /**
+     * Tests the {@link LinearConverter#forPrefix(char)} method. This also indirectly tests
the
+     * {@link LinearConverter#scale(double, double)} and {@link LinearConverter#coefficients()}
+     * methods.
+     */
+    @Test
+    public void testForPrefix() {
+        assertScale(1000000,    1, LinearConverter.forPrefix('M'));
+        assertScale(   1000,    1, LinearConverter.forPrefix('k'));
+        assertScale(      1,  100, LinearConverter.forPrefix('c'));
+        assertScale(      1, 1000, LinearConverter.forPrefix('m'));
+    }
+
+    /**
+     * Tests {@link LinearConverter#pow(UnitConverter, int, boolean)}.
+     */
+    @Test
+    public void testPow() {
+        LinearConverter c = LinearConverter.scale(10, 3);
+        assertScale( 100,  9, LinearConverter.pow(c, 2, false));
+        assertScale(1000, 27, LinearConverter.pow(c, 3, false));
+
+        c = LinearConverter.scale(1000, 27);
+        assertScale(10, 3, LinearConverter.pow(c, 3, true));
+    }
+
+    /**
+     * Tests the {@link LinearConverter#isIdentity()} and {@link LinearConverter#isLinear()}
methods.
+     * This also indirectly test the {@link LinearConverter#offset(double, double)} and
+     * {@link LinearConverter#coefficients()} methods.
+     */
+    @Test
+    public void testIsIdentityAndLinear() {
+        LinearConverter c = LinearConverter.IDENTITY;
+        assertTrue(c.isIdentity());
+        assertTrue(c.isLinear());
+        assertEquals("coefficients.length", 0, c.coefficients().length);
+
+        c = LinearConverter.scale(100, 100);
+        assertTrue(c.isIdentity());
+        assertTrue(c.isLinear());
+        assertEquals("coefficients.length", 0, c.coefficients().length);
+
+        c = LinearConverter.scale(254, 100);
+        assertFalse(c.isIdentity());
+        assertTrue (c.isLinear());
+        assertScale(254, 100, c);
+    }
+
+    /**
+     * Tests {@link LinearConverter#convert(double)}. This method tests also the pertinence
of
+     * representing the conversion factor by a ratio instead than a single {@code double}
value.
+     */
+    @Test
+    public void testConvertDouble() {
+        LinearConverter c = LinearConverter.scale(254, 100);            // inches to centimetres
+        assertEquals(1143, c.convert(450), STRICT);
+        /*
+         * Below is an example of case giving a different result depending on whether we
use the straightforward
+         * y = (x⋅scale + offset)  equation or the longer  y = (x⋅scale + offset) ∕
divisor  equation.  This test
+         * uses the US survey foot, which is defined as exactly 1200∕3937 metres.  That
conversion factor has no
+         * exact representation in the 'double' type. Converting 200 metres to US survey
foot gives 656.1666666…
+         * Converting that value back to US survey foot with (x⋅scale + offset) gives 199.99999999999997
metres,
+         * but the longer equation used by UnitConverter gives exactly 200 metres as expected.
+         *
+         * Reminder: we usually don't care much about those rounding errors as they are unavoidable
when doing
+         * floating point arithmetic and the scale factor are usually no more accurate in
base 10 than base 2.
+         * But unit conversions are special cases since the conversion factors are exact
in base 10 by definition.
+         */
+        c = LinearConverter.scale(1200, 3937);                          // US survey feet
to metres
+        assertEquals(200, c.convert(656.16666666666667), STRICT);       // Really want STRICT;
see above comment
+        /*
+         * Test conversion from degrees Celsius to Kelvin. The straightforward equation gives
300.15999999999997
+         * while the longer equation used by UnitConverter gives 300.16 as expected.
+         */
+        c = LinearConverter.offset(27315, 100);                         // Celsius to kelvin
+        assertEquals(300.16, c.convert(27.01), STRICT);                 // Really want STRICT;
see above comment
+    }
+
+    /**
+     * Tests {@link LinearConverter#convert(Number)} with a value of type {@link Float}.
+     * This method indirectly tests {@link org.apache.sis.math.DecimalFunctions#floatToDouble(float)}.
+     */
+    @Test
+    @DependsOnMethod("testConvertDouble")
+    public void testConvertFloat() {
+        LinearConverter c = LinearConverter.offset(27315, 100);
+        final Number n = c.convert(Float.valueOf(27.01f));
+        assertInstanceOf("convert(Float)", Double.class, n);
+        assertEquals(300.16, n.doubleValue(), STRICT);                  // Really want STRICT;
see testConvertDouble()
+    }
+
+    /**
+     * Tests {@link LinearConverter#convert(Number)} with a value of type {@link BigDecimal}.
+     */
+    @Test
+    @DependsOnMethod("testConvertDouble")
+    public void testConvertBigDecimal() {
+        LinearConverter c = LinearConverter.offset(27315, 100);
+        final Number n = c.convert(new BigDecimal("27.01"));
+        assertInstanceOf("convert(BigDecimal)", BigDecimal.class, n);
+        assertEquals(new BigDecimal("300.16"), n);
+    }
+
+    /**
+     * Tests {@link LinearConverter#inverse()}.
+     */
+    @Test
+    public void testInverse() {
+        LinearConverter c = LinearConverter.scale(254, 100);
+        LinearConverter inv = (LinearConverter) c.inverse();
+        assertScale(254, 100, c);
+        assertScale(100, 254, inv);
+        assertEquals(12.3, c.convert(inv.convert(12.3)), STRICT);
+        /*
+         * Following is an example of case where our effort regarding preserving accuracy
in base 10 does not work.
+         * However the concatenation of those two UnitConverter gives the identity converter,
as expected.
+         * That concatenation is not verified here because it is not the purpose of this
test case.
+         */
+        c = LinearConverter.offset(27315, 100);
+        inv = (LinearConverter) c.inverse();
+        assertEquals(12.3, c.convert(inv.convert(12.3)), 1E-13);
+    }
+
+    /**
+     * Tests {@link LinearConverter#concatenate(UnitConverter)}.
+     */
+    @Test
+    public void testConcatenate() {
+        LinearConverter c = LinearConverter.scale(254, 100);                        // inches
to centimetres
+        assertScale(254, 100, c);
+        c = (LinearConverter) c.concatenate(LinearConverter.scale(10, 1));          // centimetres
to millimetres
+        assertScale(254, 10, c);
+        c = (LinearConverter) c.concatenate(LinearConverter.scale(1, 1000));        // millimetres
to metres
+        assertScale(254, 10000, c);
+
+        c = LinearConverter.offset(27315, 100);                                     // Celsius
to kelvin
+        c = (LinearConverter) c.concatenate(LinearConverter.offset(-54630, 200));
+        assertTrue(c.isIdentity());
+    }
+
+    /**
+     * Tests {@link LinearConverter#equals(Object)} and {@link LinearConverter#hashCode()}.
+     */
+    @Test
+    public void testEquals() {
+        final LinearConverter c1 = LinearConverter.scale(254, 100);
+        final LinearConverter c2 = LinearConverter.scale( 25, 100);
+        final LinearConverter c3 = LinearConverter.scale(254, 100);
+        assertFalse(c1.equals(c2));
+        assertTrue (c1.equals(c3));
+        assertFalse(c2.equals(c3));
+        assertFalse(c1.hashCode() == c2.hashCode());
+        assertTrue (c1.hashCode() == c3.hashCode());
+        assertFalse(c2.hashCode() == c3.hashCode());
+    }
+
+    /**
+     * Tests serialization of a {@link UnitConverter}.
+     */
+    @Test
+    @DependsOnMethod("testEquals")
+    public void testSerialization() {
+        LinearConverter c = LinearConverter.scale(254, 100);
+        assertNotSame(c, assertSerializedEquals(c));
+    }
+
+    /**
+     * Tests {@link LinearConverter#toString()}, mostly for debugging purpose.
+     */
+    @Test
+    public void testToString() {
+        assertEquals("y = x",                   LinearConverter.IDENTITY          .toString());
+        assertEquals("y = 100⋅x",               LinearConverter.scale (  100,   1).toString());
+        assertEquals("y = x∕100",               LinearConverter.scale (    1, 100).toString());
+        assertEquals("y = 254⋅x∕100",           LinearConverter.scale (  254, 100).toString());
+        assertEquals("y = (100⋅x + 27315)∕100", LinearConverter.offset(27315, 100).toString());
+    }
 }




Mime
View raw message