From 3283b0b8a2e1bddec17a5e350a467c07484d8bd1 Mon Sep 17 00:00:00 2001 From: Inuka Wijerathna Date: Fri, 5 Jun 2026 10:42:42 +0530 Subject: [PATCH 1/4] Add ReturnOnInvestment to maths --- .../maths/ReturnOnInvestment.java | 30 ++++++++++++++ .../maths/ReturnOnInvestmentTest.java | 40 +++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java create mode 100644 src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java 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..0d2462b79064 --- /dev/null +++ b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java @@ -0,0 +1,30 @@ +package com.thealgorithms.maths; + +/** + * Return on Investment (ROI) measures the profitability of an investment + * relative to its cost, expressed as a percentage. + * + *

Formula: ROI = (Gain - Cost) / Cost × 100 + * + * @see Investopedia + */ +public final class ReturnOnInvestment { + + private ReturnOnInvestment() { + } + + /** + * Calculates the return on investment as a percentage. + * + * @param gainFromInvestment the total value gained from the investment + * @param costOfInvestment the total cost of the investment + * @return ROI as a percentage + * @throws IllegalArgumentException if costOfInvestment is not positive + */ + public static double returnOnInvestment(double gainFromInvestment, double costOfInvestment) { + if (costOfInvestment <= 0) { + throw new IllegalArgumentException("costOfInvestment must be greater than 0"); + } + return (gainFromInvestment - costOfInvestment) / costOfInvestment * 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..e655e5afdcd8 --- /dev/null +++ b/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java @@ -0,0 +1,40 @@ +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 { + + @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)); + } +} From d9f76d0ac4f6be23a49668f14a19daabd3246804 Mon Sep 17 00:00:00 2001 From: Inuka Wijerathna Date: Fri, 5 Jun 2026 15:47:19 +0530 Subject: [PATCH 2/4] Fix clang-format: remove blank line after class brace, fix param alignment --- src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java index 0d2462b79064..a7e5f9e1cb59 100644 --- a/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java +++ b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java @@ -9,7 +9,6 @@ * @see Investopedia */ public final class ReturnOnInvestment { - private ReturnOnInvestment() { } @@ -17,7 +16,7 @@ private ReturnOnInvestment() { * Calculates the return on investment as a percentage. * * @param gainFromInvestment the total value gained from the investment - * @param costOfInvestment the total cost of the investment + * @param costOfInvestment the total cost of the investment * @return ROI as a percentage * @throws IllegalArgumentException if costOfInvestment is not positive */ From ff3feff6a049a9dca21af6b8799f749ba2ec2c39 Mon Sep 17 00:00:00 2001 From: Inuka Wijerathna Date: Fri, 5 Jun 2026 15:53:52 +0530 Subject: [PATCH 3/4] Fix clang-format: blank line between imports, single-line assertThrows --- .../com/thealgorithms/maths/ReturnOnInvestmentTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java b/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java index e655e5afdcd8..ce8ca66d78e1 100644 --- a/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java +++ b/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java @@ -2,6 +2,7 @@ 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 { @@ -28,13 +29,11 @@ void testTotalLoss() { @Test void testZeroCostThrows() { - assertThrows(IllegalArgumentException.class, - () -> ReturnOnInvestment.returnOnInvestment(1000, 0)); + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.returnOnInvestment(1000, 0)); } @Test void testNegativeCostThrows() { - assertThrows(IllegalArgumentException.class, - () -> ReturnOnInvestment.returnOnInvestment(1000, -100)); + assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.returnOnInvestment(1000, -100)); } } From 7bee01cc37625af60a7217a695ffc998841f6c8b Mon Sep 17 00:00:00 2001 From: Inuka Wijerathna Date: Fri, 5 Jun 2026 22:17:05 +0530 Subject: [PATCH 4/4] feat(maths): add annualizedReturnOnInvestment method Extends the ROI class with annualizedReturnOnInvestment(), which applies the geometric-mean formula to convert a total ROI over n years into an equivalent annual rate. Adds 8 new tests covering one-year identity, multi-year, fractional-year, break-even, negative ROI, and invalid inputs. Co-Authored-By: Claude Sonnet 4.6 --- .../maths/ReturnOnInvestment.java | 57 +++++++++++++++---- .../maths/ReturnOnInvestmentTest.java | 54 ++++++++++++++++++ 2 files changed, 101 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java index a7e5f9e1cb59..ddb0e417ef0b 100644 --- a/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java +++ b/src/main/java/com/thealgorithms/maths/ReturnOnInvestment.java @@ -1,29 +1,66 @@ package com.thealgorithms.maths; /** - * Return on Investment (ROI) measures the profitability of an investment - * relative to its cost, expressed as a percentage. + * Return on Investment (ROI) calculations for evaluating investment profitability. * - *

Formula: ROI = (Gain - Cost) / Cost × 100 + *

This class provides two related computations: + *

* - * @see Investopedia + * @see Investopedia – ROI + * @see Investopedia – Annualized Return */ public final class ReturnOnInvestment { + private ReturnOnInvestment() { } /** - * Calculates the return on investment as a percentage. + * Calculates the simple return on investment as a percentage. * - * @param gainFromInvestment the total value gained from the investment - * @param costOfInvestment the total cost of the investment - * @return ROI as a percentage - * @throws IllegalArgumentException if costOfInvestment is not positive + * @param gainFromInvestment the total value received from the investment + * @param costOfInvestment the total cost of the investment (must be positive) + * @return ROI as a percentage; negative when a loss occurred + * @throws IllegalArgumentException if {@code costOfInvestment} is not positive */ - public static double returnOnInvestment(double gainFromInvestment, double costOfInvestment) { + public static double returnOnInvestment(final double gainFromInvestment, final double costOfInvestment) { if (costOfInvestment <= 0) { throw new IllegalArgumentException("costOfInvestment must be greater than 0"); } return (gainFromInvestment - costOfInvestment) / costOfInvestment * 100.0; } + + /** + * Calculates the annualized (per-year) return on investment. + * + *

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 index ce8ca66d78e1..df47f27e9d24 100644 --- a/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java +++ b/src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java @@ -7,6 +7,10 @@ public class ReturnOnInvestmentTest { + private static final double DELTA = 1e-9; + + // --- Simple ROI --- + @Test void testPositiveROI() { assertEquals(100.0, ReturnOnInvestment.returnOnInvestment(1000, 500)); @@ -36,4 +40,54 @@ void testZeroCostThrows() { 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)); + } }