From 903a961f9cab8c159ac1ff8ba787428674e098b2 Mon Sep 17 00:00:00 2001 From: "Shuy, Daniel" Date: Wed, 17 Feb 2021 04:23:57 +0800 Subject: [PATCH 1/5] Add tests --- .../function/DoubleMultiplyConverterTest.java | 7 +++++ .../indriya/function/UnitConverterTest.java | 26 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/test/java/tech/units/indriya/function/DoubleMultiplyConverterTest.java b/src/test/java/tech/units/indriya/function/DoubleMultiplyConverterTest.java index 0600a407..7377504c 100644 --- a/src/test/java/tech/units/indriya/function/DoubleMultiplyConverterTest.java +++ b/src/test/java/tech/units/indriya/function/DoubleMultiplyConverterTest.java @@ -52,6 +52,13 @@ public void testConvertMethod() { assertEquals(-200, converter.convert(-100), 0.1); } + @Test + public void testConvertMethodFloat() { + assertEquals(200, converter.convert(100f), 0.1); + assertEquals(0, converter.convert(0f)); + assertEquals(-200, converter.convert(-100f), 0.1); + } + @Test public void testEqualityOfTwoLogConverter() { assertNotNull(converter); diff --git a/src/test/java/tech/units/indriya/function/UnitConverterTest.java b/src/test/java/tech/units/indriya/function/UnitConverterTest.java index 9032e6f8..17ef919f 100644 --- a/src/test/java/tech/units/indriya/function/UnitConverterTest.java +++ b/src/test/java/tech/units/indriya/function/UnitConverterTest.java @@ -78,6 +78,24 @@ public void testQuantity() { assertEquals(targetUnit, quantResult1.getUnit()); } + @Test + public void testQuantityDouble() { + Quantity quantLength1 = Quantities.getQuantity(1.56, sourceUnit); + Quantity quantResult1 = quantLength1.to(targetUnit); + assertNotNull(quantResult1); + assertNumberEquals(156, quantResult1.getValue(), 1E-12); + assertEquals(targetUnit, quantResult1.getUnit()); + } + + @Test + public void testQuantityFloat() { + Quantity quantLength1 = Quantities.getQuantity(1.56f, sourceUnit); + Quantity quantResult1 = quantLength1.to(targetUnit); + assertNotNull(quantResult1); + assertNumberEquals(156, quantResult1.getValue(), 1E-12); + assertEquals(targetUnit, quantResult1.getUnit()); + } + @Test public void testKelvinToCelsius() { Quantity sut = Quantities.getQuantity(273.15d, KELVIN).to(CELSIUS); @@ -86,6 +104,14 @@ public void testKelvinToCelsius() { assertNumberEquals(0, sut.getValue(), 1E-12); } + @Test + public void testKelvinToCelsiusFloat() { + Quantity sut = Quantities.getQuantity(273.15f, KELVIN).to(CELSIUS); + assertNotNull(sut); + assertEquals(CELSIUS, sut.getUnit()); + assertNumberEquals(0, sut.getValue(), 1E-12); + } + @Test public void testConverterTo() { assertEquals(KILOGRAM.getConverterTo(KILO(GRAM)), KILO(GRAM).getConverterTo(KILOGRAM)); From a05c13bf960cc0573429cdddbab25903992d0a30 Mon Sep 17 00:00:00 2001 From: "Shuy, Daniel" Date: Wed, 17 Feb 2021 01:14:43 +0800 Subject: [PATCH 2/5] 329: Retain floating point precision when converting floats Float#doubleValue() does not preserve floating point precision --- .../indriya/function/DefaultNumberSystem.java | 83 +++++++++++++------ .../indriya/function/RationalNumber.java | 12 +++ 2 files changed, 68 insertions(+), 27 deletions(-) diff --git a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java index af9b2cd1..509d4744 100644 --- a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java +++ b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java @@ -250,7 +250,7 @@ public Number reciprocal(Number number) { return RationalNumber.of((double)number).reciprocal(); } if(number instanceof Float) { - return RationalNumber.of(number.doubleValue()).reciprocal(); + return RationalNumber.of((float)number).reciprocal(); } throw unsupportedNumberType(number); } @@ -614,9 +614,12 @@ private BigDecimal toBigDecimal(Number number) { number instanceof Byte) { return BigDecimal.valueOf(number.longValue()); } - if(number instanceof Double || number instanceof Float) { + if(number instanceof Double) { return BigDecimal.valueOf(number.doubleValue()); } + if(number instanceof Float) { + return new BigDecimal(number.toString()); + } if(number instanceof RationalNumber) { throw unexpectedCodeReach(); //Note: don't do that (potential precision loss) @@ -668,7 +671,10 @@ private Number addWideAndNarrow( } if(narrow instanceof Double || narrow instanceof Float) { - return ((BigDecimal) wide).add(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT); + BigDecimal addend = narrow instanceof Double + ? BigDecimal.valueOf(narrow.doubleValue()) + : new BigDecimal(narrow.toString()); + return ((BigDecimal) wide).add(addend, Calculus.MATH_CONTEXT); } if(narrow instanceof RationalNumber) { @@ -682,27 +688,29 @@ private Number addWideAndNarrow( } // at this point we know, that wide is one of {Double, Float} + BigDecimal augend = wide instanceof Double + ? BigDecimal.valueOf(wide.doubleValue()) + : new BigDecimal(wide.toString()); if(narrow instanceof Double || narrow instanceof Float) { //converting to BigDecimal, because especially fractional addition is sensitive to precision loss - return BigDecimal.valueOf(wide.doubleValue()) - .add(BigDecimal.valueOf(narrow.doubleValue())); + BigDecimal addend = narrow instanceof Double + ? BigDecimal.valueOf(narrow.doubleValue()) + : new BigDecimal(narrow.toString()); + return augend.add(addend); } if(narrow instanceof RationalNumber) { //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber - return BigDecimal.valueOf(wide.doubleValue()) - .add(((RationalNumber) narrow).bigDecimalValue()); + return augend.add(((RationalNumber) narrow).bigDecimalValue()); } if(narrow instanceof BigInteger) { - return BigDecimal.valueOf(wide.doubleValue()) - .add(new BigDecimal((BigInteger) narrow)); + return augend.add(new BigDecimal((BigInteger) narrow)); } // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} - return BigDecimal.valueOf(wide.doubleValue()) - .add(BigDecimal.valueOf(narrow.longValue())); + return augend.add(BigDecimal.valueOf(narrow.longValue())); } @@ -748,7 +756,10 @@ private Number multiplyWideAndNarrow( } if(narrow instanceof Double || narrow instanceof Float) { - return ((BigDecimal) wide).multiply(BigDecimal.valueOf(narrow.doubleValue()), Calculus.MATH_CONTEXT); + BigDecimal multiplier = narrow instanceof Double + ? BigDecimal.valueOf(narrow.doubleValue()) + : new BigDecimal(narrow.toString()); + return ((BigDecimal) wide).multiply(multiplier, Calculus.MATH_CONTEXT); } if(narrow instanceof RationalNumber) { @@ -763,25 +774,37 @@ private Number multiplyWideAndNarrow( // at this point we know, that wide is one of {Double, Float} - if(narrow instanceof Double || narrow instanceof Float) { + if(narrow instanceof Double) { // not converting to BigDecimal, because fractional multiplication is not sensitive to precision loss + if(wide instanceof Double) { return wide.doubleValue() * narrow.doubleValue(); + } else { + return wide.floatValue() * narrow.doubleValue(); + } } + if(narrow instanceof Float) { + if(wide instanceof Double) { + return wide.doubleValue() * narrow.floatValue(); + } else { + return wide.floatValue() * narrow.floatValue(); + } + } + + BigDecimal multiplicand = wide instanceof Double + ? BigDecimal.valueOf(wide.doubleValue()) + : new BigDecimal(wide.toString()); if(narrow instanceof RationalNumber) { //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber - return BigDecimal.valueOf(wide.doubleValue()) - .multiply(((RationalNumber) narrow).bigDecimalValue()); + return multiplicand.multiply(((RationalNumber) narrow).bigDecimalValue()); } if(narrow instanceof BigInteger) { - return BigDecimal.valueOf(wide.doubleValue()) - .multiply(new BigDecimal((BigInteger) narrow)); + return multiplicand.multiply(new BigDecimal((BigInteger) narrow)); } // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} - return BigDecimal.valueOf(wide.doubleValue()) - .multiply(BigDecimal.valueOf(narrow.longValue())); + return multiplicand.multiply(BigDecimal.valueOf(narrow.longValue())); } @@ -821,7 +844,10 @@ private int compareWideVsNarrow( } if(narrow instanceof Double || narrow instanceof Float) { - return ((BigDecimal) wide).compareTo(BigDecimal.valueOf(narrow.doubleValue())); + BigDecimal comparand = narrow instanceof Double + ? BigDecimal.valueOf(narrow.doubleValue()) + : new BigDecimal(narrow.toString()); + return ((BigDecimal) wide).compareTo(comparand); } if(narrow instanceof RationalNumber) { @@ -835,25 +861,28 @@ private int compareWideVsNarrow( } // at this point we know, that wide is one of {Double, Float} + BigDecimal comparable = wide instanceof Double + ? BigDecimal.valueOf(wide.doubleValue()) + : new BigDecimal(wide.toString()); if(narrow instanceof Double || narrow instanceof Float) { - return Double.compare(wide.doubleValue(), narrow.doubleValue()); + BigDecimal comparand = narrow instanceof Double + ? BigDecimal.valueOf(narrow.doubleValue()) + : new BigDecimal(narrow.toString()); + return comparable.compareTo(comparand); } if(narrow instanceof RationalNumber) { //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber - return BigDecimal.valueOf(wide.doubleValue()) - .compareTo(((RationalNumber) narrow).bigDecimalValue()); + return comparable.compareTo(((RationalNumber) narrow).bigDecimalValue()); } if(narrow instanceof BigInteger) { - return BigDecimal.valueOf(wide.doubleValue()) - .compareTo(new BigDecimal((BigInteger) narrow)); + return comparable.compareTo(new BigDecimal((BigInteger) narrow)); } // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} - return BigDecimal.valueOf(wide.doubleValue()) - .compareTo(BigDecimal.valueOf(narrow.longValue())); + return comparable.compareTo(BigDecimal.valueOf(narrow.longValue())); } diff --git a/src/main/java/tech/units/indriya/function/RationalNumber.java b/src/main/java/tech/units/indriya/function/RationalNumber.java index d39092ca..5e313e72 100644 --- a/src/main/java/tech/units/indriya/function/RationalNumber.java +++ b/src/main/java/tech/units/indriya/function/RationalNumber.java @@ -123,6 +123,18 @@ public static RationalNumber of(double number) { return of(decimalValue); } + /** + * Returns a {@code RationalNumber} that represents the given float + * {@code number}, with an accuracy equivalent to {@code new BigDecimal(Float.toString(number))}. + * + * @param number + */ + public static RationalNumber of(float number) { + // smh why does Java not have BigDecimal.valueOf(float) ?! + final BigDecimal decimalValue = new BigDecimal(Float.toString(number)); + return of(decimalValue); + } + /** * Returns a {@code RationalNumber} that represents the given BigDecimal decimalValue. * From faa269f8cdc76d59c5a7f78d1a9299935d2d2f2a Mon Sep 17 00:00:00 2001 From: "Shuy, Daniel" Date: Wed, 17 Feb 2021 02:42:13 +0800 Subject: [PATCH 3/5] Use Number#doubleValue()/floatValue() instead of cast --- .../java/tech/units/indriya/function/DefaultNumberSystem.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java index 509d4744..7ee54a6a 100644 --- a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java +++ b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java @@ -247,10 +247,10 @@ public Number reciprocal(Number number) { return ((RationalNumber) number).reciprocal(); } if(number instanceof Double) { - return RationalNumber.of((double)number).reciprocal(); + return RationalNumber.of(number.doubleValue()).reciprocal(); } if(number instanceof Float) { - return RationalNumber.of((float)number).reciprocal(); + return RationalNumber.of(number.floatValue()).reciprocal(); } throw unsupportedNumberType(number); } From c0a5e9a43af2dcc32da92e532548737e6942b3f7 Mon Sep 17 00:00:00 2001 From: "Shuy, Daniel" Date: Wed, 17 Feb 2021 02:45:15 +0800 Subject: [PATCH 4/5] Remove unnecessary instanceof Double check --- .../indriya/function/DefaultNumberSystem.java | 69 +++++++------------ 1 file changed, 26 insertions(+), 43 deletions(-) diff --git a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java index 7ee54a6a..05d55acd 100644 --- a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java +++ b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java @@ -614,10 +614,7 @@ private BigDecimal toBigDecimal(Number number) { number instanceof Byte) { return BigDecimal.valueOf(number.longValue()); } - if(number instanceof Double) { - return BigDecimal.valueOf(number.doubleValue()); - } - if(number instanceof Float) { + if(number instanceof Double || number instanceof Float) { return new BigDecimal(number.toString()); } if(number instanceof RationalNumber) { @@ -671,10 +668,7 @@ private Number addWideAndNarrow( } if(narrow instanceof Double || narrow instanceof Float) { - BigDecimal addend = narrow instanceof Double - ? BigDecimal.valueOf(narrow.doubleValue()) - : new BigDecimal(narrow.toString()); - return ((BigDecimal) wide).add(addend, Calculus.MATH_CONTEXT); + return ((BigDecimal) wide).add(new BigDecimal(narrow.toString()), Calculus.MATH_CONTEXT); } if(narrow instanceof RationalNumber) { @@ -688,29 +682,27 @@ private Number addWideAndNarrow( } // at this point we know, that wide is one of {Double, Float} - BigDecimal augend = wide instanceof Double - ? BigDecimal.valueOf(wide.doubleValue()) - : new BigDecimal(wide.toString()); if(narrow instanceof Double || narrow instanceof Float) { //converting to BigDecimal, because especially fractional addition is sensitive to precision loss - BigDecimal addend = narrow instanceof Double - ? BigDecimal.valueOf(narrow.doubleValue()) - : new BigDecimal(narrow.toString()); - return augend.add(addend); + return new BigDecimal(wide.toString()) + .add(new BigDecimal(narrow.toString())); } if(narrow instanceof RationalNumber) { //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber - return augend.add(((RationalNumber) narrow).bigDecimalValue()); + return new BigDecimal(wide.toString()) + .add(((RationalNumber) narrow).bigDecimalValue()); } if(narrow instanceof BigInteger) { - return augend.add(new BigDecimal((BigInteger) narrow)); + return new BigDecimal(wide.toString()) + .add(new BigDecimal((BigInteger) narrow)); } // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} - return augend.add(BigDecimal.valueOf(narrow.longValue())); + return new BigDecimal(wide.toString()) + .add(BigDecimal.valueOf(narrow.longValue())); } @@ -756,10 +748,7 @@ private Number multiplyWideAndNarrow( } if(narrow instanceof Double || narrow instanceof Float) { - BigDecimal multiplier = narrow instanceof Double - ? BigDecimal.valueOf(narrow.doubleValue()) - : new BigDecimal(narrow.toString()); - return ((BigDecimal) wide).multiply(multiplier, Calculus.MATH_CONTEXT); + return ((BigDecimal) wide).multiply(new BigDecimal(narrow.toString()), Calculus.MATH_CONTEXT); } if(narrow instanceof RationalNumber) { @@ -790,21 +779,20 @@ private Number multiplyWideAndNarrow( } } - BigDecimal multiplicand = wide instanceof Double - ? BigDecimal.valueOf(wide.doubleValue()) - : new BigDecimal(wide.toString()); - if(narrow instanceof RationalNumber) { //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber - return multiplicand.multiply(((RationalNumber) narrow).bigDecimalValue()); + return new BigDecimal(wide.toString()) + .multiply(((RationalNumber) narrow).bigDecimalValue()); } if(narrow instanceof BigInteger) { - return multiplicand.multiply(new BigDecimal((BigInteger) narrow)); + return new BigDecimal(wide.toString()) + .multiply(new BigDecimal((BigInteger) narrow)); } // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} - return multiplicand.multiply(BigDecimal.valueOf(narrow.longValue())); + return new BigDecimal(wide.toString()) + .multiply(BigDecimal.valueOf(narrow.longValue())); } @@ -844,10 +832,7 @@ private int compareWideVsNarrow( } if(narrow instanceof Double || narrow instanceof Float) { - BigDecimal comparand = narrow instanceof Double - ? BigDecimal.valueOf(narrow.doubleValue()) - : new BigDecimal(narrow.toString()); - return ((BigDecimal) wide).compareTo(comparand); + return ((BigDecimal) wide).compareTo(new BigDecimal(narrow.toString())); } if(narrow instanceof RationalNumber) { @@ -861,28 +846,26 @@ private int compareWideVsNarrow( } // at this point we know, that wide is one of {Double, Float} - BigDecimal comparable = wide instanceof Double - ? BigDecimal.valueOf(wide.doubleValue()) - : new BigDecimal(wide.toString()); if(narrow instanceof Double || narrow instanceof Float) { - BigDecimal comparand = narrow instanceof Double - ? BigDecimal.valueOf(narrow.doubleValue()) - : new BigDecimal(narrow.toString()); - return comparable.compareTo(comparand); + return new BigDecimal(wide.toString()) + .compareTo(new BigDecimal(narrow.toString())); } if(narrow instanceof RationalNumber) { //TODO[220] can we do better than that, eg. by converting BigDecimal to RationalNumber - return comparable.compareTo(((RationalNumber) narrow).bigDecimalValue()); + return new BigDecimal(wide.toString()) + .compareTo(((RationalNumber) narrow).bigDecimalValue()); } if(narrow instanceof BigInteger) { - return comparable.compareTo(new BigDecimal((BigInteger) narrow)); + return new BigDecimal(wide.toString()) + .compareTo(new BigDecimal((BigInteger) narrow)); } // at this point we know, that 'narrow' is one of {(Atomic)Long, (Atomic)Integer, Short, Byte} - return comparable.compareTo(BigDecimal.valueOf(narrow.longValue())); + return new BigDecimal(wide.toString()) + .compareTo(BigDecimal.valueOf(narrow.longValue())); } From d05aff2107634031e9e8ddee37a4fc10439de2c7 Mon Sep 17 00:00:00 2001 From: "Shuy, Daniel" Date: Wed, 17 Feb 2021 13:27:37 +0800 Subject: [PATCH 5/5] Simplify Double + Float multiplication --- .../indriya/function/DefaultNumberSystem.java | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java index 05d55acd..c670043a 100644 --- a/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java +++ b/src/main/java/tech/units/indriya/function/DefaultNumberSystem.java @@ -763,19 +763,13 @@ private Number multiplyWideAndNarrow( // at this point we know, that wide is one of {Double, Float} - if(narrow instanceof Double) { + if(narrow instanceof Double || narrow instanceof Float) { // not converting to BigDecimal, because fractional multiplication is not sensitive to precision loss - if(wide instanceof Double) { - return wide.doubleValue() * narrow.doubleValue(); - } else { - return wide.floatValue() * narrow.doubleValue(); - } - } - if(narrow instanceof Float) { - if(wide instanceof Double) { - return wide.doubleValue() * narrow.floatValue(); - } else { + if (wide instanceof Float && narrow instanceof Float) { + // return float if both are floats return wide.floatValue() * narrow.floatValue(); + } else { + return wide.doubleValue() * narrow.doubleValue(); } }