diff --git a/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java new file mode 100644 index 000000000000..ddb0e417ef0b --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java @@ -0,0 +1,66 @@ +package com.thealgorithms.maths; + +/** + * Return on Investment (ROI) calculations for evaluating investment profitability. + * + *
This class provides two related computations: + *
While simple ROI tells you the total gain over an entire holding period, + * annualized ROI normalizes that gain to a yearly rate so that investments + * held for different lengths of time can be compared on equal footing. + * It applies the geometric-mean formula: + * + *
+ * Annualized ROI = ((1 + simpleROI / 100) ^ (1 / years) - 1) × 100 + *+ * + * @param gainFromInvestment the total value received from the investment + * @param costOfInvestment the total cost of the investment (must be positive) + * @param years the number of years the investment was held (must be positive) + * @return annualized ROI as a percentage + * @throws IllegalArgumentException if {@code costOfInvestment} or {@code years} is not positive + */ + public static double annualizedReturnOnInvestment(final double gainFromInvestment, final double costOfInvestment, final double years) { + if (costOfInvestment <= 0) { + throw new IllegalArgumentException("costOfInvestment must be greater than 0"); + } + if (years <= 0) { + throw new IllegalArgumentException("years must be greater than 0"); + } + final double simpleRoi = returnOnInvestment(gainFromInvestment, costOfInvestment); + return (Math.pow(1.0 + simpleRoi / 100.0, 1.0 / years) - 1.0) * 100.0; + } +} diff --git a/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java b/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java new file mode 100644 index 000000000000..df47f27e9d24 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java @@ -0,0 +1,93 @@ +package com.thealgorithms.maths; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; + +public class ReturnOnInvestmentTest { + + private static final double DELTA = 1e-9; + + // --- Simple ROI --- + + @Test + void testPositiveROI() { + assertEquals(100.0, ReturnOnInvestment.returnOnInvestment(1000, 500)); + } + + @Test + void testZeroROI() { + assertEquals(0.0, ReturnOnInvestment.returnOnInvestment(500, 500)); + } + + @Test + void testNegativeROI() { + assertEquals(-60.0, ReturnOnInvestment.returnOnInvestment(200, 500)); + } + + @Test + void testTotalLoss() { + assertEquals(-100.0, ReturnOnInvestment.returnOnInvestment(0, 500)); + } + + @Test + void testZeroCostThrows() { + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.returnOnInvestment(1000, 0)); + } + + @Test + void testNegativeCostThrows() { + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.returnOnInvestment(1000, -100)); + } + + // --- Annualized ROI --- + + @Test + void testAnnualizedROIOneYear() { + // Over exactly 1 year, annualized ROI == simple ROI + assertEquals(100.0, ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 1), DELTA); + } + + @Test + void testAnnualizedROITwoYears() { + // Simple ROI = 100% over 2 years → annualized = (sqrt(2) - 1) * 100 ≈ 41.42% + double expected = (Math.pow(2.0, 0.5) - 1.0) * 100.0; + assertEquals(expected, ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 2), DELTA); + } + + @Test + void testAnnualizedROIFractionalYear() { + // 6 months (0.5 years): annualizes to a higher rate than the simple ROI + double expected = (Math.pow(2.0, 2.0) - 1.0) * 100.0; // (1+1)^2 - 1 = 300% + assertEquals(expected, ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 0.5), DELTA); + } + + @Test + void testAnnualizedZeroROI() { + // If gain == cost, ROI is 0 regardless of holding period + assertEquals(0.0, ReturnOnInvestment.annualizedReturnOnInvestment(500, 500, 5), DELTA); + } + + @Test + void testAnnualizedNegativeROI() { + // Loss of 50% over 2 years: annualized = (sqrt(0.5) - 1) * 100 ≈ -29.29% + double expected = (Math.pow(0.5, 0.5) - 1.0) * 100.0; + assertEquals(expected, ReturnOnInvestment.annualizedReturnOnInvestment(500, 1000, 2), DELTA); + } + + @Test + void testAnnualizedZeroYearsThrows() { + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 0)); + } + + @Test + void testAnnualizedNegativeYearsThrows() { + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, -3)); + } + + @Test + void testAnnualizedZeroCostThrows() { + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.annualizedReturnOnInvestment(1000, 0, 2)); + } +}