common.core: move saturated math to common core
authorMatthew Khouzam <matthew.khouzam@ericsson.com>
Thu, 28 Jul 2016 21:56:31 +0000 (17:56 -0400)
committerMatthew Khouzam <matthew.khouzam@ericsson.com>
Sat, 30 Jul 2016 01:33:10 +0000 (21:33 -0400)
As overflow problems are more and more common, it is justified
to make saturated (clamped) math available to all packages.

Test coverage is 100%, the code has to be rigourously tested as
this is part of the base of trace compass now.

Change-Id: Icf9771162230b8ff0371015ed0f39cc5ff07a06f
Signed-off-by: Matthew Khouzam <matthew.khouzam@ericsson.com>
Reviewed-on: https://git.eclipse.org/r/78107
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/META-INF/MANIFEST.MF
common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/math/SaturatedArithmeticTest.java [new file with mode: 0644]
common/org.eclipse.tracecompass.common.core/META-INF/MANIFEST.MF
common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/math/SaturatedArithmetic.java [new file with mode: 0644]
tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/timestamp/TmfTimestamp.java

index f957abe39854aa4bfd82e1b87cff3e5916c5a069..c05dd2bf48a416364ee84b6f8cef0ca3de100b88 100644 (file)
@@ -13,7 +13,9 @@ Require-Bundle: org.junit;bundle-version="4.0.0",
  org.eclipse.core.resources,
  org.eclipse.tracecompass.common.core
 Export-Package: org.eclipse.tracecompass.common.core.tests;x-friends:="org.eclipse.tracecompass.alltests",
- org.eclipse.tracecompass.common.core.tests.collect;x-internal:=true
+ org.eclipse.tracecompass.common.core.tests.collect;x-internal:=true,
+ org.eclipse.tracecompass.common.core.tests.format;x-internal:=true,
+ org.eclipse.tracecompass.common.core.tests.math;x-internal:=true
 Import-Package: com.google.common.base,
  com.google.common.collect,
  com.google.common.primitives
diff --git a/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/math/SaturatedArithmeticTest.java b/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/math/SaturatedArithmeticTest.java
new file mode 100644 (file)
index 0000000..b6db807
--- /dev/null
@@ -0,0 +1,183 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ *
+ * 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.math;
+
+import static org.eclipse.tracecompass.common.core.math.SaturatedArithmetic.add;
+import static org.eclipse.tracecompass.common.core.math.SaturatedArithmetic.multiply;
+import static org.eclipse.tracecompass.common.core.math.SaturatedArithmetic.sameSign;
+import static org.junit.Assert.*;
+
+import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic;
+import org.junit.Test;
+
+/**
+ * Test suite for the {@link SaturatedArithmetic} All tests must test
+ * reciprocity as well as low and high limits
+ *
+ * @author Matthew Khouzam
+ */
+public class SaturatedArithmeticTest {
+
+    /**
+     * test absorbtion
+     */
+    @Test
+    public void testMult0() {
+        assertEquals(0, multiply(0, 0));
+        assertEquals(0, multiply(0, 1));
+        assertEquals(0, multiply(1, 0));
+        assertEquals(0, multiply(42, 0));
+        assertEquals(0, multiply(0, 42));
+        assertEquals(0, multiply(-42, 0));
+        assertEquals(0, multiply(0, -42));
+        assertEquals(0, multiply(Long.MAX_VALUE, 0));
+        assertEquals(0, multiply(0, Long.MAX_VALUE));
+        assertEquals(0, multiply(Long.MIN_VALUE, 0));
+        assertEquals(0, multiply(0, Long.MIN_VALUE));
+    }
+
+    /**
+     * test identity
+     */
+    @Test
+    public void testMult1() {
+        assertEquals(0, multiply(0, 1));
+        assertEquals(1, multiply(1, 1));
+        assertEquals(42, multiply(42, 1));
+        assertEquals(42, multiply(1, 42));
+        assertEquals(-42, multiply(-42, 1));
+        assertEquals(-42, multiply(1, -42));
+        assertEquals(Long.MAX_VALUE, multiply(Long.MAX_VALUE, 1));
+        assertEquals(Long.MAX_VALUE, multiply(1, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, multiply(Long.MIN_VALUE, 1));
+        assertEquals(Long.MIN_VALUE, multiply(1, Long.MIN_VALUE));
+    }
+
+    /**
+     * test typical
+     */
+    @Test
+    public void testMult100() {
+        assertEquals(10000, multiply(100, 100));
+        assertEquals(-10000, multiply(100, -100));
+        assertEquals(-10000, multiply(-100, 100));
+        assertEquals(10000, multiply(-100, -100));
+
+        assertEquals(Long.MAX_VALUE, multiply(Long.MAX_VALUE, 100));
+        assertEquals(Long.MAX_VALUE, multiply(100, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, multiply(Long.MIN_VALUE, 100));
+        assertEquals(Long.MIN_VALUE, multiply(100, Long.MIN_VALUE));
+
+        assertEquals(Long.MIN_VALUE, multiply(Long.MAX_VALUE, -100));
+        assertEquals(Long.MIN_VALUE, multiply(-100, Long.MAX_VALUE));
+        assertEquals(Long.MAX_VALUE, multiply(Long.MIN_VALUE, -100));
+        assertEquals(Long.MAX_VALUE, multiply(-100, Long.MIN_VALUE));
+    }
+
+    /**
+     * test limit
+     */
+    @Test
+    public void testMultLimit() {
+        assertEquals(Long.MAX_VALUE, multiply(Long.MAX_VALUE, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, multiply(Long.MAX_VALUE, Long.MIN_VALUE));
+        assertEquals(Long.MIN_VALUE, multiply(Long.MIN_VALUE, Long.MAX_VALUE));
+        assertEquals(Long.MAX_VALUE, multiply(Long.MIN_VALUE, Long.MIN_VALUE));
+    }
+
+    /**
+     * test identity
+     */
+    @Test
+    public void testAdd0() {
+        assertEquals(0, add(0, 0));
+        assertEquals(1, add(0, 1));
+        assertEquals(1, add(1, 0));
+
+        assertEquals(42, add(42, 0));
+        assertEquals(42, add(0, 42));
+        assertEquals(-42, add(-42, 0));
+        assertEquals(-42, add(0, -42));
+
+        assertEquals(Long.MAX_VALUE, add(Long.MAX_VALUE, 0));
+        assertEquals(Long.MAX_VALUE, add(0, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, add(Long.MIN_VALUE, 0));
+        assertEquals(Long.MIN_VALUE, add(0, Long.MIN_VALUE));
+    }
+
+    /**
+     * test typical
+     */
+    @Test
+    public void testAdd100() {
+        assertEquals(200, add(100, 100));
+        assertEquals(0, add(100, -100));
+        assertEquals(0, add(-100, 100));
+        assertEquals(-200, add(-100, -100));
+
+        assertEquals(Long.MAX_VALUE, add(Long.MAX_VALUE, 100));
+        assertEquals(Long.MAX_VALUE, add(100, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE + 100, add(Long.MIN_VALUE, 100));
+        assertEquals(Long.MIN_VALUE + 100, add(100, Long.MIN_VALUE));
+
+        assertEquals(Long.MAX_VALUE - 100, add(Long.MAX_VALUE, -100));
+        assertEquals(Long.MAX_VALUE - 100, add(-100, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, add(Long.MIN_VALUE, -100));
+        assertEquals(Long.MIN_VALUE, add(-100, Long.MIN_VALUE));
+    }
+
+    /**
+     * test limit
+     */
+    @Test
+    public void testAddLimit() {
+        assertEquals(Long.MAX_VALUE, add(Long.MAX_VALUE, Long.MAX_VALUE));
+        assertEquals(-1, add(Long.MAX_VALUE, Long.MIN_VALUE)); // min value is 1
+                                                               // larger than
+                                                               // max value
+        assertEquals(-1, add(Long.MIN_VALUE, Long.MAX_VALUE));
+        assertEquals(Long.MIN_VALUE, add(Long.MIN_VALUE, Long.MIN_VALUE));
+    }
+
+    /**
+     * test same sign
+     */
+    @Test
+    public void testSameSign() {
+        assertTrue(sameSign(0, 0));
+        assertTrue(sameSign(0, -0));
+
+        assertFalse(sameSign(0, -100));
+        assertFalse(sameSign(-100, 0));
+        assertTrue(sameSign(0, 100));
+        assertTrue(sameSign(100, 0));
+
+        assertFalse(sameSign(-0, -100));
+        assertFalse(sameSign(-100, -0));
+        assertTrue(sameSign(-0, 100));
+        assertTrue(sameSign(100, -0));
+
+        assertTrue(sameSign(100, 100));
+        assertFalse(sameSign(100, -100));
+        assertFalse(sameSign(-100, 100));
+        assertTrue(sameSign(-100, -100));
+
+        assertTrue(sameSign(Long.MAX_VALUE, 100));
+        assertTrue(sameSign(100, Long.MAX_VALUE));
+        assertFalse(sameSign(Long.MIN_VALUE, 100));
+        assertFalse(sameSign(100, Long.MIN_VALUE));
+
+        assertFalse(sameSign(Long.MAX_VALUE, -100));
+        assertFalse(sameSign(-100, Long.MAX_VALUE));
+        assertTrue(sameSign(Long.MIN_VALUE, -100));
+        assertTrue(sameSign(-100, Long.MIN_VALUE));
+    }
+
+}
index 580d813dd2f13805f4a64422110858b9b5042f8f..4136117979c3a91f2270b481a3850e1c060fbb49 100644 (file)
@@ -14,5 +14,6 @@ Export-Package: org.eclipse.tracecompass.common.core,
  org.eclipse.tracecompass.common.core.collect,
  org.eclipse.tracecompass.common.core.format,
  org.eclipse.tracecompass.common.core.log,
+ org.eclipse.tracecompass.common.core.math,
  org.eclipse.tracecompass.internal.common.core;x-internal:=true
 Import-Package: com.google.common.collect
diff --git a/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/math/SaturatedArithmetic.java b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/math/SaturatedArithmetic.java
new file mode 100644 (file)
index 0000000..9226d36
--- /dev/null
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2016 Ericsson
+ *
+ * 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.math;
+
+/**
+ * Saturated arithmetic. These are mathematical helper functions that are used
+ * to clamp numbers to maximum and mimumum and avoid overflows.
+ *
+ * @author Matthew Khouzam
+ * @since 2.1
+ */
+public final class SaturatedArithmetic {
+
+    private SaturatedArithmetic() {
+        // do nothing
+    }
+
+    /**
+     * Saturated multiplication. It will not overflow but instead clamp the
+     * result to {@link Long#MAX_VALUE} and {@link Long#MIN_VALUE}.
+     *
+     * @param left
+     *            The left long to multiply
+     * @param right
+     *            The right long to multiply
+     * @return The saturated multiplication result. The mathematical, not Java
+     *         version of Min(Max(MIN_VALUE, left*right), MAX_VALUE).
+     * @see <a href="http://en.wikipedia.org/wiki/Saturation_arithmetic">
+     *      Saturation arithmetic</a>
+     */
+    public static long multiply(long left, long right) {
+        long retVal = left * right;
+        if ((left != 0) && ((retVal / left) != right)) {
+            return (sameSign(left, right) ? Long.MAX_VALUE : Long.MIN_VALUE);
+        }
+        return retVal;
+    }
+
+    /**
+     * Saturated addition. It will not overflow but instead clamp the result to
+     * {@link Long#MAX_VALUE} and {@link Long#MIN_VALUE}.
+     *
+     * @param left
+     *            The left long to add
+     * @param right
+     *            The right long to add
+     * @return The saturated addition result. The mathematical, not Java version
+     *         of Min(Max(MIN_VALUE, left+right), MAX_VALUE).
+     * @see <a href="http://en.wikipedia.org/wiki/Saturation_arithmetic">
+     *      Saturation arithmetic</a>
+     * @since 2.0
+     */
+    public static final long add(final long left, final long right) {
+        long retVal = left + right;
+        if (sameSign(left, right) && !sameSign(left, retVal)) {
+            if (retVal > 0 || left == Long.MIN_VALUE) {
+                return Long.MIN_VALUE;
+            }
+            return Long.MAX_VALUE;
+        }
+        return retVal;
+    }
+
+    /**
+     * Test if two numbers are the same sign or not
+     *
+     * @param left
+     *            the left long
+     * @param right
+     *            the right long
+     * @return true if both left and right are positive or both negative, false
+     *         otherwise
+     */
+    public static boolean sameSign(final long left, final long right) {
+        return (left ^ right) >= 0;
+    }
+}
index ae05150d3ef80466ab1ba06797268de34d330a2c..8fdb9d1ccc77cc6f71e4bed09cb484e74a9b0898 100644 (file)
@@ -18,6 +18,7 @@ package org.eclipse.tracecompass.tmf.core.timestamp;
 import java.nio.ByteBuffer;
 
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.tracecompass.common.core.math.SaturatedArithmetic;
 import org.eclipse.tracecompass.internal.tmf.core.timestamp.TmfNanoTimestamp;
 import org.eclipse.tracecompass.internal.tmf.core.timestamp.TmfSecondTimestamp;
 
@@ -340,12 +341,12 @@ public abstract class TmfTimestamp implements ITmfTimestamp {
                 if (getScale() < scale) {
                     value /= scalingFactor;
                 } else {
-                    value = saturatedMult(scalingFactor, value);
+                    value = SaturatedArithmetic.multiply(scalingFactor, value);
                 }
             }
         }
 
-        value = saturatedAdd(value, offset);
+        value = SaturatedArithmetic.add(value, offset);
 
         return create(value, scale);
     }
@@ -393,7 +394,7 @@ public abstract class TmfTimestamp implements ITmfTimestamp {
             if (ts.getValue() == Long.MIN_VALUE) {
                 return 1;
             }
-            final long delta = saturatedAdd(getValue(), -ts.getValue());
+            final long delta = SaturatedArithmetic.add(getValue(), -ts.getValue());
             return Long.compare(delta, 0);
         }
         final ITmfTimestamp largerScale = (scale > ts.getScale()) ? this : ts;
@@ -421,27 +422,6 @@ public abstract class TmfTimestamp implements ITmfTimestamp {
         return ts.getValue() == nts.getValue() && ts.getScale() == nts.getScale();
     }
 
-    /**
-     * Saturated multiplication. It will not overflow but instead clamp the
-     * result to {@link Long#MAX_VALUE} and {@link Long#MIN_VALUE}.
-     *
-     * @param left
-     *            The left long to multiply
-     * @param right
-     *            The right long to multiply
-     * @return The saturated multiplication result. The mathematical, not Java
-     *         version of Min(Max(MIN_VALUE, left*right), MAX_VALUE).
-     * @see <a href="http://en.wikipedia.org/wiki/Saturation_arithmetic">
-     *      Saturation arithmetic</a>
-     */
-    private static long saturatedMult(long left, long right) {
-        long retVal = left * right;
-        if ((left != 0) && ((retVal / left) != right)) {
-            return (sameSign(left, right) ? Long.MAX_VALUE : Long.MIN_VALUE);
-        }
-        return retVal;
-    }
-
     /**
      * Saturated addition. It will not overflow but instead clamp the result to
      * {@link Long#MAX_VALUE} and {@link Long#MIN_VALUE}.
@@ -455,21 +435,13 @@ public abstract class TmfTimestamp implements ITmfTimestamp {
      * @see <a href="http://en.wikipedia.org/wiki/Saturation_arithmetic">
      *      Saturation arithmetic</a>
      * @since 2.0
+     * @deprecated use {@link SaturatedArithmetic#add(long, long)} instead
      */
+    @Deprecated
     protected static final long saturatedAdd(final long left, final long right) {
-        long retVal = left + right;
-        if (sameSign(left, right) && !sameSign(left, retVal)) {
-            if (retVal > 0) {
-                return Long.MIN_VALUE;
-            }
-            return Long.MAX_VALUE;
-        }
-        return retVal;
+        return SaturatedArithmetic.add(left, right);
     }
 
-    private static boolean sameSign(final long left, final long right) {
-        return (left ^ right) >= 0;
-    }
 
     // ------------------------------------------------------------------------
     // Object
This page took 0.038007 seconds and 5 git commands to generate.