Skip to content

Commit 2bced69

Browse files
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.
1 parent ff3feff commit 2bced69

2 files changed

Lines changed: 101 additions & 10 deletions

File tree

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,66 @@
11
package com.thealgorithms.maths;
22

33
/**
4-
* Return on Investment (ROI) measures the profitability of an investment
5-
* relative to its cost, expressed as a percentage.
4+
* Return on Investment (ROI) calculations for evaluating investment profitability.
65
*
7-
* <p>Formula: ROI = (Gain - Cost) / Cost × 100
6+
* <p>This class provides two related computations:
7+
* <ul>
8+
* <li><b>Simple ROI</b> – measures total gain relative to cost:
9+
* {@code ROI = (Gain - Cost) / Cost × 100}</li>
10+
* <li><b>Annualized ROI</b> – converts a total ROI over multiple years into
11+
* an equivalent annual rate using the geometric mean:
12+
* {@code Annualized ROI = ((1 + ROI/100)^(1/n) - 1) × 100}</li>
13+
* </ul>
814
*
9-
* @see <a href="https://www.investopedia.com/terms/r/returnoninvestment.asp">Investopedia</a>
15+
* @see <a href="https://www.investopedia.com/terms/r/returnoninvestment.asp">Investopedia – ROI</a>
16+
* @see <a href="https://www.investopedia.com/terms/a/annualized-total-return.asp">Investopedia – Annualized Return</a>
1017
*/
1118
public final class ReturnOnInvestment {
19+
1220
private ReturnOnInvestment() {
1321
}
1422

1523
/**
16-
* Calculates the return on investment as a percentage.
24+
* Calculates the simple return on investment as a percentage.
1725
*
18-
* @param gainFromInvestment the total value gained from the investment
19-
* @param costOfInvestment the total cost of the investment
20-
* @return ROI as a percentage
21-
* @throws IllegalArgumentException if costOfInvestment is not positive
26+
* @param gainFromInvestment the total value received from the investment
27+
* @param costOfInvestment the total cost of the investment (must be positive)
28+
* @return ROI as a percentage; negative when a loss occurred
29+
* @throws IllegalArgumentException if {@code costOfInvestment} is not positive
2230
*/
23-
public static double returnOnInvestment(double gainFromInvestment, double costOfInvestment) {
31+
public static double returnOnInvestment(final double gainFromInvestment, final double costOfInvestment) {
2432
if (costOfInvestment <= 0) {
2533
throw new IllegalArgumentException("costOfInvestment must be greater than 0");
2634
}
2735
return (gainFromInvestment - costOfInvestment) / costOfInvestment * 100.0;
2836
}
37+
38+
/**
39+
* Calculates the annualized (per-year) return on investment.
40+
*
41+
* <p>While simple ROI tells you the total gain over an entire holding period,
42+
* annualized ROI normalizes that gain to a yearly rate so that investments
43+
* held for different lengths of time can be compared on equal footing.
44+
* It applies the geometric-mean formula:
45+
*
46+
* <pre>
47+
* Annualized ROI = ((1 + simpleROI / 100) ^ (1 / years) - 1) × 100
48+
* </pre>
49+
*
50+
* @param gainFromInvestment the total value received from the investment
51+
* @param costOfInvestment the total cost of the investment (must be positive)
52+
* @param years the number of years the investment was held (must be positive)
53+
* @return annualized ROI as a percentage
54+
* @throws IllegalArgumentException if {@code costOfInvestment} or {@code years} is not positive
55+
*/
56+
public static double annualizedReturnOnInvestment(final double gainFromInvestment, final double costOfInvestment, final double years) {
57+
if (costOfInvestment <= 0) {
58+
throw new IllegalArgumentException("costOfInvestment must be greater than 0");
59+
}
60+
if (years <= 0) {
61+
throw new IllegalArgumentException("years must be greater than 0");
62+
}
63+
final double simpleRoi = returnOnInvestment(gainFromInvestment, costOfInvestment);
64+
return (Math.pow(1.0 + simpleRoi / 100.0, 1.0 / years) - 1.0) * 100.0;
65+
}
2966
}

src/test/java/com/thealgorithms/maths/ReturnOnInvestmentTest.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77

88
public class ReturnOnInvestmentTest {
99

10+
private static final double DELTA = 1e-9;
11+
12+
// --- Simple ROI ---
13+
1014
@Test
1115
void testPositiveROI() {
1216
assertEquals(100.0, ReturnOnInvestment.returnOnInvestment(1000, 500));
@@ -36,4 +40,54 @@ void testZeroCostThrows() {
3640
void testNegativeCostThrows() {
3741
assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.returnOnInvestment(1000, -100));
3842
}
43+
44+
// --- Annualized ROI ---
45+
46+
@Test
47+
void testAnnualizedROIOneYear() {
48+
// Over exactly 1 year, annualized ROI == simple ROI
49+
assertEquals(100.0, ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 1), DELTA);
50+
}
51+
52+
@Test
53+
void testAnnualizedROITwoYears() {
54+
// Simple ROI = 100% over 2 years → annualized = (sqrt(2) - 1) * 100 ≈ 41.42%
55+
double expected = (Math.pow(2.0, 0.5) - 1.0) * 100.0;
56+
assertEquals(expected, ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 2), DELTA);
57+
}
58+
59+
@Test
60+
void testAnnualizedROIFractionalYear() {
61+
// 6 months (0.5 years): annualizes to a higher rate than the simple ROI
62+
double expected = (Math.pow(2.0, 2.0) - 1.0) * 100.0; // (1+1)^2 - 1 = 300%
63+
assertEquals(expected, ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 0.5), DELTA);
64+
}
65+
66+
@Test
67+
void testAnnualizedZeroROI() {
68+
// If gain == cost, ROI is 0 regardless of holding period
69+
assertEquals(0.0, ReturnOnInvestment.annualizedReturnOnInvestment(500, 500, 5), DELTA);
70+
}
71+
72+
@Test
73+
void testAnnualizedNegativeROI() {
74+
// Loss of 50% over 2 years: annualized = (sqrt(0.5) - 1) * 100 ≈ -29.29%
75+
double expected = (Math.pow(0.5, 0.5) - 1.0) * 100.0;
76+
assertEquals(expected, ReturnOnInvestment.annualizedReturnOnInvestment(500, 1000, 2), DELTA);
77+
}
78+
79+
@Test
80+
void testAnnualizedZeroYearsThrows() {
81+
assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, 0));
82+
}
83+
84+
@Test
85+
void testAnnualizedNegativeYearsThrows() {
86+
assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.annualizedReturnOnInvestment(1000, 500, -3));
87+
}
88+
89+
@Test
90+
void testAnnualizedZeroCostThrows() {
91+
assertThrows(IllegalArgumentException.class, () -> ReturnOnInvestment.annualizedReturnOnInvestment(1000, 0, 2));
92+
}
3993
}

0 commit comments

Comments
 (0)