From 6263890e48e422156cf083d2120ccd180d51dcde Mon Sep 17 00:00:00 2001 From: Robert Varga Date: Mon, 25 Dec 2017 12:58:44 +0100 Subject: [PATCH] Add Decimal64.{int,long,short}ValueExact() 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 --- .../yangtools/yang/common/Decimal64.java | 67 ++++++++++++++++++- .../yangtools/yang/common/Decimal64Test.java | 63 +++++++++++++++++ 2 files changed, 129 insertions(+), 1 deletion(-) diff --git a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java index 5b7bc508be..3ef8731e2e 100644 --- a/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java +++ b/yang/yang-common/src/main/java/org/opendaylight/yangtools/yang/common/Decimal64.java @@ -75,7 +75,7 @@ public final class Decimal64 extends Number implements Comparable, 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, 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) { diff --git a/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java b/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java index ff610a2f9e..c6a52b22c3 100644 --- a/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java +++ b/yang/yang-common/src/test/java/org/opendaylight/yangtools/yang/common/Decimal64Test.java @@ -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); -- 2.36.6