Add Decimal64.{int,long,short}ValueExact() 58/66758/1
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 25 Dec 2017 11:58:44 +0000 (12:58 +0100)
committerRobert Varga <robert.varga@pantheon.tech>
Mon, 25 Dec 2017 16:02:00 +0000 (17:02 +0100)
Add alternative methods which prevent loss of precision by throwing
ArithmeticException. Testing has revealed that valueOf(Integer.MIN_VALUE)
does not work correctly due to the fact negation does results in incorrect
bitmask -- which is fixed by widening conversion.

Change-Id: I206ced685d2edb753507f5d45e29bb6b405ff421
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java
yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java

index 5b7bc508be3279ed29b10e2d8c5b097ca248e959..3ef8731e2eda48e82c40a71bcde569fbc3acb39a 100644 (file)
@@ -75,7 +75,7 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
     }
 
     public static Decimal64 valueOf(final int intVal) {
-        return intVal < 0 ? new Decimal64(1, -intVal, 0, true) : new Decimal64(1, intVal, 0, false);
+        return intVal < 0 ? new Decimal64(1, - (long)intVal, 0, true) : new Decimal64(1, intVal, 0, false);
     }
 
     public static Decimal64 valueOf(final long longVal) {
@@ -218,6 +218,71 @@ public final class Decimal64 extends Number implements Comparable<Decimal64>, Im
         return 1.0 * value / SCALE[scaleOffset];
     }
 
+    /**
+     * Converts this {@code BigDecimal} to a {@code byte}, checking for lost information. If this {@code Decimal64} has
+     * a nonzero fractional part or is out of the possible range for a {@code byte} result then
+     * an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to a {@code byte}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in a {@code byte}.
+     */
+    public byte byteValueExact() {
+        final long val = longValueExact();
+        final byte ret = (byte) val;
+        if (val != ret) {
+            throw new ArithmeticException("Value " + val + " is outside of byte range");
+        }
+        return ret;
+    }
+
+    /**
+     * Converts this {@code BigDecimal} to a {@code short}, checking for lost information. If this {@code Decimal64} has
+     * a nonzero fractional part or is out of the possible range for a {@code short} result then
+     * an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to a {@code short}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in a {@code short}.
+     */
+    public short shortValueExact() {
+        final long val = longValueExact();
+        final short ret = (short) val;
+        if (val != ret) {
+            throw new ArithmeticException("Value " + val + " is outside of short range");
+        }
+        return ret;
+    }
+
+    /**
+     * Converts this {@code BigDecimal} to an {@code int}, checking for lost information. If this {@code Decimal64} has
+     * a nonzero fractional part or is out of the possible range for an {@code int} result then
+     * an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to an {@code int}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part, or will not fit in an {@code int}.
+     */
+    public int intValueExact() {
+        final long val = longValueExact();
+        final int ret = (int) val;
+        if (val != ret) {
+            throw new ArithmeticException("Value " + val + " is outside of integer range");
+        }
+        return ret;
+    }
+
+    /**
+     * Converts this {@code BigDecimal} to a {@code long}, checking for lost information.  If this {@code Decimal64} has
+     * a nonzero fractional part then an {@code ArithmeticException} is thrown.
+     *
+     * @return this {@code Decimal64} converted to a {@code long}.
+     * @throws ArithmeticException if {@code this} has a nonzero fractional part.
+     */
+    public long longValueExact() {
+        if (fracPart() != 0) {
+            throw new ArithmeticException("Conversion of " + this + " would lose fraction");
+        }
+        return intPart();
+    }
+
     @Override
     @SuppressWarnings("checkstyle:parameterName")
     public int compareTo(final Decimal64 o) {
index ff610a2f9e1c97787b7167bab917de7b781e78d6..c6a52b22c3254e52bd66b95eac196c47b3b36d8c 100644 (file)
@@ -213,6 +213,69 @@ public class Decimal64Test {
         assertEquals("-1.0", Decimal64.valueOf(BigDecimal.ONE.negate()).toString());
     }
 
+    @Test
+    public void testBoundaries() {
+        assertEquals(-128L, Decimal64.valueOf(Byte.MIN_VALUE).longValue());
+        assertEquals(127L, Decimal64.valueOf(Byte.MAX_VALUE).longValue());
+        assertEquals(-32768L, Decimal64.valueOf(Short.MIN_VALUE).longValue());
+        assertEquals(32767L, Decimal64.valueOf(Short.MAX_VALUE).longValue());
+        assertEquals(-2147483648L, Decimal64.valueOf(Integer.MIN_VALUE).longValue());
+        assertEquals(2147483647L, Decimal64.valueOf(Integer.MAX_VALUE).longValue());
+    }
+
+    @Test
+    public void testByteValueExact() {
+        assertEquals(Byte.MIN_VALUE, Decimal64.valueOf(Byte.MIN_VALUE).byteValueExact());
+        assertEquals(Byte.MAX_VALUE, Decimal64.valueOf(Byte.MAX_VALUE).byteValueExact());
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testByteValueExactFrac() {
+        Decimal64.valueOf("1.1").byteValueExact();
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testByteValueExactRange() {
+        Decimal64.valueOf(Byte.MAX_VALUE + 1).byteValueExact();
+    }
+
+    @Test
+    public void testShortValueExact() {
+        assertEquals(Short.MIN_VALUE, Decimal64.valueOf(Short.MIN_VALUE).shortValueExact());
+        assertEquals(Short.MAX_VALUE, Decimal64.valueOf(Short.MAX_VALUE).shortValueExact());
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testShortValueExactFrac() {
+        Decimal64.valueOf("1.1").shortValueExact();
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testShortValueExactRange() {
+        Decimal64.valueOf(Short.MAX_VALUE + 1).shortValueExact();
+    }
+
+    @Test
+    public void testIntValueExact() {
+        assertEquals(Integer.MIN_VALUE, Decimal64.valueOf(Integer.MIN_VALUE).intValueExact());
+        assertEquals(Integer.MAX_VALUE, Decimal64.valueOf(Integer.MAX_VALUE).intValueExact());
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testIntValueExactFrac() {
+        Decimal64.valueOf("1.1").intValueExact();
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testIntValueExactRange() {
+        Decimal64.valueOf(Integer.MAX_VALUE + 1L).intValueExact();
+    }
+
+    @Test(expected = ArithmeticException.class)
+    public void testLongValueExactFrac() {
+        Decimal64.valueOf("1.1").longValueExact();
+    }
+
     private static void assertCanonicalVariants(final String str, final long intPart, final long fracPart,
             final int digits) {
         assertCanonicalString(str, intPart, fracPart, digits, false);