diff --git a/README.md b/README.md index de4ed0e..bec107a 100644 --- a/README.md +++ b/README.md @@ -61,13 +61,13 @@ features, such as overloaded operators. Copy the Maven dependency provided below to your pom.xml file, and you are ready to go. For other package managers, check maven central repository: -[UNITILITY](https://search.maven.org/artifact/com.synerset/unitility/2.3.0/jar?eh=). +[UNITILITY](https://search.maven.org/artifact/com.synerset/unitility/2.4.0/jar?eh=). ```xml com.synerset unitility-core - 2.3.0 + 2.4.0 ``` If you use frameworks to develop web applications, it is recommended to use Unitility extension modules, @@ -79,7 +79,7 @@ Extension for the Spring Boot framework: com.synerset unitility-spring - 2.3.0 + 2.4.0 ``` Extension for the Quarkus framework: @@ -87,7 +87,7 @@ Extension for the Quarkus framework: com.synerset unitility-quarkus - 2.3.0 + 2.4.0 ``` Extensions include CORE module, so you don't have to put it separate in your pom. @@ -318,7 +318,6 @@ Temperature temperatureToAdd = Temperature.ofKelvins(20 + 273.15); Temperature actualTemperature = sourceTemperature.add(temperatureToAdd); // results in: 40 °C ``` - Performing addition or subtraction will yield a PhysicalQuantity with the same unit. The algorithm will convert the unit of the addend quantity to match that of the augend, ensuring that the operation is conducted within a consistent unit. The unit of the resulting quantity will be set to match that of the augend. @@ -342,6 +341,49 @@ In the provided example, division and multiplication both yield a double value. absence of a unit resolver within the developmental stage. The existing unit values are directly employed for multiplication or division operations. +* natural logarithm, logarithm with base of 10 + +```java +Distance roadLength = Distance.ofKilometers(100); +Distance reducedDistance1 = roadLength.log(); // 4.60517(..) +Distance reducedDistance2 = roadLength.log10(); // 2.0 +``` + +* trigonometric functions: sin(), cos(), tan(), ctg() including hyperbolic and reversed + +```java +Angle exampleAngle = Angle.ofDegrees(90); +Angle sinResult = exampleAngle.sin(); // 1 +Angle cosResult = exampleAngle.cos(); // 0 + +Angle angle45 = exampleAngle.withValue(45); +Angle tanResult = angle45.tan(); // 1 +Angle cotResult = angle45.cot(); // 1 + +Angle angleRad1 = exampleAngle.toRadians().withValue(1); +Angle aSinResult = angleRad1.asin(); // 1.570796(..) +Angle aCosResult = angleRad1.acos(); // 0 +Angle aTanResult = angleRad1.atan(); // 0.785398(..) +Angle aCotResult = angleRad1.acot(); // 0.785398(..) +``` +Please keep in mind that not all values belong to the domain of a given trigonometric function. +Some might throw an exception if they hold invalid value for a specific function. +Value will be automatically converted to radians before using Java Math trigonometric functions. + +* ceil, floor, roundHalfEven with relevant digits + +```java +ThermalConductivity thermCond = ThermalConductivity.ofWattsPerMeterKelvin(0.00366); +ThermalConductivity ceilResult = thermCond.ceil(); // 1 +ThermalConductivity floorResult = thermCond.floor(); // 0 +ThermalConductivity roundedResult = thermCond.roundHalfEven(2); // 0.0037 +``` +Please note that rounding function is based on concept of relevant digits. It is NOT a simple truncation to a specified +number of decimal places. This function will evaluate where relevant digits starts and will attempt to keep the specified +number by user. For an example for roundHalfEven(3) will output:
+0.12345 -> roundHalfEven(3) -> 0.123
+0.00012345 -> roundHalfEven(3) -> 0.000123
+ ### 4.5 Equality and sorting Each PhysicalQuantity class has implemented the equals and hashCode methods and implements the Comparable interface, @@ -439,7 +481,7 @@ deserialization back to Java objects. To include this module in your project, us com.synerset unitility-jackson - 2.3.0 + 2.4.0 ``` PhysicalQuantity JSON structure for valid serialization / deserialization has been defined as in the following example: @@ -456,7 +498,7 @@ obtain the appropriate parser depending on the class type. This module is part o therefore, it does not need to be added explicitly if framework extensions are included in the project. ## 5.2 Spring Boot module -Module tested for Spring Boot platform version: **3.2.2**
+Module tested for Spring Boot version: **3.3.0**
Spring Boot module includes **unitility-jackson** and **unitility-core** modules, and it will automatically create required beans through the autoconfiguration dependency injection mechanism. To use Spring extension module, add the following dependency: @@ -464,7 +506,7 @@ add the following dependency: com.synerset unitility-spring - 2.3.0 + 2.4.0 ``` Adding Spring module to the project will automatically: @@ -496,7 +538,7 @@ steps must be carried out to ensure that custom unit is properly resolved from J see a section: [Registering custom quantity in Spring](#63-registering-custom-quantities-in-spring).
## 5.3 Quarkus module -Module tested for Quarkus platform version: **3.5.1**
+Module tested for Quarkus platform version: **3.11.0**
Quarkus module includes **unitility-jackson** and **unitility-core** modules, and it will be automatically discovered through Jandex index and will create required CDI beans. To use Quarkus extension module, add following dependency: @@ -504,7 +546,7 @@ add following dependency: com.synerset unitility-quarkus - 2.3.0 + 2.4.0 ``` Adding Quarkus module to the project will automatically: diff --git a/pom.xml b/pom.xml index 5886c99..a7d7589 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ - 2.3.1 + 2.4.0 17 @@ -54,26 +54,26 @@ 2.17.1 - 3.2.5 + 3.3.0 - 3.10.1 + 3.11.0 3.1.0 - 8.0.1.Final 6.0.0 + 8.0.1.Final - 0.8.11 + 0.8.12 5.10.2 3.25.3 1.6.0 3.6.3 - 3.3.0 + 3.3.1 1.6.13 - 3.2.2 - 3.1.6 + 3.2.5 + 3.2.0 synerset diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/CalculableQuantity.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/CalculableQuantity.java index 02df31c..52d8b43 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/CalculableQuantity.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/CalculableQuantity.java @@ -1,6 +1,7 @@ package com.synerset.unitility.unitsystem; import com.synerset.unitility.unitsystem.exceptions.UnitSystemArgumentException; +import com.synerset.unitility.unitsystem.util.ValueFormatter; /** * Interface representing a calculable quantity with operations for basic arithmetics. @@ -16,6 +17,7 @@ public interface CalculableQuantity + * This method calculates the value of the physical quantity raised to the specified exponent + * and returns a new instance of the physical quantity with the updated value. + * + * @param exponent The exponent to which the current value is raised. + * @return A new physical quantity with the value raised to the given exponent. + */ + default Q power(double exponent) { + double newValue = Math.pow(getValue(), exponent); + return withValue(newValue); + } + + /** + * Calculates the square root of the physical quantity's value.

+ * This method computes the square root of the current value of the physical quantity + * and returns a new instance of the physical quantity with the updated value. + * + * @return A new physical quantity with the value as the square root of the original value. + */ + default Q sqrt() { + double newValue = Math.sqrt(getValue()); + return withValue(newValue); + } + + /** + * Calculates the natural logarithm of the physical quantity's value. + * This method computes the natural logarithm of the current value of the physical quantity + * and returns a new instance of the physical quantity with the updated value.

+ * IMPORTANT: In some parts of the world (like Europe) natural logarithm is expressed as 'ln' symbol, + * and log means logarithm with base of 10. In this app, consistency with Math library was maintained, + * therefore log is natural logarithm (with e number in a base). + * + * @return A new physical quantity with the value as the natural logarithm of the original value. + * @throws UnitSystemArgumentException if the current value is not greater than zero. + */ + default Q log() { + double value = getValue(); + if (value <= 0) { + throw new UnitSystemArgumentException("Cannot calculate logarithm for non-positive value: " + value); + } + double newValue = Math.log(value); + return withValue(newValue); + } + + /** + * Calculates the base-10 logarithm of the physical quantity's value. + * This method computes the base-10 logarithm of the current value of the physical quantity + * and returns a new instance of the physical quantity with the updated value.

+ * IMPORTANT: In some parts of the world (like Europe) natural logarithm is expressed as 'ln' symbol, + * and log means logarithm with base of 10. In this app, consistency with Math library was maintained, + * therefore log is natural logarithm (with e number in a base).

+ * + * @return A new physical quantity with the value as the base-10 logarithm of the original value. + * @throws UnitSystemArgumentException if the current value is not greater than zero. + */ + default Q log10() { + double value = getValue(); + if (value <= 0) { + throw new UnitSystemArgumentException("Cannot calculate logarithm for non-positive value: " + value); + } + double newValue = Math.log10(value); + return withValue(newValue); + } + + // Ceiling, and rounding up + + /** + * Returns a new physical quantity with the value rounded up to the nearest integer.

+ * Examples:

+ * ceil() for 10.123456 -> will result to 11

+ * ceil() for 0.123456 -> will result to 1

+ * ceil() for -10.123456- > will result to 10 (this one is contr intuitive) + * + * @return A new physical quantity with the value rounded up. + */ + default Q ceil() { + double newValue = Math.ceil(getValue()); + return withValue(newValue); + } + + /** + * Returns a new physical quantity with the value rounded down to the nearest integer.

+ * Examples:

+ * floor() for 10.123456 -> will result to 10

+ * floor() for 0.123456 -> will result to 0

+ * floor() for -10.123456- > will result to -11 (this one is contr intuitive) + * + * @return A new physical quantity with the value rounded down. + */ + default Q floor() { + double newValue = Math.floor(getValue()); + return withValue(newValue); + } + + /** + * Returns a new physical quantity with the value rounded in HALF_EVEN way to the specified number of relevant digits. + * Absolute value of an input argument for relevant digits is used. + * Examples:

+ * roundHalfEven(0) for 10.123456 -> will result to 10

+ * roundHalfEven(1) for 0.153456 -> will result to 0.2

+ * roundHalfEven(2) for -10.123456- > will result to -10.12 + * + * @param relevantDigits The number of relevant digits to round to. + * @return A new physical quantity with the value rounded to the specified number of relevant digits. + */ + default Q roundHalfEven(int relevantDigits) { + String formattedValue = ValueFormatter.toStringWithRelevantDigits(getValue(), relevantDigits); + double newValue = Double.parseDouble(formattedValue); + return withValue(newValue); + } + // Handling PhysicalQuantity as input argument /** @@ -203,4 +317,20 @@ default > double div(T inputQuanti return divide(inputQuantity); } + /** + * Raises this physical quantity's value to the power of another physical quantity's value.

+ * This method calculates the result of raising this quantity's value to the power of the value + * of the input quantity, and returns the result. + * + * @param inputQuantity The other physical quantity for raising to the power. + * @return The result of raising this quantity's value to the power of the other quantity's value. + */ + default > double power(T inputQuantity) { + if (inputQuantity == null) { + return getValue(); + } + double thisValue = getValue(); + return Math.pow(thisValue, inputQuantity.getValue()); + } + } \ No newline at end of file diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantity.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantity.java index 1ccee8f..b35d119 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantity.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/PhysicalQuantity.java @@ -11,6 +11,14 @@ */ public interface PhysicalQuantity extends Comparable> { + /** + * Create a new physical quantity with new value of the same unit. + * + * @param value The value for the new physical quantity. + * @return A new physical quantity with the specified value. + */ + PhysicalQuantity withValue(double value); + /** * Get the value of the physical quantity. * diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/TrigonometricQuantity.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/TrigonometricQuantity.java new file mode 100644 index 0000000..115988c --- /dev/null +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/TrigonometricQuantity.java @@ -0,0 +1,179 @@ +package com.synerset.unitility.unitsystem; + +import com.synerset.unitility.unitsystem.common.AngleUnit; +import com.synerset.unitility.unitsystem.common.AngleUnits; +import com.synerset.unitility.unitsystem.exceptions.UnitSystemArgumentException; + +import java.util.function.DoubleUnaryOperator; + +/** + * Interface representing a calculable quantity with operations for basic arithmetics. + * + * @param The type of {@link PhysicalQuantity} implementing this interface. + */ +public interface TrigonometricQuantity> extends CalculableQuantity { + + // Trigonometric + /** + * Calculate the sine of the physical quantity's value in a current unit.

+ * If the quantity is an instance of {@link AngleUnit} its value will be automatically + * converted to radians before calculating resulting value. + * + * @return A new physical quantity with the sine of the current value in a current unit. + */ + default Q sin() { + return applyTrigonometricFunction(Math::sin); + } + + /** + * Calculate the hyperbolic sine of the physical quantity's value. + * + * @return A new physical quantity with the hyperbolic sine of the current value. + */ + default Q sinh() { + return applyTrigonometricFunction(Math::sinh); + } + + /** + * Calculate the arcsine of the physical quantity's value. + * + * @return A new physical quantity with the arcsine of the current value. + * @throws UnitSystemArgumentException if the value is out of the range [-1, 1]. + */ + default Q asin() { + double value = getValueInRadians(); + if (value < -1 || value > 1) { + throw new UnitSystemArgumentException("Value out of range for arcsine. Valid range is [-1, 1]."); + } + return applyTrigonometricFunction(Math::asin); + } + + /** + * Calculate the cosine of the physical quantity's value in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the cosine of the current value. + */ + default Q cos() { + return applyTrigonometricFunction(Math::cos); + } + + /** + * Calculate the hyperbolic cosine of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the hyperbolic cosine of the current value. + */ + default Q cosh() { + return applyTrigonometricFunction(Math::cosh); + } + + /** + * Calculate the arccosine of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the arccosine of the current value. + * @throws UnitSystemArgumentException if the value is out of the range [-1, 1]. + */ + default Q acos() { + double value = getValueInRadians(); + if (value < -1 || value > 1) { + throw new UnitSystemArgumentException("Value out of range for arccosine. Valid range is [-1, 1]."); + } + return applyTrigonometricFunction(Math::acos); + } + + /** + * Calculate the tangent of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the tangent of the current value. + * @throws UnitSystemArgumentException if the value is an odd multiple of π/2. + */ + default Q tan() { + double value = getValueInRadians(); + if (isMultipleOfPiOverTwo(value)) { + throw new UnitSystemArgumentException("Tangent is undefined for odd multiples of π/2."); + } + return applyTrigonometricFunction(Math::tan); + } + + /** + * Calculate the hyperbolic tangent of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the hyperbolic tangent of the current value. + */ + default Q tanh() { + return applyTrigonometricFunction(Math::tanh); + } + + /** + * Calculate the arctangent of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the arctangent of the current value. + */ + default Q atan() { + return applyTrigonometricFunction(Math::atan); + } + + /** + * Calculate the cotangent of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the cotangent of the current value. + * @throws UnitSystemArgumentException if the value is a multiple of π. + */ + default Q cot() { + double value = getValueInRadians(); + if (isMultipleOfPi(value)) { + throw new UnitSystemArgumentException("Cotangent is undefined for multiples of π."); + } + return withValue(1 / tan().getValue()); + } + + /** + * Calculate the hyperbolic cotangent of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the hyperbolic cotangent of the current value. + * @throws UnitSystemArgumentException if the value is 0. + */ + default Q coth() { + double value = getValueInRadians(); + if (value == 0) { + throw new UnitSystemArgumentException("Hyperbolic cotangent is undefined for 0."); + } + return withValue(1 / tanh().getValue()); + } + + /** + * Calculate the arccotangent of the physical quantity's in a current unit. + * Use toUnit(Unit) if intended to convert value in other supported unit. + * + * @return A new physical quantity with the arccotangent of the current value. + */ + default Q acot() { + return withValue(1 / atan().getValue()); + } + + @SuppressWarnings("unchecked") + private Q applyTrigonometricFunction(DoubleUnaryOperator unaryOperator) { + PhysicalQuantity unitInRadians = toUnit(AngleUnits.RADIANS); + double resultingValue = unaryOperator.applyAsDouble(unitInRadians.getValue()); + return (Q) unitInRadians.withValue(resultingValue).toUnit(getUnitType()); + } + + private double getValueInRadians() { + return toUnit(AngleUnits.RADIANS).getValue(); + } + + private boolean isMultipleOfPiOverTwo(double value) { + return Math.abs((value / (Math.PI / 2)) % 1) < 1e-10; + } + + private boolean isMultipleOfPi(double value) { + return Math.abs((value / Math.PI) % 1) < 1e-10; + } +} diff --git a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/common/Angle.java b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/common/Angle.java index 86c8273..b943e1d 100644 --- a/unitility-core/src/main/java/com/synerset/unitility/unitsystem/common/Angle.java +++ b/unitility-core/src/main/java/com/synerset/unitility/unitsystem/common/Angle.java @@ -1,10 +1,10 @@ package com.synerset.unitility.unitsystem.common; -import com.synerset.unitility.unitsystem.CalculableQuantity; +import com.synerset.unitility.unitsystem.TrigonometricQuantity; import java.util.Objects; -public class Angle implements CalculableQuantity { +public class Angle implements TrigonometricQuantity { private final double value; private final double baseValue; diff --git a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/CalculableQuantityTest.java b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/CalculableQuantityTest.java new file mode 100644 index 0000000..5a8d2a4 --- /dev/null +++ b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/CalculableQuantityTest.java @@ -0,0 +1,303 @@ +package com.synerset.unitility.unitsystem; + +import com.synerset.unitility.unitsystem.exceptions.UnitSystemArgumentException; +import com.synerset.unitility.unitsystem.thermodynamic.Pressure; +import com.synerset.unitility.unitsystem.thermodynamic.Temperature; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class CalculableQuantityTest { + + // Basic math operations + + @Test + @DisplayName("Add value: should correctly add value to quantity") + void add_shouldAddValueToQuantity() { + // Given + Temperature temperature = Temperature.ofCelsius(20); + + // When + Temperature actualTemperature = temperature.plus(20); + + // Then + Temperature exptectedTemperature = Temperature.ofCelsius(40); + assertThat(actualTemperature).isEqualTo(exptectedTemperature); + } + + @Test + @DisplayName("Add quantity: should correctly subtract value from quantity") + void add_shouldSubtractValueToQuantity() { + // Given + Temperature temperature = Temperature.ofCelsius(20); + + // When + Temperature actualTemperature = temperature.minus(20); + + // Then + Temperature exptectedTemperature = Temperature.ofCelsius(0); + assertThat(actualTemperature).isEqualTo(exptectedTemperature); + } + + @Test + @DisplayName("Subtract value: should correctly add quantities of the same type, but different units") + void subtract_shouldAddQuantityToSourceQuantity() { + // Given + Temperature sourceTemperature = Temperature.ofCelsius(20); + Temperature temperatureToAdd = Temperature.ofKelvins(20 + 273.15); + + // When + Temperature actualTemperature = sourceTemperature.plus(temperatureToAdd); + + // Then + Temperature exptectedTemperature = Temperature.ofCelsius(40); + assertThat(actualTemperature).isEqualTo(exptectedTemperature); + } + + @Test + @DisplayName("Subtract quantity: should correctly subtract quantities of the same type, but different units") + void subtract_shouldSubtractQuantityToSourceQuantity() { + // Given + Temperature sourceTemperature = Temperature.ofCelsius(20); + Temperature temperatureToAdd = Temperature.ofKelvins(20 + 273.15); + + // When + Temperature actualTemperature = sourceTemperature.minus(temperatureToAdd); + + // Then + Temperature exptectedTemperature = Temperature.ofCelsius(0); + assertThat(actualTemperature).isEqualTo(exptectedTemperature); + } + + @Test + @DisplayName("Multiply by value: should correctly multiply quantity by value") + void multiply_shouldMultiplyValueToQuantity() { + // Given + Temperature temperature = Temperature.ofCelsius(20); + + // When + Temperature actualTemperature = temperature.multiply(2); + Temperature actualTemperatureTimes = temperature.times(2); + + // Then + Temperature exptectedTemperature = Temperature.ofCelsius(40); + assertThat(actualTemperature).isEqualTo(exptectedTemperature).isEqualTo(actualTemperatureTimes); + } + + @Test + @DisplayName("Multiply by quantity: should correctly multiply quantities of the same type, but different units") + void multiply_shouldMultiplyQuantityToSourceQuantity() { + // Given + Temperature sourceTemperature = Temperature.ofCelsius(20); + Pressure pressure = Pressure.ofPascal(2); + + // When + double actualMultiplyResult = sourceTemperature.multiply(pressure); + double actualMultiplyResultTimes = sourceTemperature.times(pressure); + + // Then + double expectedMultiplyResult = 40; + assertThat(actualMultiplyResult).isEqualTo(expectedMultiplyResult).isEqualTo(actualMultiplyResultTimes); + } + + @Test + @DisplayName("Divide by value: should correctly divide quantity by value") + void divide_shouldDivideValueToQuantity() { + // Given + Temperature temperature = Temperature.ofCelsius(20); + + // When + Temperature actualTemperature = temperature.div(2); + + // Then + Temperature exptectedTemperature = Temperature.ofCelsius(10); + assertThat(actualTemperature).isEqualTo(exptectedTemperature); + } + + @Test + @DisplayName("Divide by 0: should throw an exception if divided by 0") + void divide_shouldNotDivideByZeroThrowingException() { + // Given + Temperature temperature = Temperature.ofCelsius(20); + + // Then + assertThatThrownBy(() -> temperature.div(0)) + .isInstanceOf(UnitSystemArgumentException.class) + .hasMessage("Division by zero is not allowed. Please provide a non-zero divider value."); + } + + @Test + @DisplayName("Divide by quantity: should correctly divide quantities of the same type, but different units") + void divide_shouldDivideQuantityToSourceQuantity() { + // Given + Temperature sourceTemperature = Temperature.ofCelsius(20); + Pressure pressure = Pressure.ofPascal(2); + + // When + double actualDivideResult = sourceTemperature.div(pressure); + + // Then + double expectedDivideResult = 10; + assertThat(actualDivideResult).isEqualTo(expectedDivideResult); + } + + @Test + @DisplayName("Absolute value: should change wrapped value to absolute Math.abs(value)") + void abs_shouldChangeValueToAbsolute() { + // Given + Temperature temperature = Temperature.ofCelsius(-20); + + // When + Temperature acutalTemperature = temperature.abs(); + + // Then + assertThat(acutalTemperature.getValue()).isEqualTo(20); + } + + @Test + @DisplayName("Power to exponent: should correctly raise quantity to the power of the given exponent") + void power_shouldRaiseQuantityToExponent() { + // Given + Temperature temperature = Temperature.ofCelsius(2); + + // When + Temperature poweredTemperature = temperature.power(3); + + // Then + Temperature expectedTemperature = Temperature.ofCelsius(8); + assertThat(poweredTemperature).isEqualTo(expectedTemperature); + } + + // Square Root + + @Test + @DisplayName("Square root: should correctly calculate square root of the quantity's value") + void sqrt_shouldCalculateSquareRoot() { + // Given + Temperature temperature = Temperature.ofCelsius(25); + + // When + Temperature sqrtTemperature = temperature.sqrt(); + + // Then + Temperature expectedTemperature = Temperature.ofCelsius(5); + assertThat(sqrtTemperature).isEqualTo(expectedTemperature); + } + + // Logarithms + + @Test + @DisplayName("Natural logarithm: should correctly calculate natural logarithm of the quantity's value") + void log_shouldCalculateNaturalLogarithm() { + // Given + Temperature temperature = Temperature.ofCelsius(10); + + // When + Temperature logTemperature = temperature.log(); + + // Then + Temperature expectedTemperature = Temperature.ofCelsius(Math.log(10)); + assertThat(logTemperature).isEqualTo(expectedTemperature); + } + + @Test + @DisplayName("Natural logarithm of negative value: should throw exception when calculating natural logarithm of non-positive value") + void log_shouldThrowExceptionForNonPositiveValue() { + // Given + Temperature temperature = Temperature.ofCelsius(0); + + // Then + assertThatThrownBy(temperature::log) + .isInstanceOf(UnitSystemArgumentException.class) + .hasMessageContaining("Cannot calculate logarithm for non-positive value"); + } + + @Test + @DisplayName("Logarithm of 10 base: should correctly calculate base-10 logarithm of the quantity's value") + void log10_shouldCalculateBase10Logarithm() { + // Given + Temperature temperature = Temperature.ofCelsius(1000); + + // When + Temperature log10Temperature = temperature.log10(); + + // Then + Temperature expectedTemperature = Temperature.ofCelsius(3); + assertThat(log10Temperature).isEqualTo(expectedTemperature); + } + + @Test + @DisplayName("Logarithm of 10 base of negative value: should throw exception when calculating base-10 logarithm of non-positive value") + void log10_shouldThrowExceptionForNonPositiveValue() { + // Given + Temperature temperature = Temperature.ofCelsius(-10); + + // Then + assertThatThrownBy(temperature::log10) + .isInstanceOf(UnitSystemArgumentException.class); + + + } + + // Ceiling, and rounding up + + @Test + @DisplayName("Ceil: should correctly ceil up to the nearest integer") + void ceil_shouldCeilToNearestInteger() { + // Given + Temperature temperature1 = Temperature.ofCelsius(10.123456); + Temperature temperature2 = Temperature.ofCelsius(0.123456); + Temperature temperature3 = Temperature.ofCelsius(-10.123456); + + // When + Temperature ceilTemp1 = temperature1.ceil(); + Temperature ceilTemp2 = temperature2.ceil(); + Temperature ceilTemp3 = temperature3.ceil(); + + // Then + assertThat(ceilTemp1).isEqualTo(Temperature.ofCelsius(11)); + assertThat(ceilTemp2).isEqualTo(Temperature.ofCelsius(1)); + assertThat(ceilTemp3).isEqualTo(Temperature.ofCelsius(-10)); + } + + @Test + @DisplayName("Floor: should correctly floor down to the nearest integer") + void floor_shouldFloorToNearestInteger() { + // Given + Temperature temperature1 = Temperature.ofCelsius(10.123456); + Temperature temperature2 = Temperature.ofCelsius(0.123456); + Temperature temperature3 = Temperature.ofCelsius(-10.123456); + + // When + Temperature floorTemp1 = temperature1.floor(); + Temperature floorTemp2 = temperature2.floor(); + Temperature floorTemp3 = temperature3.floor(); + + // Then + assertThat(floorTemp1).isEqualTo(Temperature.ofCelsius(10)); + assertThat(floorTemp2).isEqualTo(Temperature.ofCelsius(0)); + assertThat(floorTemp3).isEqualTo(Temperature.ofCelsius(-11)); + } + + @Test + @DisplayName("Round half even: should correctly round up to half even for specified relevant digits") + void roundHalfEven_shouldFloorToNearestInteger() { + // Given + Temperature temperature1 = Temperature.ofCelsius(10.123456); + Temperature temperature2 = Temperature.ofCelsius(0.153456); + Temperature temperature3 = Temperature.ofCelsius(-10.123456); + + // When + Temperature roundTemp1 = temperature1.roundHalfEven(0); + Temperature roundTemp2 = temperature2.roundHalfEven(1); + Temperature roundTemp3 = temperature3.roundHalfEven(2); + + // Then + assertThat(roundTemp1).isEqualTo(Temperature.ofCelsius(10.0)); + assertThat(roundTemp2).isEqualTo(Temperature.ofCelsius(0.2)); + assertThat(roundTemp3).isEqualTo(Temperature.ofCelsius(-10.12)); + } + +} \ No newline at end of file diff --git a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/PhysicalQuantityTest.java b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/PhysicalQuantityTest.java index 4cfdfca..cac09e8 100644 --- a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/PhysicalQuantityTest.java +++ b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/PhysicalQuantityTest.java @@ -3,7 +3,6 @@ import com.synerset.unitility.unitsystem.common.Distance; import com.synerset.unitility.unitsystem.common.DistanceUnits; import com.synerset.unitility.unitsystem.dimensionless.BypassFactor; -import com.synerset.unitility.unitsystem.exceptions.UnitSystemArgumentException; import com.synerset.unitility.unitsystem.flow.VolumetricFlow; import com.synerset.unitility.unitsystem.flow.VolumetricFlowUnit; import com.synerset.unitility.unitsystem.thermodynamic.Pressure; @@ -15,7 +14,6 @@ import java.util.Arrays; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; class PhysicalQuantityTest { @@ -105,165 +103,6 @@ void isCloseToZero_shouldEvaluateIfCloseToZero(){ assertThat(closeToZeroFalse).isFalse(); } - // Transformations - - @Test - @DisplayName("should correctly add value to quantity") - void add_shouldAddValueToQuantity() { - // Given - Temperature temperature = Temperature.ofCelsius(20); - - // When - Temperature actualTemperature = temperature.plus(20); - - // Then - Temperature exptectedTemperature = Temperature.ofCelsius(40); - assertThat(actualTemperature).isEqualTo(exptectedTemperature); - } - - @Test - @DisplayName("should correctly subtract value from quantity") - void add_shouldSubtractValueToQuantity() { - // Given - Temperature temperature = Temperature.ofCelsius(20); - - // When - Temperature actualTemperature = temperature.minus(20); - - // Then - Temperature exptectedTemperature = Temperature.ofCelsius(0); - assertThat(actualTemperature).isEqualTo(exptectedTemperature); - } - - @Test - @DisplayName("should correctly add quantities of the same type, but different units") - void subtract_shouldAddQuantityToSourceQuantity() { - // Given - Temperature sourceTemperature = Temperature.ofCelsius(20); - Temperature temperatureToAdd = Temperature.ofKelvins(20 + 273.15); - - // When - Temperature actualTemperature = sourceTemperature.plus(temperatureToAdd); - - // Then - Temperature exptectedTemperature = Temperature.ofCelsius(40); - assertThat(actualTemperature).isEqualTo(exptectedTemperature); - } - - @Test - @DisplayName("should correctly subtract quantities of the same type, but different units") - void subtract_shouldSubtractQuantityToSourceQuantity() { - // Given - Temperature sourceTemperature = Temperature.ofCelsius(20); - Temperature temperatureToAdd = Temperature.ofKelvins(20 + 273.15); - - // When - Temperature actualTemperature = sourceTemperature.minus(temperatureToAdd); - - // Then - Temperature exptectedTemperature = Temperature.ofCelsius(0); - assertThat(actualTemperature).isEqualTo(exptectedTemperature); - } - - @Test - @DisplayName("should correctly multiply quantity by value") - void multiply_shouldMultiplyValueToQuantity() { - // Given - Temperature temperature = Temperature.ofCelsius(20); - - // When - Temperature actualTemperature = temperature.multiply(2); - Temperature actualTemperatureTimes = temperature.times(2); - - // Then - Temperature exptectedTemperature = Temperature.ofCelsius(40); - assertThat(actualTemperature).isEqualTo(exptectedTemperature).isEqualTo(actualTemperatureTimes); - } - - @Test - @DisplayName("should correctly multiply quantities of the same type, but different units") - void multiply_shouldMultiplyQuantityToSourceQuantity() { - // Given - Temperature sourceTemperature = Temperature.ofCelsius(20); - Pressure pressure = Pressure.ofPascal(2); - - // When - double actualMultiplyResult = sourceTemperature.multiply(pressure); - double actualMultiplyResultTimes = sourceTemperature.times(pressure); - - // Then - double expectedMultiplyResult = 40; - assertThat(actualMultiplyResult).isEqualTo(expectedMultiplyResult).isEqualTo(actualMultiplyResultTimes); - } - - @Test - @DisplayName("should correctly divide quantity by value") - void divide_shouldDivideValueToQuantity() { - // Given - Temperature temperature = Temperature.ofCelsius(20); - - // When - Temperature actualTemperature = temperature.div(2); - - // Then - Temperature exptectedTemperature = Temperature.ofCelsius(10); - assertThat(actualTemperature).isEqualTo(exptectedTemperature); - } - - @Test - @DisplayName("should throw an exception if divided by 0") - void divide_shouldNotDivideByZeroThrowingException() { - // Given - Temperature temperature = Temperature.ofCelsius(20); - - // Then - assertThatThrownBy(() -> temperature.div(0)) - .isInstanceOf(UnitSystemArgumentException.class) - .hasMessage("Division by zero is not allowed. Please provide a non-zero divider value."); - } - - @Test - @DisplayName("should correctly divide quantities of the same type, but different units") - void divide_shouldDivideQuantityToSourceQuantity() { - // Given - Temperature sourceTemperature = Temperature.ofCelsius(20); - Pressure pressure = Pressure.ofPascal(2); - - // When - double actualDivideResult = sourceTemperature.div(pressure); - - // Then - double expectedDivideResult = 10; - assertThat(actualDivideResult).isEqualTo(expectedDivideResult); - } - - @Test - @DisplayName("should subtract value of current unit from number") - void subtract_shouldSubtractFromValue() { - // Given - Temperature sourceTemperature = Temperature.ofCelsius(0.5); - - // When - Temperature actualTemperature = sourceTemperature.minusFromValue(1); - - // Then - Temperature expectedTemperature = Temperature.ofCelsius((1 - 0.5)); - assertThat(actualTemperature).isEqualTo(expectedTemperature); - } - - @Test - @DisplayName("should change wrapped value to absolute abs(value)") - void abs_shouldChangeValueToAbsolute(){ - // Given - Temperature temperature = Temperature.ofCelsius(-20); - - // When - Temperature acutalTemperature = temperature.abs(); - - // Then - assertThat(acutalTemperature.getValue()).isEqualTo(20); - } - // Others @Test diff --git a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/TrigonometricQuantityTest.java b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/TrigonometricQuantityTest.java new file mode 100644 index 0000000..81f8871 --- /dev/null +++ b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/TrigonometricQuantityTest.java @@ -0,0 +1,177 @@ +package com.synerset.unitility.unitsystem; + +import com.synerset.unitility.unitsystem.common.Angle; +import com.synerset.unitility.unitsystem.exceptions.UnitSystemArgumentException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class TrigonometricQuantityTest { + + @Test + @DisplayName("Sine: should correctly calculate sine of the quantity's value in radians") + void sin_shouldCalculateSine() { + // Given + Angle angle = Angle.ofRadians(Math.PI / 2); + + // When + Angle sinAngle = angle.sin(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.sin(Math.PI / 2)); + assertThat(sinAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Cosine: should correctly calculate cosine of the quantity's value in radians") + void cos_shouldCalculateCosine() { + // Given + Angle angle = Angle.ofRadians(0); + + // When + Angle cosAngle = angle.cos(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.cos(0)); + assertThat(cosAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Tangent: should correctly calculate tangent of the quantity's value in radians") + void tan_shouldCalculateTangent() { + // Given + Angle angle = Angle.ofRadians(Math.PI / 4); + + // When + Angle tanAngle = angle.tan(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.tan(Math.PI / 4)); + assertThat(tanAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Arcsine: should correctly calculate arcsine of the quantity's value") + void asin_shouldCalculateArcsine() { + // Given + Angle angle = Angle.ofRadians(Math.sin(Math.PI / 6)); + + // When + Angle asinAngle = angle.asin(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.asin(Math.sin(Math.PI / 6))); + assertThat(asinAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Arcsine out of range: should throw exception when calculating arcsine for out of range value") + void asin_shouldThrowExceptionForOutOfRangeValue() { + // Given + Angle angle = Angle.ofRadians(2); // sin(2) > 1, which is out of range for asin + // Then + assertThatThrownBy(angle::asin) + .isInstanceOf(UnitSystemArgumentException.class) + .hasMessageContaining("Value out of range for arcsine"); + } + + @Test + @DisplayName("Hyperbolic sine: should correctly calculate hyperbolic sine of the quantity's value") + void sinh_shouldCalculateHyperbolicSine() { + // Given + Angle angle = Angle.ofRadians(1); + + // When + Angle sinhAngle = angle.sinh(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.sinh(1)); + assertThat(sinhAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Hyperbolic cosine: should correctly calculate hyperbolic cosine of the quantity's value") + void cosh_shouldCalculateHyperbolicCosine() { + // Given + Angle angle = Angle.ofRadians(1); + + // When + Angle coshAngle = angle.cosh(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.cosh(1)); + assertThat(coshAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Cotangent: should correctly calculate cotangent of the quantity's value") + void cot_shouldCalculateCotangent() { + // Given + Angle angle = Angle.ofRadians(Math.PI / 4); + + // When + Angle ctgAngle = angle.cot(); + + // Then + Angle expectedAngle = Angle.ofRadians(1 / Math.tan(Math.PI / 4)); + assertThat(ctgAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Hyperbolic cotangent: should correctly calculate hyperbolic cotangent of the quantity's value") + void coth_shouldCalculateHyperbolicCotangent() { + // Given + Angle angle = Angle.ofRadians(1); + + // When + Angle ctghAngle = angle.coth(); + + // Then + Angle expectedAngle = Angle.ofRadians(1 / Math.tanh(1)); + assertThat(ctghAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Arccosine: should correctly calculate arccosine of the quantity's value") + void acos_shouldCalculateArccosine() { + // Given + Angle angle = Angle.ofRadians(Math.cos(Math.PI / 3)); + + // When + Angle acosAngle = angle.acos(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.acos(Math.cos(Math.PI / 3))); + assertThat(acosAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Arctangent: should correctly calculate arctangent of the quantity's value") + void atan_shouldCalculateArctangent() { + // Given + Angle angle = Angle.ofRadians(Math.tan(Math.PI / 4)); + + // When + Angle atanAngle = angle.atan(); + + // Then + Angle expectedAngle = Angle.ofRadians(Math.atan(Math.tan(Math.PI / 4))); + assertThat(atanAngle).isEqualTo(expectedAngle); + } + + @Test + @DisplayName("Arccotangent: should correctly calculate arccotangent of the quantity's value") + void acot_shouldCalculateArccotangent() { + // Given + Angle angle = Angle.ofRadians(Math.tan(Math.PI / 4)); + + // When + Angle actgAngle = angle.acot(); + + // Then + Angle expectedAngle = Angle.ofRadians(1 / Math.atan(Math.tan(Math.PI / 4))); + assertThat(actgAngle).isEqualTo(expectedAngle); + } +} diff --git a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunit/CustomAngle.java b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunit/CustomAngle.java index 8f8b7d1..f032fe6 100644 --- a/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunit/CustomAngle.java +++ b/unitility-core/src/test/java/com/synerset/unitility/unitsystem/customunit/CustomAngle.java @@ -1,12 +1,12 @@ package com.synerset.unitility.unitsystem.customunit; -import com.synerset.unitility.unitsystem.CalculableQuantity; +import com.synerset.unitility.unitsystem.TrigonometricQuantity; import com.synerset.unitility.unitsystem.common.AngleUnit; import com.synerset.unitility.unitsystem.common.AngleUnits; import java.util.Objects; -public class CustomAngle implements CalculableQuantity { +public class CustomAngle implements TrigonometricQuantity { private final double value; private final double baseValue;