to(UnitanotherUnit) { if (anotherUnit.equals(this.getUnit())) { return this; } - if (Scale.RELATIVE.equals(this.getScale())) { - final UnitsystemUnit = this.getUnit().getSystemUnit(); - final ToSystemUnitConverter converter = ToSystemUnitConverter.forQuantity(this, systemUnit); - final Number valueInSystemUnit = converter.apply(getValue()); - final Number valueInOtherUnit = systemUnit.getConverterTo(anotherUnit).convert(valueInSystemUnit); - return Quantities.getQuantity(valueInOtherUnit, anotherUnit); - } - UnitConverter t = getUnit().getConverterTo(anotherUnit); - Number convertedValue = t.convert(getValue()); - return Quantities.getQuantity(convertedValue, anotherUnit); + return ScaleHelper.convertTo(this, anotherUnit); } @Override @@ -311,15 +298,17 @@ public String toString() { } @Override - public, E extends Quantity > ComparableQuantity divide(Quantity that, Class asTypeQuantity) { - - return divide(Objects.requireNonNull(that)).asType(Objects.requireNonNull(asTypeQuantity)); - + public , E extends Quantity > ComparableQuantity + divide(Quantity that, Class asTypeQuantity) { + return divide(Objects.requireNonNull(that)) + .asType(Objects.requireNonNull(asTypeQuantity)); } @Override - public , E extends Quantity > ComparableQuantity multiply(Quantity that, Class asTypeQuantity) { - return multiply(Objects.requireNonNull(that)).asType(Objects.requireNonNull(asTypeQuantity)); + public , E extends Quantity > ComparableQuantity + multiply(Quantity that, Class asTypeQuantity) { + return multiply(Objects.requireNonNull(that)) + .asType(Objects.requireNonNull(asTypeQuantity)); } @Override @@ -343,9 +332,9 @@ public > ComparableQuantity inverse(Class quantityCl * if the specified quantity class does not have a public static field named "UNIT" holding the SI unit for the quantity. * @see Unit#asType(Class) */ - public final > ComparableQuantity asType(Class type) throws ClassCastException { - this.getUnit().asType(type); // Raises ClassCastException if dimension - // mismatches. + public final > ComparableQuantity + asType(Class type) throws ClassCastException { + this.getUnit().asType(type); // ClassCastException if dimension mismatches. return (ComparableQuantity ) this; } @@ -353,7 +342,7 @@ public final > ComparableQuantity asType(Class type) * Returns the quantity of unknown type corresponding to the specified representation. This method can be used to parse dimensionless * quantities.
*- * Quatity
* *proportion = AbstractQuantity.parse("0.234").asType(Dimensionless.class); + * Quantity proportion = AbstractQuantity.parse("0.234").asType(Dimensionless.class); * @@ -380,78 +369,5 @@ protected boolean hasFraction(BigDecimal value) { protected NumberSystem numberSystem() { return Calculus.currentNumberSystem(); } - - // also honors RELATIVE scale - protected static class ToSystemUnitConverter implements UnaryOperator
{ - private final UnaryOperator unaryOperator; - private final UnaryOperator inverseOperator; - - public static > - ToSystemUnitConverter forQuantity(Quantityquantity, UnitsystemUnit) { - if(quantity.getUnit().equals(systemUnit)) { - return ToSystemUnitConverter.noop(); // no conversion required - } - - final UnitConverter converter = quantity.getUnit().getConverterTo(systemUnit); - - if(ABSOLUTE.equals(quantity.getScale())) { - - return ToSystemUnitConverter.of(converter::convert); // convert to system units - - } else { - final Number linearFactor = linearFactorOf(converter).orElse(null); - if(linearFactor!=null) { - // conversion by factor required ... Δ2°C -> Δ2K , Δ2°F -> 5/9 * Δ2K - return ToSystemUnitConverter.factor(linearFactor); - } - // convert any other cases of RELATIVE scale to system unit (ABSOLUTE) ... - throw unsupportedConverter(converter, quantity.getUnit()); - } - } - - public Number invert(Number x) { - return isNoop() - ? x - : inverseOperator.apply(x); - } - - public static ToSystemUnitConverter of(UnaryOperatorunaryOperator) { - return new ToSystemUnitConverter(unaryOperator, null); - } - public static ToSystemUnitConverter noop() { - return new ToSystemUnitConverter(null, null); - } - public static ToSystemUnitConverter factor(Number factor) { - return new ToSystemUnitConverter( - number->Calculator.of(number).multiply(factor).peek(), - number->Calculator.of(number).divide(factor).peek()); - } - private ToSystemUnitConverter(UnaryOperator unaryOperator, UnaryOperator inverseOperator) { - this.unaryOperator = unaryOperator; - this.inverseOperator = inverseOperator; - } - public boolean isNoop() { - return unaryOperator==null; - } - @Override - public Number apply(Number x) { - return isNoop() - ? x - : unaryOperator.apply(x); - } - - private static Optional linearFactorOf(UnitConverter converter) { - return (converter instanceof AbstractConverter) - ? ((AbstractConverter)converter).linearFactor() - : Optional.empty(); - } - - private static UnsupportedOperationException unsupportedConverter(UnitConverter converter, Unit> unit) { - return new UnsupportedOperationException( - String.format( - "Scale conversion from RELATIVE to ABSOLUTE for Unit %s having Converter %s is not implemented.", - unit, converter)); - } - - } + } diff --git a/src/main/java/tech/units/indriya/internal/function/ScaleHelper.java b/src/main/java/tech/units/indriya/internal/function/ScaleHelper.java new file mode 100644 index 00000000..b4023b1d --- /dev/null +++ b/src/main/java/tech/units/indriya/internal/function/ScaleHelper.java @@ -0,0 +1,273 @@ +/* + * Units of Measurement Reference Implementation + * Copyright (c) 2005-2020, Jean-Marie Dautelle, Werner Keil, Otavio Santana. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package tech.units.indriya.internal.function; + +import java.util.Optional; +import java.util.function.BinaryOperator; +import java.util.function.UnaryOperator; + +import javax.measure.Quantity; +import javax.measure.Quantity.Scale; +import javax.measure.Unit; +import javax.measure.UnitConverter; + +import static javax.measure.Quantity.Scale.ABSOLUTE; +import static javax.measure.Quantity.Scale.RELATIVE; + +import tech.units.indriya.ComparableQuantity; +import tech.units.indriya.function.AbstractConverter; +import tech.units.indriya.quantity.Quantities; + +/** + * Encapsulates scale-honoring quantity arithmetics. + * + * @author Andi Huber + */ +public final class ScaleHelper { + + public static enum OperandMode { + ALL_ABSOLUTE, + ALL_RELATIVE, + MIXED; + public static OperandMode get( + final Quantity> q1, + final Quantity> q2) { + if(q1.getScale()!=q2.getScale()) { + return OperandMode.MIXED; + } + return isAbsolute(q1) + ? OperandMode.ALL_ABSOLUTE + : OperandMode.ALL_RELATIVE; + } + public boolean isAllRelative() { + return this==ALL_RELATIVE; + } + } + + public static boolean isAbsolute(final Quantity> quantity) { + return ABSOLUTE==quantity.getScale(); + } + + public static boolean isRelative(final Quantity> quantity) { + return RELATIVE==quantity.getScale(); + } + + public static > ComparableQuantityconvertTo( + final Quantityquantity, + final UnitanotherUnit) { + + final UnitConverter converter = quantity.getUnit().getConverterTo(anotherUnit); + + if (isRelative(quantity)) { + final Number linearFactor = linearFactorOf(converter).orElse(null); + if(linearFactor==null) { + throw unsupportedRelativeScaleConversion(quantity, anotherUnit); + } + final Number valueInOtherUnit = Calculator.of(linearFactor).multiply(quantity.getValue()).peek(); + return Quantities.getQuantity(valueInOtherUnit, anotherUnit, Scale.RELATIVE); + } + + final Number convertedValue = converter.convert(quantity.getValue()); + return Quantities.getQuantity(convertedValue, anotherUnit, Scale.ABSOLUTE); + } + + public static> ComparableQuantityaddition( + final Quantityq1, + final Quantityq2, + final BinaryOperatoroperator) { + + final boolean yieldsRelativeScale = OperandMode.get(q1, q2).isAllRelative(); + + // converting almost all, except system units and those that are shifted and relative like eg. Δ2°C == Δ2K + final ToSystemUnitConverter thisConverter = toSystemUnitConverterForAdd(q1, q1); + final ToSystemUnitConverter thatConverter = toSystemUnitConverterForAdd(q1, q2); + + final Number thisValueInSystemUnit = thisConverter.apply(q1.getValue()); + final Number thatValueInSystemUnit = thatConverter.apply(q2.getValue()); + + final Number resultValueInSystemUnit = operator.apply(thisValueInSystemUnit, thatValueInSystemUnit); + + if (yieldsRelativeScale) { + return Quantities.getQuantity(thisConverter.invert(resultValueInSystemUnit), q1.getUnit(), RELATIVE); + } + + final boolean needsInvering = !thisConverter.isNoop() || !thatConverter.isNoop(); + final Number resultValueInThisUnit = needsInvering + ? q1.getUnit().getConverterTo(q1.getUnit().getSystemUnit()).inverse().convert(resultValueInSystemUnit) + : resultValueInSystemUnit; + + return Quantities.getQuantity(resultValueInThisUnit, q1.getUnit(), ABSOLUTE); + } + + public static > ComparableQuantityscalarMultiplication( + final Quantityquantity, + final UnaryOperatoroperator) { + + // if operands has scale RELATIVE, multiplication is trivial + if (isRelative(quantity)) { + return Quantities.getQuantity(operator.apply(quantity.getValue()), quantity.getUnit(), RELATIVE); + } + + final ToSystemUnitConverter thisConverter = toSystemUnitConverterForMul(quantity); + + final Number thisValueWithAbsoluteScale = thisConverter.apply(quantity.getValue()); + final Number resultValueInAbsUnits = operator.apply(thisValueWithAbsoluteScale); + final boolean needsInvering = !thisConverter.isNoop(); + + final Number resultValueInThisUnit = needsInvering + ? quantity.getUnit().getConverterTo(quantity.getUnit().getSystemUnit()).inverse().convert(resultValueInAbsUnits) + : resultValueInAbsUnits; + + return Quantities.getQuantity(resultValueInThisUnit, quantity.getUnit(), quantity.getScale()); + } + + public static ComparableQuantity> multiplication( + final Quantity> q1, + final Quantity> q2, + final BinaryOperator amountOperator, + final BinaryOperator > unitOperator) { + + final ToSystemUnitConverter thisConverter = toSystemUnitConverterForMul(q1); + final ToSystemUnitConverter thatConverter = toSystemUnitConverterForMul(q2); + + final Number thisValueWithAbsoluteScale = thisConverter.apply(q1.getValue()); + final Number thatValueWithAbsoluteScale = thatConverter.apply(q2.getValue()); + + final Number resultValueInSystemUnits = amountOperator.apply(thisValueWithAbsoluteScale, thatValueWithAbsoluteScale); + + final Unit> thisAbsUnit = thisConverter.isNoop() ? q1.getUnit() : q1.getUnit().getSystemUnit(); + final Unit> thatAbsUnit = thatConverter.isNoop() ? q2.getUnit() : q2.getUnit().getSystemUnit(); + + return Quantities.getQuantity( + resultValueInSystemUnits, + unitOperator.apply(thisAbsUnit, thatAbsUnit)); + } + + private static > UnsupportedOperationException unsupportedRelativeScaleConversion( + Quantityquantity, + UnitanotherUnit) { + return new UnsupportedOperationException( + String.format( + "Conversion of Quantitity %s to Unit %s is not supported for realtive scale.", + quantity, anotherUnit)); + } + + // -- HELPER - LOW LEVEL + + // used for addition, also honors RELATIVE scale + private static> ToSystemUnitConverter toSystemUnitConverterForAdd( + final Quantityq1, + final Quantityq2) { + final UnitsystemUnit = q1.getUnit().getSystemUnit(); + return ToSystemUnitConverter.forQuantity(q2, systemUnit); + } + + // used for multiplication, also honors RELATIVE scale + private static> + ToSystemUnitConverter toSystemUnitConverterForMul(Quantity quantity) { + final Unit systemUnit = quantity.getUnit().getSystemUnit(); + return ToSystemUnitConverter.forQuantity(quantity, systemUnit); + } + + private static Optional linearFactorOf(UnitConverter converter) { + return (converter instanceof AbstractConverter) + ? ((AbstractConverter)converter).linearFactor() + : Optional.empty(); + } + + // also honors RELATIVE scale + private static class ToSystemUnitConverter implements UnaryOperator { + private final UnaryOperator unaryOperator; + private final UnaryOperator inverseOperator; + + public static > + ToSystemUnitConverter forQuantity(Quantityquantity, UnitsystemUnit) { + if(quantity.getUnit().equals(systemUnit)) { + return ToSystemUnitConverter.noop(); // no conversion required + } + + final UnitConverter converter = quantity.getUnit().getConverterTo(systemUnit); + + if(isAbsolute(quantity)) { + + return ToSystemUnitConverter.of(converter::convert); // convert to system units + + } else { + final Number linearFactor = linearFactorOf(converter).orElse(null); + if(linearFactor!=null) { + // conversion by factor required ... Δ2°C -> Δ2K , Δ2°F -> 5/9 * Δ2K + return ToSystemUnitConverter.factor(linearFactor); + } + // convert any other cases of RELATIVE scale to system unit (ABSOLUTE) ... + throw unsupportedConverter(converter, quantity.getUnit()); + } + } + + public Number invert(Number x) { + return isNoop() + ? x + : inverseOperator.apply(x); + } + + public static ToSystemUnitConverter of(UnaryOperatorunaryOperator) { + return new ToSystemUnitConverter(unaryOperator, null); + } + public static ToSystemUnitConverter noop() { + return new ToSystemUnitConverter(null, null); + } + public static ToSystemUnitConverter factor(Number factor) { + return new ToSystemUnitConverter( + number->Calculator.of(number).multiply(factor).peek(), + number->Calculator.of(number).divide(factor).peek()); + } + private ToSystemUnitConverter(UnaryOperator unaryOperator, UnaryOperator inverseOperator) { + this.unaryOperator = unaryOperator; + this.inverseOperator = inverseOperator; + } + public boolean isNoop() { + return unaryOperator==null; + } + @Override + public Number apply(Number x) { + return isNoop() + ? x + : unaryOperator.apply(x); + } + + private static UnsupportedOperationException unsupportedConverter(UnitConverter converter, Unit> unit) { + return new UnsupportedOperationException( + String.format( + "Scale conversion from RELATIVE to ABSOLUTE for Unit %s having Converter %s is not implemented.", + unit, converter)); + } + + } + +} diff --git a/src/main/java/tech/units/indriya/quantity/NumberQuantity.java b/src/main/java/tech/units/indriya/quantity/NumberQuantity.java index 06b84716..9ee38318 100644 --- a/src/main/java/tech/units/indriya/quantity/NumberQuantity.java +++ b/src/main/java/tech/units/indriya/quantity/NumberQuantity.java @@ -30,10 +30,6 @@ package tech.units.indriya.quantity; import static javax.measure.Quantity.Scale.ABSOLUTE; -import static javax.measure.Quantity.Scale.RELATIVE; - -import java.util.function.BinaryOperator; -import java.util.function.UnaryOperator; import javax.measure.Quantity; import javax.measure.Unit; @@ -41,6 +37,7 @@ import tech.units.indriya.AbstractQuantity; import tech.units.indriya.ComparableQuantity; import tech.units.indriya.internal.function.Calculator; +import tech.units.indriya.internal.function.ScaleHelper; /** * Implementation of {@link ComparableQuantity} that holds a Java {@link Number}, @@ -56,7 +53,7 @@ * The type of the quantity. * @author Andi Huber * @author Werner Keil - * @version 1.4 + * @version 1.5 * @since 1.0 * */ @@ -80,39 +77,39 @@ protected NumberQuantity(Number number, Unit unit) { @Override public ComparableQuantityadd(Quantitythat) { - return addition(that, + return ScaleHelper.addition(this, that, (thisValue, thatValue) -> Calculator.of(thisValue).add(thatValue).peek()); } @Override public ComparableQuantitysubtract(Quantitythat) { - return addition(that, + return ScaleHelper.addition(this, that, (thisValue, thatValue) -> Calculator.of(thisValue).subtract(thatValue).peek()); } @Override public ComparableQuantity> divide(Quantity> that) { - return multiplication(that, + return ScaleHelper.multiplication(this, that, (thisValue, thatValue) -> Calculator.of(thisValue).divide(thatValue).peek(), (thisUnit, thatUnit) -> thisUnit.divide(thatUnit)); } @Override public ComparableQuantitydivide(Number divisor) { - return scalarMultiplication(thisValue -> + return ScaleHelper.scalarMultiplication(this, thisValue -> Calculator.of(thisValue).divide(divisor).peek()); } @Override public ComparableQuantity> multiply(Quantity> that) { - return multiplication(that, + return ScaleHelper.multiplication(this, that, (thisValue, thatValue) -> Calculator.of(thisValue).multiply(thatValue).peek(), (thisUnit, thatUnit) -> thisUnit.multiply(thatUnit)); } @Override public ComparableQuantitymultiply(Number factor) { - return scalarMultiplication(thisValue -> + return ScaleHelper.scalarMultiplication(this, thisValue -> Calculator.of(thisValue).multiply(factor).peek()); } @@ -122,7 +119,7 @@ public ComparableQuantity> inverse() { .of(getValue()) .reciprocal() .peek(); - return Quantities.getQuantity(resultValueInThisUnit, getUnit().inverse()); + return Quantities.getQuantity(resultValueInThisUnit, getUnit().inverse(), getScale()); } @Override @@ -131,7 +128,7 @@ public Quantitynegate() { .of(getValue()) .negate() .peek(); - return Quantities.getQuantity(resultValueInThisUnit, getUnit()); + return Quantities.getQuantity(resultValueInThisUnit, getUnit(), getScale()); } @Override @@ -139,89 +136,6 @@ public Number getValue() { return value; } - // -- HELPER - - private ComparableQuantityaddition(Quantitythat, BinaryOperatoroperator) { - - final boolean yieldsRelativeScale = RELATIVE.equals(this.getScale()) && RELATIVE.equals(that.getScale()); - - // converting almost all, except system units and those that are shifted and relative like eg. Δ2°C == Δ2K - final ToSystemUnitConverter thisConverter = toSystemUnitConverterForAdd(this); - final ToSystemUnitConverter thatConverter = toSystemUnitConverterForAdd(that); - - final Number thisValueInSystemUnit = thisConverter.apply(this.getValue()); - final Number thatValueInSystemUnit = thatConverter.apply(that.getValue()); - - final Number resultValueInSystemUnit = operator.apply(thisValueInSystemUnit, thatValueInSystemUnit); - - if (yieldsRelativeScale) { - return Quantities.getQuantity(thisConverter.invert(resultValueInSystemUnit), this.getUnit(), RELATIVE); - } - - final boolean needsInvering = !thisConverter.isNoop() || !thatConverter.isNoop(); - final Number resultValueInThisUnit = needsInvering - ? this.getUnit().getConverterTo(this.getUnit().getSystemUnit()).inverse().convert(resultValueInSystemUnit) - : resultValueInSystemUnit; - - return Quantities.getQuantity(resultValueInThisUnit, this.getUnit(), ABSOLUTE); - } - - private ComparableQuantity scalarMultiplication(UnaryOperatoroperator) { - - // if operands has scale RELATIVE, multiplication is trivial - if (RELATIVE.equals(this.getScale())) { - return Quantities.getQuantity(operator.apply(this.getValue()), this.getUnit(), RELATIVE); - } - - final ToSystemUnitConverter thisConverter = toSystemUnitConverterForMul(this); - - final Number thisValueWithAbsoluteScale = thisConverter.apply(this.getValue()); - final Number resultValueInAbsUnits = operator.apply(thisValueWithAbsoluteScale); - final boolean needsInvering = !thisConverter.isNoop(); - - final Number resultValueInThisUnit = needsInvering - ? this.getUnit().getConverterTo(this.getUnit().getSystemUnit()).inverse().convert(resultValueInAbsUnits) - : resultValueInAbsUnits; - - return Quantities.getQuantity(resultValueInThisUnit, this.getUnit(), this.getScale()); - } - - private ComparableQuantity> multiplication( - Quantity> that, - BinaryOperator amountOperator, - BinaryOperator > unitOperator) { - - final ToSystemUnitConverter thisConverter = toSystemUnitConverterForMul(this); - final ToSystemUnitConverter thatConverter = toSystemUnitConverterForMul(that); - - final Number thisValueWithAbsoluteScale = thisConverter.apply(this.getValue()); - final Number thatValueWithAbsoluteScale = thatConverter.apply(that.getValue()); - - final Number resultValueInSystemUnits = amountOperator.apply(thisValueWithAbsoluteScale, thatValueWithAbsoluteScale); - - final Unit thisAbsUnit = thisConverter.isNoop() ? this.getUnit() : this.getUnit().getSystemUnit(); - final Unit> thatAbsUnit = thatConverter.isNoop() ? that.getUnit() : that.getUnit().getSystemUnit(); - - return Quantities.getQuantity( - resultValueInSystemUnits, - unitOperator.apply(thisAbsUnit, thatAbsUnit)); - } - - // -- HELPER - LOW LEVEL - - // used for addition, also honors RELATIVE scale - private ToSystemUnitConverter toSystemUnitConverterForAdd(Quantityquantity) { - final UnitsystemUnit = this.getUnit().getSystemUnit(); - return ToSystemUnitConverter.forQuantity(quantity, systemUnit); - } - - // used for multiplication, also honors RELATIVE scale - private static> - ToSystemUnitConverter toSystemUnitConverterForMul(Quantity quantity) { - final Unit systemUnit = quantity.getUnit().getSystemUnit(); - return ToSystemUnitConverter.forQuantity(quantity, systemUnit); - } - } diff --git a/src/test/java/tech/units/indriya/quantity/AbsoluteVsRelativeTest.java b/src/test/java/tech/units/indriya/quantity/AbsoluteVsRelativeTest.java index fc433d8f..9ed05ac0 100644 --- a/src/test/java/tech/units/indriya/quantity/AbsoluteVsRelativeTest.java +++ b/src/test/java/tech/units/indriya/quantity/AbsoluteVsRelativeTest.java @@ -41,7 +41,6 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import tech.units.indriya.quantity.Quantities; import tech.units.indriya.unit.Units; /** diff --git a/src/test/java/tech/units/indriya/quantity/QuantitiesTest.java b/src/test/java/tech/units/indriya/quantity/QuantitiesTest.java index a6803a52..a30e7e5f 100644 --- a/src/test/java/tech/units/indriya/quantity/QuantitiesTest.java +++ b/src/test/java/tech/units/indriya/quantity/QuantitiesTest.java @@ -29,14 +29,6 @@ */ package tech.units.indriya.quantity; -import static javax.measure.Quantity.Scale.RELATIVE; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static tech.units.indriya.NumberAssertions.assertNumberEquals; -import static tech.units.indriya.unit.Units.CELSIUS; -import static tech.units.indriya.unit.Units.PASCAL; - import java.math.BigDecimal; import java.math.BigInteger; @@ -46,11 +38,21 @@ import javax.measure.quantity.Temperature; import javax.measure.quantity.Time; +import static javax.measure.Quantity.Scale.RELATIVE; + import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + import tech.units.indriya.ComparableQuantity; import tech.units.indriya.unit.Units; +import static tech.units.indriya.NumberAssertions.assertNumberEquals; +import static tech.units.indriya.unit.Units.CELSIUS; +import static tech.units.indriya.unit.Units.PASCAL; + /** * * @author Werner Keil @@ -58,7 +60,7 @@ * @version 0.6 */ public class QuantitiesTest { - + @Test public void ofTest() { Quantity pressure = Quantities.getQuantity(BigDecimal.ONE, PASCAL); @@ -138,10 +140,11 @@ public void toTest() { } @Test - //@Disabled("fails because of MultiplyConverter not being sufficiently accurate") public void quantityEquivalentTest() { ComparableQuantity shouldBe = Quantities.getQuantity(15, Units.KILOMETRE_PER_HOUR); Quantity parsedSpeed = Quantities.getQuantity("15.0 km/h").asType(Speed.class); assertTrue(shouldBe.isEquivalentTo(parsedSpeed)); } + + } \ No newline at end of file diff --git a/src/test/java/tech/units/indriya/quantity/RelativeQuantitiesTest.java b/src/test/java/tech/units/indriya/quantity/RelativeQuantitiesTest.java new file mode 100644 index 00000000..846c4bd7 --- /dev/null +++ b/src/test/java/tech/units/indriya/quantity/RelativeQuantitiesTest.java @@ -0,0 +1,148 @@ +/* + * Units of Measurement Reference Implementation + * Copyright (c) 2005-2020, Jean-Marie Dautelle, Werner Keil, Otavio Santana. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions + * and the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of JSR-385, Indriya nor the names of their contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package tech.units.indriya.quantity; + +import javax.measure.Quantity; +import javax.measure.Unit; +import javax.measure.Quantity.Scale; +import javax.measure.quantity.Energy; +import javax.measure.quantity.Temperature; +import javax.measure.quantity.Volume; + +import static javax.measure.MetricPrefix.KILO; +import static javax.measure.MetricPrefix.MILLI; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import tech.units.indriya.format.SimpleUnitFormat; +import tech.units.indriya.function.RationalNumber; +import tech.units.indriya.unit.Units; + +import static tech.units.indriya.NumberAssertions.assertNumberEquals; + +/** + * + * @author Andi Huber + */ +class RelativeQuantitiesTest { + + private static Unit UNIT_CAL; + private static Unit UNIT_MILLILITRE; + private static Unit UNIT_KILO_WATT_HOUR; + + @BeforeAll + static void setUp() { + UNIT_CAL = Units.JOULE.multiply(RationalNumber.of(4184, 1000)); // as per definition + UNIT_MILLILITRE = MILLI(Units.LITRE); + UNIT_KILO_WATT_HOUR = KILO(Units.WATT.multiply(Units.HOUR)).asType(Energy.class); + SimpleUnitFormat.getInstance().label(UNIT_CAL, "cal"); // customize formatting + } + + //[indriya#295] + @Test @DisplayName("Unit Conversion should carry over Scale") + void relativeTemperatureRoundTrip() { + + Quantity celsiusRelative = Quantities.getQuantity(1, Units.CELSIUS, Scale.RELATIVE); + Quantity kelvinRelative = celsiusRelative.to(Units.KELVIN); + + assertEquals(kelvinRelative.getScale(), Scale.RELATIVE); + assertNumberEquals(1, kelvinRelative.getValue(), 1E-12); + + Quantity celsiusRelativeAfterRoundtrip = kelvinRelative.to(Units.CELSIUS); + + assertEquals(celsiusRelativeAfterRoundtrip.getScale(), Scale.RELATIVE); + assertNumberEquals(1, celsiusRelativeAfterRoundtrip.getValue(), 1E-12); + + } + + //[indriya#294] + @Disabled //FIXME + @Test @DisplayName("Multiplication should carry over operand Units (abs. scale)") + void intuitivelyKeepUnits_whenAbsolute() { + + // given + Quantity> energyPerVolume = Quantities.getQuantity(1, UNIT_CAL.divide(UNIT_MILLILITRE)); + assertEquals("1 cal/ml", energyPerVolume.toString()); + + // when scalar multiply, keep units + assertEquals("2 cal/ml", energyPerVolume.multiply(2).toString()); + + // when multiply by 'ml', keep 'cal' as unit (don't convert to system units) + assertEquals("1 ml", energyPerVolume.multiply(Quantities.getQuantity(1, UNIT_MILLILITRE)).toString()); + } + + + //[indriya#294] + @Disabled //FIXME + @Test @DisplayName("Multiplication should carry over operand Units if possible") + void intuitivelyKeepUnits_whenRelative() { + + // given + Quantity deltaT = Quantities.getQuantity(10, Units.CELSIUS, Scale.RELATIVE); + Quantity> temperaturePerTime = deltaT.divide(Quantities.getQuantity(1, Units.HOUR)); + + // when scalar multiply, keep units (don't convert to system units) + assertEquals("20 ℃", deltaT.multiply(2).toString()); + + // convert Celsius[℃] to Kelvin[k], but keep Hour[h] (don't convert to system unit Second[s]) + assertEquals("20 K/h", temperaturePerTime.toString()); + } + + + @Test @DisplayName("Heat Requirement Calculation with relative-scoped Celsius") + void relativeTemperatureMultiplication() { + + // given + Quantity> ENERGY_NEEDED_PER_MILLILITRE_AND_KELVIN = + Quantities.getQuantity(1, UNIT_CAL.divide(Units.KELVIN).divide(UNIT_MILLILITRE)); + Quantity givenVolume = Quantities.getQuantity(1000, Units.LITRE); + Quantity deltaT = Quantities.getQuantity(20, Units.CELSIUS, Scale.RELATIVE); + + // what's the energy requirement we need to put into given volume to heat it up a temperature amount of deltaT? + Quantity energyRequirementToHeatUpGivenVolume = + ENERGY_NEEDED_PER_MILLILITRE_AND_KELVIN + .multiply(givenVolume) // 1000 Litre + .multiply(deltaT) // 20ºC + .asType(Energy.class); + + // convert to kWh + Quantity kWh = energyRequirementToHeatUpGivenVolume + .to(UNIT_KILO_WATT_HOUR); + + assertNumberEquals(RationalNumber.of(1046, 45), kWh.getValue(), 1E-20); // ~ 23.2444 kWh + } + + +} \ No newline at end of file diff --git a/src/test/java/tech/units/indriya/quantity/WolframTutorialTemperatureTest.java b/src/test/java/tech/units/indriya/quantity/WolframTutorialTemperatureTest.java index 8950ca5b..a13a5fb9 100644 --- a/src/test/java/tech/units/indriya/quantity/WolframTutorialTemperatureTest.java +++ b/src/test/java/tech/units/indriya/quantity/WolframTutorialTemperatureTest.java @@ -113,7 +113,7 @@ public void in1() { assertFahrenheit(465.67, t_2f, ABSOLUTE); final Quantity t_k = t_2f.to(Units.KELVIN); - assertKelvin(514.078, t_k); + assertKelvin(514.078, t_k, ABSOLUTE); } @Test @@ -121,7 +121,7 @@ public void in1() { public void in2() { final Quantity t_f = Quantities.getQuantity(3., DegreesFahrenheit, ABSOLUTE); final Quantity t_k = t_f.to(Units.KELVIN).multiply(2.); - assertKelvin(514.078, t_k); + assertKelvin(514.078, t_k, ABSOLUTE); } @Test @@ -130,7 +130,7 @@ public void in3() { final Quantity t_f = Quantities.getQuantity(3., DegreesFahrenheit, RELATIVE); assertEquals(RELATIVE, t_f.getScale()); final Quantity t_k = t_f.to(Units.KELVIN).multiply(2.); - assertKelvin(3.333333, t_k); + assertKelvin(3.333333, t_k, RELATIVE); } @Test @@ -138,7 +138,7 @@ public void in3() { public void in4() { final Quantity t_f = Quantities.getQuantity(3., DegreesFahrenheit, RELATIVE); final Quantity t_k = t_f.multiply(2.).to(Units.KELVIN); - assertKelvin(3.333333, t_k); + assertKelvin(3.333333, t_k, RELATIVE); } // -- (2) -- Adding Temperatures @@ -192,7 +192,7 @@ public void in9() { final Quantity t_k2 = Quantities.getQuantity(4., Units.KELVIN); final Quantity t_k = t_k1.add(t_k2); - assertKiloKelvin(751. / 250., t_k); + assertKiloKelvin(751. / 250., t_k, ABSOLUTE); } // -- 3 -- Multiplying Temperatures @@ -206,7 +206,7 @@ public void in10() { assertCelsius((18.2 * (3. + 273.15)) - 273.15, t_c2, ABSOLUTE); final Quantity t_k = t_c2.to(Units.KELVIN); - assertKelvin((18.2 * (3. + 273.15)), t_k); + assertKelvin((18.2 * (3. + 273.15)), t_k, ABSOLUTE); } @Test @@ -216,7 +216,7 @@ public void in11() { final Quantity t_c = Quantities.getQuantity(3., Units.CELSIUS); final Quantity t_k = t_c.to(Units.KELVIN).multiply(18.2); - assertKelvin(5025.93, t_k); + assertKelvin(5025.93, t_k, ABSOLUTE); } @Test @@ -245,15 +245,15 @@ public void in13() { // -- HELPER - private static void assertKelvin(Number expected, Quantity> x) { + private static void assertKelvin(Number expected, Quantity> x, Scale scale) { assertEquals("K", x.getUnit().toString()); - assertEquals(ABSOLUTE, x.getScale()); // should be ABSOLUTE by convention + assertEquals(scale, x.getScale()); assertNumberEquals(expected, x.getValue(), 1E-3); } - private static void assertKiloKelvin(Number expected, Quantity> x) { + private static void assertKiloKelvin(Number expected, Quantity> x, Scale scale) { assertEquals("kK", x.getUnit().toString()); - assertEquals(ABSOLUTE, x.getScale()); // should be ABSOLUTE by convention + assertEquals(scale, x.getScale()); assertNumberEquals(expected, x.getValue(), 1E-3); }