sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Try to preserve better the unit symbol used in expressions like "J/kg" (i.e. do not replace "J/kg" by "m²∕s²"). https://issues.apache.org/jira/browse/SIS-378
Date Sat, 04 Aug 2018 16:01:17 GMT
This is an automated email from the ASF dual-hosted git repository.

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


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new 30889f0  Try to preserve better the unit symbol used in expressions like "J/kg" (i.e. do not replace "J/kg" by "m²∕s²"). https://issues.apache.org/jira/browse/SIS-378
30889f0 is described below

commit 30889f050d1b99200cd156dfca98208571cda165
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Tue Jul 31 10:42:52 2018 +0200

    Try to preserve better the unit symbol used in expressions like "J/kg" (i.e. do not replace "J/kg" by "m²∕s²").
    https://issues.apache.org/jira/browse/SIS-378
---
 .../main/java/org/apache/sis/math/Fraction.java    |  55 ++++++
 .../java/org/apache/sis/measure/AbstractUnit.java  |  35 ++--
 .../org/apache/sis/measure/ConventionalUnit.java   |  42 +++--
 .../org/apache/sis/measure/LinearConverter.java    |   4 +-
 .../java/org/apache/sis/measure/SystemUnit.java    | 122 +++++++++++---
 .../java/org/apache/sis/measure/UnitDimension.java |  27 ++-
 .../java/org/apache/sis/measure/UnitFormat.java    | 184 ++++++++++++++-------
 .../java/org/apache/sis/math/FractionTest.java     |  27 +++
 .../apache/sis/measure/ConventionalUnitTest.java   |  19 ++-
 .../org/apache/sis/measure/UnitFormatTest.java     | 128 +++++++++++++-
 10 files changed, 521 insertions(+), 122 deletions(-)

diff --git a/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java b/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
index acc3cdd..2862f94 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/math/Fraction.java
@@ -17,6 +17,7 @@
 package org.apache.sis.math;
 
 import java.io.Serializable;
+import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.collection.WeakHashSet;
 
 
@@ -472,4 +473,58 @@ public final class Fraction extends Number implements Comparable<Fraction>, Seri
         }
         return new StringBuilder().append(numerator).append('⁄').append(denominator).toString();
     }
+
+    /**
+     * Creates a new fraction from the given text. This constructor is the converse of {@link #toString()} method.
+     * It can parse single numbers like "3", fractions like "2/3", Unicode characters like "⅔" and infinity symbols
+     * "∞" and "−∞". The given text shall not contain spaces.
+     *
+     * @param  s  the text to parse.
+     * @throws NumberFormatException if the given text can not be parsed.
+     *
+     * @since 1.0
+     */
+    public Fraction(final String s) throws NumberFormatException {
+        ArgumentChecks.ensureNonEmpty("s", s);
+        final int length = s.length();
+        if (length == 1) {
+            final char c = s.charAt(0);
+            if (c >= 128) {
+                for (int j=0; j<UNICODES.length; j++) {
+                    final char[] unicodes = UNICODES[j];
+                    for (int i=0; i<unicodes.length; i++) {
+                        if (unicodes[i] == c) {
+                            numerator   = j;
+                            denominator = j + i + 1;
+                            return;
+                        }
+                    }
+                }
+                if (c == '∞') {
+                    numerator   = 1;
+                    denominator = 0;
+                    return;
+                }
+            }
+        }
+        if (s.equals("−∞") || s.equals("-∞")) {
+            numerator   = -1;
+            denominator =  0;
+            return;
+        }
+        for (int i=0; i<length; i++) {
+            switch (s.charAt(i)) {
+                case '÷':
+                case '⁄':
+                case '/':
+                case '∕': {
+                    numerator   = Integer.parseInt(s.substring(0,i));       // TODO: revisit with JDK9.
+                    denominator = Integer.parseInt(s.substring(i+1));
+                    return;
+                }
+            }
+        }
+        numerator   = Integer.parseInt(s);
+        denominator = 1;
+    }
 }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java b/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
index 4e4f857..871a80d 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/AbstractUnit.java
@@ -67,6 +67,12 @@ abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, LenientCo
     private static final long serialVersionUID = -5559950920796714303L;
 
     /**
+     * The multiplication and division symbols used for Unicode representation.
+     * Also used for internal representation of {@link #symbol}.
+     */
+    static final char MULTIPLY = '⋅', DIVIDE = '∕';
+
+    /**
      * 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.
      * If non-null, this symbol should complies with the {@link UnitFormat.Style#SYMBOL} formatting
@@ -160,11 +166,12 @@ abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, LenientCo
      */
     @Override
     public final String getName() {
-        try {
+        if (symbol != null) try {
             return UnitFormat.getBundle(Locale.getDefault()).getString(symbol);
         } catch (MissingResourceException e) {
-            return null;
+            // Ignore as per this method contract.
         }
+        return null;
     }
 
     /**
@@ -238,12 +245,10 @@ abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, LenientCo
      * @return this unit scaled by the specified multiplier.
      */
     @Override
-    public final Unit<Q> multiply(final double multiplier) {
-        final double r = inverse(multiplier);
-        if (!Double.isNaN(r)) {
-            return divide(r);
-        }
-        return transform(LinearConverter.scale(multiplier, 1));
+    public final Unit<Q> multiply(double multiplier) {
+        final double divisor = inverse(multiplier);
+        if (divisor != 1) multiplier = 1;
+        return transform(LinearConverter.scale(multiplier, divisor));
     }
 
     /**
@@ -254,16 +259,14 @@ abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, LenientCo
      * @return this unit divided by the specified divisor.
      */
     @Override
-    public final Unit<Q> divide(final double divisor) {
-        final double r = inverse(divisor);
-        if (!Double.isNaN(r)) {
-            return multiply(r);
-        }
-        return transform(LinearConverter.scale(1, divisor));
+    public final Unit<Q> divide(double divisor) {
+        final double multiplier = inverse(divisor);
+        if (multiplier != 1) divisor = 1;
+        return transform(LinearConverter.scale(multiplier, divisor));
     }
 
     /**
-     * If the inverse of the given multiplier is an integer, returns that inverse. Otherwise returns NaN.
+     * If the inverse of the given multiplier is an integer, returns that inverse. Otherwise returns 1.
      * This method is used for replacing e.g. {@code multiply(0.001)} calls by {@code divide(1000)} calls.
      * The later allows more accurate operations because of the way {@link LinearConverter} is implemented.
      */
@@ -275,7 +278,7 @@ abstract class AbstractUnit<Q extends Quantity<Q>> implements Unit<Q>, LenientCo
                 return r;
             }
         }
-        return Double.NaN;
+        return 1;
     }
 
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java b/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
index 9949cd7..d9c6051 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/ConventionalUnit.java
@@ -94,7 +94,6 @@ final class ConventionalUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> {
      * @param  target    the base or derived units to which the new unit will be related.
      * @param  toTarget  the conversion from the new unit to the {@code target} unit.
      */
-    @SuppressWarnings("unchecked")
     static <Q extends Quantity<Q>> AbstractUnit<Q> create(final AbstractUnit<Q> target, final UnitConverter toTarget) {
         if (toTarget.isIdentity()) {
             return target;
@@ -149,25 +148,36 @@ final class ConventionalUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> {
          * have more information.  For example instances provided by Units static constants may contain an
          * EPSG code, or even an alternative symbol (e.g. “hm²” will be replaced by “ha” for hectare).
          */
-        final ConventionalUnit<Q> unit = new ConventionalUnit<>(target, toTarget, symbol, (byte) 0, (short) 0);
+        ConventionalUnit<Q> unit = new ConventionalUnit<>(target, toTarget, symbol, (byte) 0, (short) 0);
         if (symbol != null) {
-            final Object existing = UnitRegistry.putIfAbsent(symbol, unit);
-            if (existing instanceof ConventionalUnit<?>) {
-                final ConventionalUnit<?> c = (ConventionalUnit<?>) existing;
-                if (target.equals(c.target)) {
-                    final boolean equivalent;
-                    if (toTarget instanceof LinearConverter && c.toTarget instanceof LinearConverter) {
-                        equivalent = ((LinearConverter) toTarget).equivalent((LinearConverter) c.toTarget);
-                    } else {
-                        equivalent = toTarget.equals(c.toTarget);   // Fallback for unknown implementations.
-                    }
-                    if (equivalent) {
-                        return (ConventionalUnit<Q>) c;
-                    }
+            unit = unit.unique(symbol);
+        }
+        return unit;
+    }
+
+    /**
+     * Returns a unique instance of this unit if available, or store this unit in the map of existing unit otherwise.
+     *
+     * @param  symbol  the symbol of this unit, which must be non-null.
+     */
+    @SuppressWarnings("unchecked")
+    final ConventionalUnit<Q> unique(final String symbol) {
+        final Object existing = UnitRegistry.putIfAbsent(symbol, this);
+        if (existing instanceof ConventionalUnit<?>) {
+            final ConventionalUnit<?> c = (ConventionalUnit<?>) existing;
+            if (target.equals(c.target)) {
+                final boolean equivalent;
+                if (toTarget instanceof LinearConverter && c.toTarget instanceof LinearConverter) {
+                    equivalent = ((LinearConverter) toTarget).equivalent((LinearConverter) c.toTarget);
+                } else {
+                    equivalent = toTarget.equals(c.toTarget);   // Fallback for unknown implementations.
+                }
+                if (equivalent) {
+                    return (ConventionalUnit<Q>) c;
                 }
             }
         }
-        return unit;
+        return this;
     }
 
     /**
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java b/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
index 03cf2e0..69b36e8 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/LinearConverter.java
@@ -476,7 +476,7 @@ final class LinearConverter extends AbstractConverter implements LenientComparab
         }
         if (scale != 1) {
             StringBuilders.trimFractionalPart(buffer.append(scale));
-            buffer.append('⋅');
+            buffer.append(AbstractUnit.MULTIPLY);
         }
         buffer.append('x');
         if (offset != 0) {
@@ -484,7 +484,7 @@ final class LinearConverter extends AbstractConverter implements LenientComparab
             buffer.append(')');
         }
         if (divisor != 1) {
-            StringBuilders.trimFractionalPart(buffer.append('∕').append(divisor));
+            StringBuilders.trimFractionalPart(buffer.append(AbstractUnit.DIVIDE).append(divisor));
         }
         return buffer.toString();
     }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java b/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
index 1af0fc6..16edc25 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/SystemUnit.java
@@ -27,6 +27,7 @@ import javax.measure.UnitConverter;
 import javax.measure.UnconvertibleException;
 import javax.measure.IncommensurableException;
 import javax.measure.spi.QuantityFactory;
+import org.apache.sis.util.Characters;
 import org.apache.sis.util.ArgumentChecks;
 import org.apache.sis.util.ObjectConverters;
 import org.apache.sis.util.ComparisonMode;
@@ -42,7 +43,7 @@ import org.apache.sis.math.Fraction;
  * without scale factor or offset.
  *
  * @author  Martin Desruisseaux (MPO, Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @param <Q>  the kind of quantity to be measured using this units.
  *
@@ -103,19 +104,57 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
     /**
      * Returns a unit of the given dimension with default name and symbol.
      * This method is invoked for creating the result of arithmetic operations.
+     * If there is no predefined unit for the given dimension, then the new unit may be allocated a symbol derived from
+     * this unit's symbol. A new symbol is created only if this unit symbol and the {@code other} unit symbol are simple
+     * (for example "m" but not "m²", or "N" but not "N/m").
+     *
+     * @param  operation  symbol to write after the symbol of this unit for generating the new unit symbol, or 0
+     *                    for not inferring new symbol. Ignored if the condition documented in javadoc does not hold.
+     * @param  other      other units to append after the operation symbol, or {@code null} if none.
+     *                    Ignored if the condition documented in javadoc does not hold.
      */
-    private SystemUnit<?> create(final UnitDimension dim) {
-        if (dim == dimension) {
+    private SystemUnit<?> create(final UnitDimension newDimension, final char operation, final Unit<?> other) {
+        if (newDimension == dimension) {
             return this;
         }
-        SystemUnit<?> result = Units.get(dim);
+        SystemUnit<?> result = Units.get(newDimension);
         if (result == null) {
-            result = new SystemUnit<>(null, dim, null, (byte) 0, (short) 0, null);
+            String symbol = null;
+            if (operation != 0) {
+                final String ts = getSymbol();
+                if (invalidCharForSymbol(ts) == -1) {
+                    if (other == null) {
+                        symbol = (ts + operation).intern();
+                    } else {
+                        final String os = other.getSymbol();
+                        if (invalidCharForSymbol(os) == -1) {
+                            symbol = (ts + operation + os).intern();
+                        }
+                    }
+                }
+            }
+            result = new SystemUnit<>(null, newDimension, symbol, (byte) 0, (short) 0, null);
         }
         return result;
     }
 
     /**
+     * If the given symbol contains an invalid character for a unit symbol, returns the character code point.
+     * Otherwise if the given symbol is null or empty, returns -2. Otherwise (the symbol is valid) returns -1.
+     */
+    private static int invalidCharForSymbol(final String symbol) {
+        if (symbol == null || symbol.isEmpty()) {
+            return -2;
+        }
+        for (int i=0; i < symbol.length();) {
+            final int c = symbol.codePointAt(i);
+            if (!isSymbolChar(c)) return c;
+            i += Character.charCount(c);
+        }
+        return -1;
+    }
+
+    /**
      * Returns the dimension of this unit.
      * Two units {@code u1} and {@code u2} are {@linkplain #isCompatible(Unit) compatible}
      * if and only if {@code u1.getDimension().equals(u2.getDimension())}.
@@ -358,13 +397,10 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
     @SuppressWarnings("unchecked")
     public Unit<Q> alternate(final String symbol) {
         ArgumentChecks.ensureNonEmpty("symbol", symbol);
-        for (int i=0; i < symbol.length();) {
-            final int c = symbol.codePointAt(i);
-            if (!isSymbolChar(c)) {
-                throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2,
-                        "symbol", String.valueOf(Character.toChars(c))));
-            }
-            i += Character.charCount(c);
+        final int c = invalidCharForSymbol(symbol);
+        if (c >= 0) {
+            throw new IllegalArgumentException(Errors.format(Errors.Keys.IllegalCharacter_2,
+                    "symbol", String.valueOf(Character.toChars(c))));
         }
         if (symbol.equals(getSymbol())) {
             return this;
@@ -405,7 +441,8 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
     @Override
     public Unit<?> multiply(final Unit<?> multiplier) {
         ArgumentChecks.ensureNonNull("multiplier", multiplier);
-        return combine(multiplier, false);
+        if (multiplier == this) return pow(2);                      // For formating e.g. "K²" instead than "K⋅K".
+        return product(multiplier, false);
     }
 
     /**
@@ -417,22 +454,33 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
     @Override
     public Unit<?> divide(final Unit<?> divisor) {
         ArgumentChecks.ensureNonNull("divisor", divisor);
-        return combine(divisor, true);
+        return product(divisor, true);
     }
 
     /**
      * Implementation of {@link #multiply(Unit)} and {@link #divide(Unit)} methods.
+     *
+     * @param  inverse  wether to use the inverse of {@code other}.
      */
-    private <T extends Quantity<T>> Unit<?> combine(final Unit<T> other, final boolean divide) {
-        final Unit<T> step = other.getSystemUnit();
-        final Dimension dim = step.getDimension();
-        Unit<?> result = create(divide ? dimension.divide(dim) : dimension.multiply(dim));
-        if (step != other) {
-            UnitConverter c = other.getConverterTo(step);
+    private <T extends Quantity<T>> Unit<?> product(final Unit<T> other, final boolean inverse) {
+        final Unit<T> intermediate = other.getSystemUnit();
+        final Dimension dim = intermediate.getDimension();
+        final UnitDimension newDimension;
+        final char operation;
+        if (inverse) {
+            operation = DIVIDE;
+            newDimension = dimension.divide(dim);
+        } else {
+            operation = MULTIPLY;
+            newDimension = dimension.multiply(dim);
+        }
+        Unit<?> result = create(newDimension, operation, other);
+        if (intermediate != other) {
+            UnitConverter c = other.getConverterTo(intermediate);
             if (!c.isLinear()) {
                 throw new IllegalArgumentException(Errors.format(Errors.Keys.NonRatioUnit_1, other));
             }
-            if (divide) c = c.inverse();
+            if (inverse) c = c.inverse();
             result = result.transform(c);
         }
         return result;
@@ -446,7 +494,7 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
      */
     @Override
     public Unit<?> pow(final int n) {
-        return create(dimension.pow(n));
+        return create(dimension.pow(n), (n >= 0 && n <= 9) ? Characters.toSuperScript((char) ('0' + n)) : 0, null);
     }
 
     /**
@@ -458,7 +506,7 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
      */
     @Override
     public Unit<?> root(final int n) {
-        return create(dimension.root(n));
+        return create(dimension.root(n), (char) 0, null);
     }
 
     /**
@@ -472,9 +520,31 @@ final class SystemUnit<Q extends Quantity<Q>> extends AbstractUnit<Q> implements
     public Unit<Q> transform(UnitConverter operation) {
         ArgumentChecks.ensureNonNull("operation", operation);
         AbstractUnit<Q> base = this;
-        if (this == Units.KILOGRAM) {
-            base = (AbstractUnit<Q>) Units.GRAM;
-            operation = operation.concatenate(LinearConverter.forPrefix('k'));
+        if ((scope & ~UnitRegistry.SI) == 0 && dimension.numeratorIs('M')) {
+            /*
+             * Special case for Units.KILOGRAM, to be replaced by Units.GRAM so a prefix can be computed.
+             * The kilogram may appear in an expression like "kg/m", which we want to replace by "g/m".
+             * We do that by dividing the unit by 1000 (the converter for "milli" prefix).
+             *
+             * Note: we could argue that this block should be UnitFormat work rather than SystemUnit.
+             * For now we perform this work here because this unit symbol may be different than what
+             * UnitFormat would infer, for example because of the symbols recorded by the sequence of
+             * Unit.multiply(Unit) and Unit.divide(Unit) operations. However we may revisit this policy
+             * in a future version.
+             */
+            if (this == Units.KILOGRAM) {
+                base = (AbstractUnit<Q>) Units.GRAM;                    // Optimization for a common case.
+            } else {
+                String symbol = getSymbol();
+                if (symbol != null && symbol.length() >= 3 && symbol.startsWith("kg") && !isSymbolChar(symbol.codePointAt(2))) {
+                    symbol = symbol.substring(1);
+                    base = new ConventionalUnit<>(this, LinearConverter.forPrefix('m'),
+                            symbol, UnitRegistry.PREFIXABLE, (byte) 0).unique(symbol);
+                }
+            }
+            if (base != this) {
+                operation = operation.concatenate(LinearConverter.forPrefix('k'));
+            }
         }
         return ConventionalUnit.create(base, operation);
     }
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java b/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
index d7e77c4..45f159b 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/UnitDimension.java
@@ -54,7 +54,7 @@ import org.apache.sis.internal.util.CollectionsExt;
  * All {@code UnitDimension} instances are immutable and thus inherently thread-safe.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -173,6 +173,31 @@ final class UnitDimension implements Dimension, Serializable {
     }
 
     /**
+     * Returns {@code true} if the numerator is the dimension identified by the given symbol.
+     * This method returns {@code true} only if the numerator is not be raised to any exponent
+     * other than 1. All denominator terms are ignored.
+     */
+    final boolean numeratorIs(final char s) {
+        if (symbol == s) {
+            assert components.keySet().equals(Collections.singleton(this));
+            return true;
+        }
+        boolean found = false;
+        for (final Map.Entry<UnitDimension,Fraction> e : components.entrySet()) {
+            final Fraction value = e.getValue();
+            if (e.getKey().symbol == s) {
+                if (value.numerator != 1 || value.denominator != 1) {
+                    return false;
+                }
+                found = true;
+            } else if (value.signum() >= 0) {
+                return false;
+            }
+        }
+        return found;
+    }
+
+    /**
      * Returns the (fundamental) base dimensions and their exponent whose product is this dimension,
      * or null if this dimension is a base dimension.
      */
diff --git a/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java b/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
index 0ff9cc9..ea77608 100644
--- a/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
+++ b/core/sis-utility/src/main/java/org/apache/sis/measure/UnitFormat.java
@@ -74,7 +74,7 @@ import org.apache.sis.util.collection.WeakValueHashMap;
  * different threads.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  *
  * @see Units#valueOf(String)
  *
@@ -154,7 +154,7 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
          *
          * @see Unit#getSymbol()
          */
-        SYMBOL('⋅', '∕'),
+        SYMBOL(AbstractUnit.MULTIPLY, AbstractUnit.DIVIDE),
 
         /**
          * Format unit symbols using a syntax close to the Unified Code for Units of Measure (UCUM) one.
@@ -212,7 +212,7 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
          *
          * @see Unit#getName()
          */
-        NAME('⋅', '∕');
+        NAME(AbstractUnit.MULTIPLY, AbstractUnit.DIVIDE);
 
         /**
          * Other symbols not in the {@link Style} enumeration because common to all.
@@ -459,14 +459,11 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
     }
 
     /**
-     * Returns the unit instance for the given long (un)localized or name.
+     * Returns the unit instance for the given long (un)localized name.
      * This method is somewhat the converse of {@link #symbolToName()}, but recognizes also
      * international and American spelling of unit names in addition of localized names.
      * The intent is to recognize "meter" as well as "metre".
      *
-     * <p>While we said that {@code UnitFormat} is not thread safe, we make an exception for this method
-     * for allowing the singleton {@link #INSTANCE} to parse symbols in a multi-threads environment.</p>
-     *
      * @param  uom  the unit symbol, without leading or trailing spaces.
      */
     private Unit<?> fromName(String uom) {
@@ -670,14 +667,20 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
             }
             boolean asFloatingPoint = (style == Style.UCUM);
             if (!asFloatingPoint) {
+                /*
+                 * Try to format the scale using exponent, for example 10⁵⋅m instead of 100000⋅m.
+                 * If the scale is not a power of 10, or if we have been requested to format UCUM
+                 * symbol, then we fallback on the usual 'Double.toString(double)' representation.
+                 */
                 double power = Math.log10(scale);
                 if (2*Math.ulp(power) >= Math.abs(power - (power = Math.round(power)))) {
-                    asFloatingPoint = false;
                     toAppendTo.append("10");
                     final String text = Integer.toString((int) power);
                     for (int i=0; i<text.length(); i++) {
                         toAppendTo.append(Characters.toSuperScript(text.charAt(i)));
                     }
+                } else {
+                    asFloatingPoint = true;                         // Scale is not a power of 10.
                 }
             }
             if (asFloatingPoint) {
@@ -688,6 +691,13 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
                 }
                 toAppendTo.append(text, 0, length);
             }
+            /*
+             * The 'formatComponents' method appends division symbol only, no multiplication symbol.
+             * If we have formatted a scale factor and there is at least one component to multiply,
+             * we need to append the multiplication symbol ourselves. Note that 'formatComponents'
+             * put numerators before denominators, so we are sure that the first term after the
+             * multiplication symbol is a numerator.
+             */
             if (hasPositivePower) {
                 toAppendTo.append(style.multiply);
             }
@@ -898,6 +908,13 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
     }
 
     /**
+     * Returns {@code true} if the given character is the sign of a division operator.
+     */
+    private static boolean isDivisor(final char c) {
+        return c == '/' || c == AbstractUnit.DIVIDE;
+    }
+
+    /**
      * Returns {@code true} if the given character sequence contains at least one digit.
      * This is a hack for allowing to recognize units like "100 feet" (in principle not
      * legal, but seen in practice). This verification has some value if digits are not
@@ -1029,7 +1046,7 @@ public class UnitFormat extends Format implements javax.measure.format.UnitForma
          *
          * The 'start' variable is the index of the first character of the next unit term to parse.
          */
-        int operation = NOOP;            // Enumeration value: IMPLICIT, MULTIPLY, DIVIDE.
+        final Operation operation = new Operation();    // Enumeration value: NOOP, IMPLICIT, MULTIPLY, DIVIDE.
         Unit<?> unit = null;
         boolean hasSpaces = false;
         int i = start;
@@ -1045,8 +1062,8 @@ scan:   for (int n; i < end; i += n) {
                  */
                 default:  {
                     if (AbstractUnit.isSymbolChar(c)) {
-                        if (operation == IMPLICIT) {
-                            operation = MULTIPLY;
+                        if (operation.code == Operation.IMPLICIT) {
+                            operation.code =  Operation.MULTIPLY;
                         }
                         continue;
                     }
@@ -1067,14 +1084,14 @@ scan:   for (int n; i < end; i += n) {
                 case Style.EXPONENT_OR_MULTIPLY: {
                     final int w = exponentOperator(symbols, i, end);
                     if (w < 0) {
-                        next = MULTIPLY;
+                        next = Operation.MULTIPLY;
                         break;
                     }
                     i += w;
                     // else fall through.
                 }
                 case Style.EXPONENT: {
-                    if (operation == IMPLICIT) {
+                    if (operation.code == Operation.IMPLICIT) {
                         // Support of exponentiation after parenthesis is not yet supported.
                         break scan;
                     }
@@ -1087,12 +1104,12 @@ scan:   for (int n; i < end; i += n) {
                  * is interpreted as a decimal separator if there is a decimal digit before and after it.
                  */
                 case '.': if (isDecimalSeparator(symbols, i, end)) continue;
-                case '⋅': // Fall through
-                case '×': next = MULTIPLY; break;
+                case '×': // Fall through
+                case AbstractUnit.MULTIPLY: next = Operation.MULTIPLY; break;
                 case '÷':
                 case '⁄': // Fraction slash
                 case '/':
-                case '∕': next = DIVIDE; break;
+                case AbstractUnit.DIVIDE: next = Operation.DIVIDE; break;
                 /*
                  * If we find an '(' parenthesis, invoke recursively this method for the part inside parenthesis.
                  * The parsing should end at the ')' parenthesis since it is not a valid unit symbol. If we do not
@@ -1106,24 +1123,35 @@ scan:   for (int n; i < end; i += n) {
                         throw new ParserException(Errors.format(Errors.Keys.NonEquilibratedParenthesis_2,
                                symbols.subSequence(start, i), Style.CLOSE), symbols, start);
                     }
-                    unit = apply(operation, unit, term);
-                    operation = IMPLICIT;       // Default operation if there is no × or / symbols after parenthesis.
-                    start = i + (n = 1);        // Skip the number of characters in the '(' Unicode code point.
+                    unit = operation.apply(unit, term);
+                    operation.code = Operation.IMPLICIT;    // Default operation if there is no × or / symbols after parenthesis.
+                    start = i + (n = 1);                    // Skip the number of characters in the '(' Unicode code point.
                     continue;
                 }
             }
             /*
+             * We reach this point only if we found some operator (division or multiplication).
+             * If the operator has been found between two digits, we consider it as part of the
+             * term. For example "m2/3" is considered as a single term where "2/3" is the exponent.
+             */
+            if (i > start && i+n < end
+                    && Character.isDigit(Character.codePointBefore(symbols, i))
+                    && Character.isDigit(Character.codePointAt(symbols, i+n)))
+            {
+                continue;
+            }
+            /*
              * At this point, we have either a first unit to parse (NOOP), or a multiplication or division to apply
              * between the previously parsed units and the next unit to parse. A special case is IMPLICIT, which is
              * a multiplication without explicit × symbol after the parenthesis. The implicit multiplication can be
              * overridden by an explicit × or / symbol, which is what happened if we reach this point (tip: look in
              * the above 'switch' statement all cases that end with 'break', not 'break scan' or 'continue').
              */
-            if (operation != IMPLICIT) {
-                unit = apply(operation, unit, parseTerm(symbols, start, i));
+            if (operation.code != Operation.IMPLICIT) {
+                unit = operation.apply(unit, parseTerm(symbols, start, i, operation));
             }
             hasSpaces = false;
-            operation = next;
+            operation.code = next;
             start = i + n;
         }
         /*
@@ -1166,48 +1194,86 @@ search:     while ((i = CharSequences.skipTrailingWhitespaces(symbols, start, i)
             }
         }
         if (component == null) {
-            component = parseTerm(symbols, start, i);
+            component = parseTerm(symbols, start, i, operation);
         }
-        unit = apply(operation, unit, component);
+        unit = operation.apply(unit, component);
         position.setIndex(endOfURI >= 0 ? endOfURI : i);
         return unit;
     }
 
     /**
-     * Meaning of some characters parsed by {@link #parse(CharSequence)}. The {@code IMPLICIT} case
-     * is a multiplication without symbol, which can be overridden by an explicit × or / symbol.
+     * Represents an operation to be applied between two terms parsed by
+     * {@link UnitFormat#parseTerm(CharSequence, int, int, Operation)}.
      */
-    private static final int NOOP = 0, IMPLICIT = 1, MULTIPLY = 2, DIVIDE = 3;
+    private static final class Operation {
+        /**
+         * Meaning of some characters parsed by {@link UnitFormat#parse(CharSequence)}.
+         * The {@code IMPLICIT} case is a multiplication without symbol, which can be
+         * overridden by an explicit × or / symbol.
+         */
+        static final int NOOP = 0, IMPLICIT = 1, MULTIPLY = 2, DIVIDE = 3;
 
-    /**
-     * Applies a multiplication or division operation between the given units.
-     *
-     * @param  operation  one of {@link #NOOP}, {@link #IMPLICIT}, {@link #MULTIPLY} or {@link #DIVIDE}.
-     * @param  unit       the left operand, which is the unit parsed so far.
-     * @param  term       the right operation, which is the newly parsed unit.
-     */
-    private static Unit<?> apply(final int operation, final Unit<?> unit, final Unit<?> term) {
-        switch (operation) {
-            case NOOP:     return term;
-            case IMPLICIT:
-            case MULTIPLY: return unit.multiply(term);
-            case DIVIDE:   return unit.divide(term);
-            default: throw new AssertionError(operation);
+        /**
+         * The operation as one of the {@link #NOOP}, {@link #IMPLICIT}, {@link #MULTIPLY}
+         * or {@link #DIVIDE} values.
+         */
+        int code;
+
+        /**
+         * Creates an operation initialized to {@link #NOOP}.
+         */
+        Operation() {
+        }
+
+        /**
+         * Applies a multiplication or division operation between the given units.
+         *
+         * @param  unit  the left operand, which is the unit parsed so far.
+         * @param  term  the right operation, which is the newly parsed unit.
+         */
+        Unit<?> apply(final Unit<?> unit, final Unit<?> term) {
+            switch (code) {
+                case NOOP:     return term;
+                case IMPLICIT:
+                case MULTIPLY: return unit.multiply(term);
+                case DIVIDE:   return unit.divide(term);
+                default: throw new AssertionError(code);
+            }
+        }
+
+        /**
+         * If this operation is a multiplication, replaces by division. Otherwise do nothing
+         * (we do <strong>not</strong> replace division by multiplication). The intent is to
+         * replace units like "m⋅s-1" by "m/s".
+         *
+         * @return whether the operation has been inverted.
+         */
+        boolean invert() {
+            switch (code) {
+                case IMPLICIT:
+                case MULTIPLY: code = DIVIDE; return true;
+                default: return false;
+            }
         }
     }
 
     /**
      * Parses a single unit symbol with its exponent.
-     * The given symbol shall not contain multiplication or division operator.
+     * The given symbol shall not contain multiplication or division operator except in exponent.
+     * Parsing of fractional exponent as in "m2/3" is not yet supported.
      *
-     * @param  symbols  the complete string specified by the user.
-     * @param  lower    index where to begin parsing in the {@code symbols} string.
-     * @param  upper    index after the last character to parse in the {@code symbols} string.
+     * @param  symbols    the complete string specified by the user.
+     * @param  lower      index where to begin parsing in the {@code symbols} string.
+     * @param  upper      index after the last character to parse in the {@code symbols} string.
+     * @param  operation  if the term will be used as multiplier or divisor of another unit, the
+     *                    operation to be applied. Otherwise {@code null}.
      * @return the parsed unit symbol (never {@code null}).
      * @throws ParserException if a problem occurred while parsing the given symbols.
      */
     @SuppressWarnings("fallthrough")
-    private Unit<?> parseTerm(final CharSequence symbols, final int lower, final int upper) throws ParserException {
+    private Unit<?> parseTerm(final CharSequence symbols, final int lower, final int upper, final Operation operation)
+            throws ParserException
+    {
         final String uom = CharSequences.trimWhitespaces(symbols, lower, upper).toString();
         /*
          * Check for labels explicitly given by users. Those labels have precedence over the Apache SIS hard-coded
@@ -1236,12 +1302,12 @@ search:     while ((i = CharSequences.skipTrailingWhitespaces(symbols, start, i)
                     if (isDigit(c) || isSign(c)) {
                         final double multiplier;
                         try {
-                            int s = uom.lastIndexOf(' ');
+                            int s = uom.indexOf(' ');
                             if (s >= 0) {
                                 final int next = CharSequences.skipLeadingWhitespaces(uom, s, length);
                                 if (next < length && AbstractUnit.isSymbolChar(uom.codePointAt(next))) {
                                     multiplier = Double.parseDouble(uom.substring(0, s));
-                                    return parseTerm(uom, s, length).multiply(multiplier);
+                                    return parseTerm(uom, s, length, null).multiply(multiplier);
                                 }
                             }
                             multiplier = parseMultiplicationFactor(uom);
@@ -1258,34 +1324,31 @@ search:     while ((i = CharSequences.skipTrailingWhitespaces(symbols, start, i)
                      * exponent.  That exponent can be a Unicode character (only one character in current UnitFormat
                      * implementation) or a number parseable with Integer.parseInt(String).
                      */
-                    int  power = 1;
+                    Fraction power = null;
                     int  i = length;
                     char c = uom.charAt(--i);
-                    boolean canApply = false;
                     if (Characters.isSuperScript(c)) {
                         c = Characters.toNormalScript(c);
                         if (isDigit(c)) {
-                            power = c - '0';
-                            canApply = true;
+                            power = new Fraction(c - '0', 1);
                         }
                     } else if (isDigit(c)) {
                         do {
                             c = uom.charAt(--i);
-                            if (!isDigit(c)) {
+                            if (!isDigit(c) && !isDivisor(c)) {
                                 if (!isSign(c)) i++;
                                 try {
-                                    power = Integer.parseInt(uom.substring(i));
+                                    power = new Fraction(uom.substring(i));
                                 } catch (NumberFormatException e) {
                                     // Should never happen unless the number is larger than 'int' capacity.
                                     throw (ParserException) new ParserException(Errors.format(
                                             Errors.Keys.UnknownUnit_1, uom), symbols, lower+i).initCause(e);
                                 }
-                                canApply = true;
                                 break;
                             }
                         } while (i != 0);
                     }
-                    if (canApply) {
+                    if (power != null) {
                         /*
                          * At this point we have parsed the exponent. Before to parse the raw unit symbol,
                          * skip the exponent symbol (^, * or **) if any.
@@ -1305,7 +1368,14 @@ search:     while ((i = CharSequences.skipTrailingWhitespaces(symbols, start, i)
                         }
                         unit = getPrefixed(uom.substring(CharSequences.skipLeadingWhitespaces(uom, 0, i), i));
                         if (unit != null) {
-                            return unit.pow(power);
+                            int numerator   = power.numerator;
+                            int denominator = power.denominator;
+                            if (numerator < 0 && operation != null && operation.invert()) {
+                                numerator = -numerator;
+                            }
+                            if (numerator   != 1) unit = unit.pow (numerator);
+                            if (denominator != 1) unit = unit.root(denominator);
+                            return unit;
                         }
                     }
                 }
diff --git a/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java b/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
index afd4552..598f55e 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/math/FractionTest.java
@@ -149,6 +149,33 @@ public final strictfp class FractionTest extends TestCase {
     }
 
     /**
+     * Tests the {@link Fraction#Fraction(String)} constructor.
+     */
+    @Test
+    public void testParse() {
+        verifyParsing( 2,  3,  "2/3");
+        verifyParsing(-2, -3, "-2/-3");
+        verifyParsing( 4,  1,  "4");
+        verifyParsing( 1,  0,  "∞");
+        verifyParsing(-1,  0, "-∞");
+        verifyParsing( 1,  4,  "¼");
+        verifyParsing( 5,  6,  "⅚");
+    }
+
+    /**
+     * Verifies that parsing the given fraction produces the given numerator and denominator.
+     *
+     * @param numerator    the expected numerator.
+     * @param denominator  the expected denominator.
+     * @param s            the text to parse.
+     */
+    private static void verifyParsing(final int numerator, final int denominator, final String s) {
+        final Fraction f = new Fraction(s);
+        assertEquals("numerator",   numerator,   f.numerator);
+        assertEquals("denominator", denominator, f.denominator);
+    }
+
+    /**
      * Tests serialization.
      */
     @Test
diff --git a/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java b/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
index bb40a3f..1155e4f 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/measure/ConventionalUnitTest.java
@@ -35,7 +35,7 @@ import static org.apache.sis.test.Assert.*;
  * but those methods just delegate to {@link ConventionalUnit#create(AbstractUnit, UnitConverter)}.
  *
  * @author  Martin Desruisseaux (Geomatys)
- * @version 0.8
+ * @version 1.0
  * @since   0.8
  * @module
  */
@@ -214,6 +214,23 @@ public final strictfp class ConventionalUnitTest extends TestCase {
     }
 
     /**
+     * Tests operation on kilogram.
+     * The management of SI prefixes need to make a special case for kilogram.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-382">SIS-382</a>
+     * @see UnitFormatTest#testParseKilogram()
+     */
+    @Test
+    public void testKilogram() {
+        Unit<?> unit = Units.KILOGRAM.divide(1E+9);
+        assertEquals("µg", unit.getSymbol());
+        unit = unit.divide(Units.METRE);
+        assertEquals("µg∕m", unit.getSymbol());
+        unit = unit.multiply(1E+3);
+        assertEquals("mg∕m", unit.getSymbol());
+    }
+
+    /**
      * Tests {@link ConventionalUnit#isCompatible(Unit)}.
      */
     @Test
diff --git a/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java b/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
index 2086412..b307211 100644
--- a/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
+++ b/core/sis-utility/src/test/java/org/apache/sis/measure/UnitFormatTest.java
@@ -280,6 +280,19 @@ public final strictfp class UnitFormatTest extends TestCase {
     }
 
     /**
+     * Tests formatting of some more unusual units. The units tested by this method are artificial
+     * and somewhat convolved. The intent is to verify that unit formatting is still robust.
+     */
+    @Test
+    public void testFormatUnusual() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        final Unit<?> u1 = Units.SECOND.pow(-1).multiply(3);
+        assertEquals("3∕s",   f.format(u1));
+        assertEquals("3⋅m∕s", f.format(Units.METRE.multiply(u1)));
+        assertEquals("m^⅔",   f.format(Units.METRE.pow(2).root(3)));
+    }
+
+    /**
      * Tests formatting of units when the numerator is unity.
      *
      * @see <a href="https://issues.apache.org/jira/browse/SIS-414">SIS-414</a>
@@ -438,8 +451,6 @@ public final strictfp class UnitFormatTest extends TestCase {
 
     /**
      * Tests parsing of symbols composed of terms combined by arithmetic operations (e.g. "m/s").
-     *
-     * @see <a href="https://issues.apache.org/jira/browse/SIS-382">SIS-382</a>
      */
     @Test
     @DependsOnMethod("testParseTerms")
@@ -459,9 +470,28 @@ public final strictfp class UnitFormatTest extends TestCase {
     }
 
     /**
+     * Tests parsing of symbols composed of terms containing kilogram.
+     * The management of SI prefixes need to make a special case for kilogram.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-382">SIS-382</a>
+     * @see ConventionalUnitTest#testKilogram()
+     */
+    @Test
+    @DependsOnMethod("testParseTerms")
+    public void testParseKilogram() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        /*
+         * Kilograms should be identified even if they appear in an expression.
+         * Current implementation creates a symbol early when it detect such case.
+         */
+        assertEquals("mg∕m",  f.parse("10^-6.kg/m").getSymbol());
+//      assertEquals("μg∕m³", f.parse("μg.m-3").getSymbol());
+    }
+
+    /**
      * Tests parsing of symbols containing an explicit exponentiation operation.
      * Usually the exponentiation is implicit, as in {@code "m*s-1"}.
-     * However some formats write it explicitely, as in {@code "m*s^-1"}.
+     * However some formats write it explicitly, as in {@code "m*s^-1"}.
      */
     @Test
     @DependsOnMethod("testParseMultiplier")
@@ -526,4 +556,96 @@ public final strictfp class UnitFormatTest extends TestCase {
         assertEquals("otherMeterLabel", f2.format(Units.METRE));
         assertEquals("mySecondLabel",   f2.format(Units.SECOND));
     }
+
+    /**
+     * Tests parsing of miscellaneous symbols, followed by formatting.
+     * This test uses some units defined by World Meteorological Organisation (WMO).
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-378">SIS-378</a>
+     */
+    @Test
+    @DependsOnMethod({"testFormatScaled", "testParseMultiplier"})
+    public void testParseAndFormat() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        roundtrip(f, "K.m2.kg-1.s-1",    "K⋅m²∕(kg⋅s)");
+        roundtrip(f, "Pa.s-1",           "Pa∕s");
+        roundtrip(f, "S.m-1",            "S∕m");
+        roundtrip(f, "m2/3.s-1",         "m^⅔∕s");
+        roundtrip(f, "J.kg-1",           "J∕kg");
+        roundtrip(f, "mol.s-1",          "mol∕s");
+        roundtrip(f, "K.s-1",            "K∕s");
+        roundtrip(f, "m.s-1",            "m∕s");
+        roundtrip(f, "m.s-2",            "m∕s²");
+        roundtrip(f, "Pa.m",             "Pa⋅m");
+        roundtrip(f, "m3.s-1",           "m³∕s");
+        roundtrip(f, "kg.m-2.s-1",       "kg∕(m²⋅s)");
+        roundtrip(f, "K.m-1",            "K∕m");
+        roundtrip(f, "N.m-2",            "Pa");
+        roundtrip(f, "kg.m-2",           "kg∕m²");
+        roundtrip(f, "kg.m-3",           "kg∕m³");
+        roundtrip(f, "K*m.s-1",          "K⋅m∕s");
+        roundtrip(f, "N.m-2.s",          "Pa⋅s");
+        roundtrip(f, "K*m/s",            "K⋅m∕s");
+        roundtrip(f, "kg/kg*Pa/s",       "Pa∕s");
+        roundtrip(f, "kg/kg*m/s",        "m∕s");
+        roundtrip(f, "day",              "d");
+        roundtrip(f, "Pa*Pa",            "Pa²");
+        roundtrip(f, "N.m-1",            "N∕m");
+        roundtrip(f, "m-2.s-1",          "1∕(m²⋅s)");
+        roundtrip(f, "°",                "°");
+        roundtrip(f, "s.m-1",            "s∕m");
+        roundtrip(f, "V.m-1",            "V∕m");
+        roundtrip(f, "m2.s-2",           "m²∕s²");
+        roundtrip(f, "m2.s-1",           "m²∕s");
+        roundtrip(f, "mol.m-3",          "mol∕m³");
+        roundtrip(f, "psu",              "psu");
+        roundtrip(f, "kg-2.s-1",         "1∕(kg²⋅s)");
+        roundtrip(f, "K*K",              "K²");
+        roundtrip(f, "kg.m-3.s-1",       "kg∕(m³⋅s)");
+    }
+
+    /**
+     * Reminder for units parsing and formatting that still need improvement.
+     * The "expected" values checked in this method are not really what we expect,
+     * but they reflect the current behavior of Apache SIS units library. We keep
+     * those tests as a reminder of work to do, but they should be modified if SIS
+     * support of those units is improved in a future version.
+     *
+     * @see <a href="https://issues.apache.org/jira/browse/SIS-378">SIS-378</a>
+     */
+    @Test
+    public void needForImprovements() {
+        final UnitFormat f = new UnitFormat(Locale.UK);
+        roundtrip(f, "m.m6.m-3",         "m⁴");
+        roundtrip(f, "kg.kg-1.m.s-1",    "m∕s");
+        roundtrip(f, "mol.mol-1",        "");
+        roundtrip(f, "μg.m-3",           "10⁻⁹⋅kg∕m³");
+        roundtrip(f, "W.m-2",            "kg∕s³");
+        roundtrip(f, "(m2.s.sr)-1",      "-1⋅m²⋅s");
+        roundtrip(f, "cm/day",           "1.1574074074074074E-7⋅m∕s");
+        roundtrip(f, "m-2.s.rad-1",      "s∕m²");
+        roundtrip(f, "kg.kg-1.s-1",      "Hz");
+        roundtrip(f, "kg/kg*kg/kg",      "");
+        roundtrip(f, "µg.m-3",           "10⁻⁹⋅kg∕m³");
+        roundtrip(f, "W.m-2.Hz-1",       "kg∕s²");
+        roundtrip(f, "W.sr-1.m-2",       "kg∕s³");
+        roundtrip(f, "W.m-1.sr-1",       "W∕m");
+        roundtrip(f, "kg.kg-1",          "");
+        roundtrip(f, "W.m-3.sr-1",       "kg∕(m⋅s³)");
+        roundtrip(f, "K*Pa/s",           "K⋅kg∕(m⋅s³)");
+        roundtrip(f, "m3.m-3",           "");
+        roundtrip(f, "J.m-2",            "kg∕s²");
+        roundtrip(f, "m3.s-1.m-1",       "m²∕s");
+        roundtrip(f, "(kg.m-3)*(m.s-1)", "kg∕(m²⋅s)");
+        roundtrip(f, "W.m-2.nm-1",       "10⁹⋅kg∕(m⋅s³)");
+    }
+
+    /**
+     * Parses the given symbol, then reformat the unit and compares with expected symbol.
+     */
+    private static void roundtrip(final UnitFormat f, final String symbol, final String expected) {
+        final Unit<?> unit = f.parse(symbol);
+        final String actual = f.format(unit);
+        assertEquals(expected, actual);
+    }
 }


Mime
View raw message