common: Add a DecimalUnitFormat formatter
authorMichael Jeanson <mjeanson@efficios.com>
Wed, 13 Apr 2016 21:43:43 +0000 (17:43 -0400)
committerAlexandre Montplaisir <alexmonthy@efficios.com>
Wed, 4 May 2016 20:59:26 +0000 (16:59 -0400)
This format implementation will shorten number by using
SI prefixes (k, M, G,...).

For the special cases of NaN and +- inf, no unit is appended.

Change-Id: I941fcd410f050666c93c5c9c0ecaf4d9f92e2ec3
Signed-off-by: Michael Jeanson <mjeanson@efficios.com>
Signed-off-by: Alexandre Montplaisir <alexmonthy@efficios.com>
Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/70718
Reviewed-by: Hudson CI
Reviewed-by: Patrick Tasse <patrick.tasse@gmail.com>
Tested-by: Patrick Tasse <patrick.tasse@gmail.com>
common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatErrorTest.java [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatFactorTest.java [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatTest.java [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/format/DecimalUnitFormat.java [new file with mode: 0644]

diff --git a/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatErrorTest.java b/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatErrorTest.java
new file mode 100644 (file)
index 0000000..6a76898
--- /dev/null
@@ -0,0 +1,34 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS inc, Michael Jeanson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.tests.format;
+
+import java.text.Format;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.junit.Test;
+
+/**
+ * Test the {@link DecimalUnitFormat} class
+ *
+ * @author Michael Jeanson
+ */
+public class DecimalUnitFormatErrorTest {
+
+    private static final @NonNull Format FORMATTER = new DecimalUnitFormat();
+
+    /**
+     * Test an illegal argument
+     */
+    @Test(expected = IllegalArgumentException.class)
+    public void testException() {
+        FORMATTER.format(new String("Toto"));
+    }
+}
diff --git a/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatFactorTest.java b/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatFactorTest.java
new file mode 100644 (file)
index 0000000..1e34687
--- /dev/null
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS inc, Michael Jeanson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.tests.format;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.Format;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test the {@link DecimalUnitFormat} class
+ *
+ * @author Michael Jeanson
+ */
+@RunWith(Parameterized.class)
+public class DecimalUnitFormatFactorTest {
+
+    private final @NonNull Format fFormat;
+
+    private final @NonNull Number fNumValue;
+    private final @NonNull String fExpected;
+
+    /**
+     * Constructor
+     *
+     * @param value
+     *            The numeric value to format
+     * @param expected
+     *            The expected formatted result
+     * @param factor
+     *            The multiplication factor to apply before formatting
+     */
+    public DecimalUnitFormatFactorTest(@NonNull Number value, @NonNull String expected, @NonNull Double factor) {
+        fNumValue = value;
+        fExpected = expected;
+        fFormat = new DecimalUnitFormat(factor);
+    }
+
+    /**
+     * @return The arrays of parameters
+     */
+    @Parameters(name = "{index}: {0}")
+    public static Iterable<Object[]> getParameters() {
+        return Arrays.asList(new Object[][] {
+                { 0, "0", 10.0 },
+                { 3, "300", 100.0 },
+                { 975, "97.5", 0.1 },
+                { 1000, "1 k", 1.0 },
+                { 4000, "40", 0.01 },
+                { -4000, "-40", 0.01 },
+                { -0.04, "-4", 100.0 },
+                { 0.002, "20", 10000.0 },
+                { 0.0555, "5.5 k", 100000.0 },
+                { 0.0004928373928, "49.3 n", 0.0001 },
+                { 0.000000251, "251 p", 0.001 },
+                { Double.POSITIVE_INFINITY, "∞", 0.001 },
+                { Double.MAX_VALUE, "4", Double.MIN_NORMAL},
+        });
+    }
+
+    /**
+     * Test the {@link Format#format(Object)} method
+     */
+    @Test
+    public void testFormat() {
+        assertEquals("format value", fExpected, fFormat.format(fNumValue));
+    }
+}
diff --git a/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatTest.java b/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/format/DecimalUnitFormatTest.java
new file mode 100644 (file)
index 0000000..d343979
--- /dev/null
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS inc, Michael Jeanson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.tests.format;
+
+import static org.junit.Assert.assertEquals;
+
+import java.text.Format;
+import java.util.Arrays;
+
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.format.DecimalUnitFormat;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test the {@link DecimalUnitFormat} class
+ *
+ * @author Michael Jeanson
+ */
+@RunWith(Parameterized.class)
+public class DecimalUnitFormatTest {
+
+    private static final @NonNull Format FORMATTER = new DecimalUnitFormat();
+
+    private final @NonNull Number fNumValue;
+    private final @NonNull String fExpected;
+
+    /**
+     * Constructor
+     *
+     * @param value
+     *            The numeric value to format
+     * @param expected
+     *            The expected formatted result
+     */
+    public DecimalUnitFormatTest(@NonNull Number value, @NonNull String expected) {
+        fNumValue = value;
+        fExpected = expected;
+    }
+
+    /**
+     * @return The arrays of parameters
+     */
+    @Parameters(name = "{index}: {0}")
+    public static Iterable<Object[]> getParameters() {
+        return Arrays.asList(new Object[][] {
+                { 0, "0" },
+                { 3, "3" },
+                { 975, "975" },
+                { 1000, "1 k" },
+                { 4000, "4 k" },
+                { -4000, "-4 k" },
+                { 4000L, "4 k" },
+                { 4000.0, "4 k" },
+                { 12345678, "12.3 M" },
+                { Integer.MAX_VALUE, "2.1 G" },
+                { Integer.MIN_VALUE, "-2.1 G" },
+                { Long.MAX_VALUE, "9223.4 P" },
+                { 98765432.123456, "98.8 M" },
+                { -98765432.123456, "-98.8 M" },
+                { 555555555555L, "555.6 G" },
+                { 555555555555555L, "555.6 T" },
+                { 100100000, "100.1 M" },
+                { 0.1, "100 m" },
+                { 0.001, "1 m" },
+                { 0.000001, "1 µ" },
+                { 0.000000001, "1 n" },
+                { 0.000000000001, "1 p" },
+                { -0.04, "-40 m" },
+                { 0.002, "2 m" },
+                { 0.0555, "55.5 m" },
+                { 0.0004928373928, "492.8 µ" },
+                { 0.000000251, "251 n"},
+                { 0.000000000043, "43 p"},
+                { 0.000000045643, "45.6 n"},
+                { Double.MAX_VALUE, "179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 P" },
+                { Double.POSITIVE_INFINITY, "∞" },
+                { Double.MIN_NORMAL, "0" },
+                { Double.NEGATIVE_INFINITY, "-∞" },
+                { Double.NaN, "�" },
+        });
+    }
+
+    /**
+     * Test the {@link Format#format(Object)} method
+     */
+    @Test
+    public void testFormat() {
+        assertEquals("format value", fExpected, FORMATTER.format(fNumValue));
+    }
+}
diff --git a/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/format/DecimalUnitFormat.java b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/format/DecimalUnitFormat.java
new file mode 100644 (file)
index 0000000..69f6353
--- /dev/null
@@ -0,0 +1,128 @@
+/*******************************************************************************
+ * Copyright (c) 2016 EfficiOS Inc., Michael Jeanson
+ *
+ * All rights reserved. This program and the accompanying materials are
+ * made available under the terms of the Eclipse Public License v1.0 which
+ * accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *******************************************************************************/
+
+package org.eclipse.tracecompass.common.core.format;
+
+import java.text.DecimalFormat;
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.ParsePosition;
+
+/**
+ * Provides a formatter for decimal numbers with International System of Units
+ * prefixes up to peta (quadrillion). It receives a number and formats it in the
+ * closest thousand's unit, with at most 1 decimal.
+ *
+ * @author Michael Jeanson
+ * @since 2.0
+ */
+public class DecimalUnitFormat extends Format {
+
+    private static final long serialVersionUID = 3650332020346870384L;
+
+    /* International System of Units prefixes */
+    private static final String KILO_PREFIX = "k"; //$NON-NLS-1$
+    private static final String MEGA_PREFIX = "M"; //$NON-NLS-1$
+    private static final String GIGA_PREFIX = "G"; //$NON-NLS-1$
+    private static final String TERA_PREFIX = "T"; //$NON-NLS-1$
+    private static final String PETA_PREFIX = "P"; //$NON-NLS-1$
+
+    private static final String MILLI_PREFIX = "m"; //$NON-NLS-1$
+    private static final String MICRO_PREFIX = "µ"; //$NON-NLS-1$
+    private static final String NANO_PREFIX = "n"; //$NON-NLS-1$
+    private static final String PICO_PREFIX = "p"; //$NON-NLS-1$
+
+    private static final long KILO = 1000L;
+    private static final long MEGA = 1000000L;
+    private static final long GIGA = 1000000000L;
+    private static final long TERA = 1000000000000L;
+    private static final long PETA = 1000000000000000L;
+
+    private static final double MILLI = 0.001;
+    private static final double MICRO = 0.000001;
+    private static final double NANO  = 0.000000001;
+    private static final double PICO  = 0.000000000001;
+
+    private static final Format FORMAT = new DecimalFormat("#.#"); //$NON-NLS-1$
+    private final double fFactor;
+
+
+    /**
+     * Default constructor.
+     */
+    public DecimalUnitFormat() {
+        super();
+        fFactor = 1.0;
+    }
+
+    /**
+     * Constructor with multiplication factor.
+     *
+     * @param factor Multiplication factor to apply to the value
+     */
+    public DecimalUnitFormat(double factor) {
+        super();
+        fFactor = factor;
+    }
+
+    @Override
+    public StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos) {
+        if (obj instanceof Number) {
+            Number num = (Number) obj;
+
+            /* Apply the multiplication factor before formatting */
+            double value = num.doubleValue() * fFactor;
+
+            double abs = Math.abs(value);
+
+            if (Double.isInfinite(value) || Double.isNaN(value) || abs < PICO) {
+                return toAppendTo.append(FORMAT.format(value));
+            }
+
+            if (abs >= 1) {
+                if (abs >= PETA) {
+                    return toAppendTo.append(FORMAT.format(value / PETA)).append(' ').append(PETA_PREFIX);
+                }
+                if (abs >= TERA) {
+                    return toAppendTo.append(FORMAT.format(value / TERA)).append(' ').append(TERA_PREFIX);
+                }
+                if (abs >= GIGA) {
+                    return toAppendTo.append(FORMAT.format(value / GIGA)).append(' ').append(GIGA_PREFIX);
+                }
+                if (abs >= MEGA) {
+                    return toAppendTo.append(FORMAT.format(value / MEGA)).append(' ').append(MEGA_PREFIX);
+                }
+                if (abs >= KILO) {
+                    return toAppendTo.append(FORMAT.format(value / KILO)).append(' ').append(KILO_PREFIX);
+                }
+
+                return toAppendTo.append(FORMAT.format(value));
+            }
+
+            if (abs < NANO) {
+                return toAppendTo.append(FORMAT.format(value * TERA)).append(' ').append(PICO_PREFIX);
+            }
+            if (abs < MICRO) {
+                return toAppendTo.append(FORMAT.format(value * GIGA)).append(' ').append(NANO_PREFIX);
+            }
+            if (abs < MILLI) {
+                return toAppendTo.append(FORMAT.format(value * MEGA)).append(' ').append(MICRO_PREFIX);
+            }
+
+            return toAppendTo.append(FORMAT.format(value * KILO)).append(' ').append(MILLI_PREFIX);
+        }
+
+        throw new IllegalArgumentException("Cannot format given Object as a Number: " + obj); //$NON-NLS-1$
+    }
+
+    @Override
+    public Object parseObject(String source, ParsePosition pos) {
+        return (source == null ? "" : source); //$NON-NLS-1$
+    }
+}
This page took 0.031456 seconds and 5 git commands to generate.