diff --git a/src/main/java/com/thealgorithms/conversions/AffineConverter.java b/src/main/java/com/thealgorithms/conversions/AffineConverter.java index a580b23f9..199a6dd51 100644 --- a/src/main/java/com/thealgorithms/conversions/AffineConverter.java +++ b/src/main/java/com/thealgorithms/conversions/AffineConverter.java @@ -1,23 +1,64 @@ package com.thealgorithms.conversions; +/** + * A utility class to perform affine transformations of the form: + * y = slope * x + intercept. + * + * This class supports inversion and composition of affine transformations. + * It is immutable, meaning each instance represents a fixed transformation. + */ public final class AffineConverter { private final double slope; private final double intercept; + + /** + * Constructs an AffineConverter with the given slope and intercept. + * + * @param inSlope The slope of the affine transformation. + * @param inIntercept The intercept (constant term) of the affine transformation. + * @throws IllegalArgumentException if either parameter is NaN. + */ public AffineConverter(final double inSlope, final double inIntercept) { + if (Double.isNaN(inSlope) || Double.isNaN(inIntercept)) { + throw new IllegalArgumentException("Slope and intercept must be valid numbers."); + } slope = inSlope; intercept = inIntercept; } + /** + * Converts the given input value using the affine transformation: + * result = slope * inValue + intercept. + * + * @param inValue The input value to convert. + * @return The transformed value. + */ public double convert(final double inValue) { return slope * inValue + intercept; } + /** + * Returns a new AffineConverter representing the inverse of the current transformation. + * The inverse of y = slope * x + intercept is x = (y - intercept) / slope. + * + * @return A new AffineConverter representing the inverse transformation. + * @throws AssertionError if the slope is zero, as the inverse would be undefined. + */ public AffineConverter invert() { - assert slope != 0.0; + assert slope != 0.0 : "Slope cannot be zero for inversion."; return new AffineConverter(1.0 / slope, -intercept / slope); } + /** + * Composes this affine transformation with another, returning a new AffineConverter. + * If this transformation is f(x) and the other is g(x), the result is f(g(x)). + * + * @param other Another AffineConverter to compose with. + * @return A new AffineConverter representing the composition of the two transformations. + */ public AffineConverter compose(final AffineConverter other) { - return new AffineConverter(slope * other.slope, slope * other.intercept + intercept); + double newSlope = slope * other.slope; + double newIntercept = slope * other.intercept + intercept; + return new AffineConverter(newSlope, newIntercept); } } diff --git a/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java b/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java index 2705955f6..47eea139f 100644 --- a/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java +++ b/src/test/java/com/thealgorithms/conversions/AffineConverterTest.java @@ -16,31 +16,39 @@ public class AffineConverterTest { } @Test - void testConstructor() { + void testConstructorWithValidValues() { assertEquals(3.0, converter.convert(0.0), "Expected value when input is 0.0"); assertEquals(5.0, converter.convert(1.0), "Expected value when input is 1.0"); - assertEquals(7.0, converter.convert(2.0), "Expected value when input is 2.0"); } @Test - void testConvert() { - assertEquals(3.0, converter.convert(0.0), "Conversion at 0.0 should equal the intercept"); - assertEquals(7.0, converter.convert(2.0), "2.0 should convert to 7.0"); - assertEquals(11.0, converter.convert(4.0), "4.0 should convert to 11.0"); + void testConstructorWithInvalidValues() { + assertThrows(IllegalArgumentException.class, () -> new AffineConverter(Double.NaN, 3.0), "Constructor should throw IllegalArgumentException for NaN slope"); + } + + @Test + void testConvertWithNegativeValues() { + assertEquals(-1.0, converter.convert(-2.0), "Negative input should convert correctly"); + assertEquals(-3.0, new AffineConverter(-1.0, -1.0).convert(2.0), "Slope and intercept can be negative"); + } + + @Test + void testConvertWithFloatingPointPrecision() { + double result = new AffineConverter(1.3333, 0.6667).convert(3.0); + assertEquals(4.6666, result, 1e-4, "Conversion should maintain floating-point precision"); } @Test void testInvert() { AffineConverter inverted = converter.invert(); - assertEquals(0.0, inverted.convert(3.0), "Inverted converter should return 0.0 for input 3.0"); - assertEquals(1.0, inverted.convert(5.0), "Inverted converter should return 1.0 for input 5.0"); - assertEquals(2.0, inverted.convert(7.0), "Inverted converter should return 2.0 for input 7.0"); + assertEquals(0.0, inverted.convert(3.0), "Inverted should return 0.0 for input 3.0"); + assertEquals(1.0, inverted.convert(5.0), "Inverted should return 1.0 for input 5.0"); } @Test void testInvertWithZeroSlope() { AffineConverter zeroSlopeConverter = new AffineConverter(0.0, 3.0); - assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw assertion error when slope is zero"); + assertThrows(AssertionError.class, zeroSlopeConverter::invert, "Invert should throw AssertionError when slope is zero"); } @Test @@ -50,6 +58,30 @@ public class AffineConverterTest { assertEquals(7.0, composed.convert(0.0), "Expected composed conversion at 0.0"); assertEquals(9.0, composed.convert(1.0), "Expected composed conversion at 1.0"); - assertEquals(11.0, composed.convert(2.0), "Expected composed conversion at 2.0"); + } + + @Test + void testMultipleCompositions() { + AffineConverter c1 = new AffineConverter(2.0, 1.0); + AffineConverter c2 = new AffineConverter(3.0, -2.0); + AffineConverter c3 = c1.compose(c2); // (2x + 1) ∘ (3x - 2) => 6x - 1 + + assertEquals(-3.0, c3.convert(0.0), "Composed transformation should return -3.0 at 0.0"); + assertEquals(3.0, c3.convert(1.0), "Composed transformation should return 3.0 at 1.0"); + } + + @Test + void testIdentityComposition() { + AffineConverter identity = new AffineConverter(1.0, 0.0); + AffineConverter composed = converter.compose(identity); + + assertEquals(3.0, composed.convert(0.0), "Identity composition should not change the transformation"); + assertEquals(7.0, composed.convert(2.0), "Identity composition should behave like the original"); + } + + @Test + void testLargeInputs() { + double largeValue = 1e6; + assertEquals(2.0 * largeValue + 3.0, converter.convert(largeValue), "Should handle large input values without overflow"); } }