From: Matthew Khouzam Date: Thu, 28 Jul 2016 21:56:31 +0000 (-0400) Subject: common.core: move saturated math to common core X-Git-Url: http://git.efficios.com/?a=commitdiff_plain;h=16dd744fcb757a0b29af064a4f7fdbd21eb3a113;p=deliverable%2Ftracecompass.git common.core: move saturated math to common core 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 Reviewed-on: https://git.eclipse.org/r/78107 Reviewed-by: Hudson CI Reviewed-by: Patrick Tasse Tested-by: Patrick Tasse --- diff --git a/common/org.eclipse.tracecompass.common.core.tests/META-INF/MANIFEST.MF b/common/org.eclipse.tracecompass.common.core.tests/META-INF/MANIFEST.MF index f957abe398..c05dd2bf48 100644 --- a/common/org.eclipse.tracecompass.common.core.tests/META-INF/MANIFEST.MF +++ b/common/org.eclipse.tracecompass.common.core.tests/META-INF/MANIFEST.MF @@ -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 index 0000000000..b6db807b3c --- /dev/null +++ b/common/org.eclipse.tracecompass.common.core.tests/src/org/eclipse/tracecompass/common/core/tests/math/SaturatedArithmeticTest.java @@ -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)); + } + +} diff --git a/common/org.eclipse.tracecompass.common.core/META-INF/MANIFEST.MF b/common/org.eclipse.tracecompass.common.core/META-INF/MANIFEST.MF index 580d813dd2..4136117979 100644 --- a/common/org.eclipse.tracecompass.common.core/META-INF/MANIFEST.MF +++ b/common/org.eclipse.tracecompass.common.core/META-INF/MANIFEST.MF @@ -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 index 0000000000..9226d36b13 --- /dev/null +++ b/common/org.eclipse.tracecompass.common.core/src/org/eclipse/tracecompass/common/core/math/SaturatedArithmetic.java @@ -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 + * Saturation arithmetic + */ + 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 + * Saturation arithmetic + * @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; + } +} diff --git a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/timestamp/TmfTimestamp.java b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/timestamp/TmfTimestamp.java index ae05150d3e..8fdb9d1ccc 100644 --- a/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/timestamp/TmfTimestamp.java +++ b/tmf/org.eclipse.tracecompass.tmf.core/src/org/eclipse/tracecompass/tmf/core/timestamp/TmfTimestamp.java @@ -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 - * Saturation arithmetic - */ - 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 * Saturation arithmetic * @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