There are multiple ways to approach this problem.
Here is one possible way using a List
and BigDecimal
to get you started:
public static BigDecimal findRate(List<BigDecimal> allowedRates, BigDecimal inputRate) {
return allowedRates.stream()
.min(Comparator.comparing(a -> inputRate.setScale(0, RoundingMode.HALF_UP)
.subtract(a).abs()))
.orElseThrow(IllegalArgumentException::new);
}
Here is a sample Junit 5 test. You can check to see whether your particular requirements are met.
List<BigDecimal> ALLOWED_RATES = List.of(new BigDecimal("7.0"),
new BigDecimal("9.0"),
new BigDecimal("21.0"));
@ParameterizedTest(name = "#{index}: Testing {0}")
@DisplayName("Find the appropriate tax rate")
@CsvSource({
"7.0, 7.0",
"9.0, 9.0",
"21.0, 21.0",
"10.0, 9.0",
"16.0, 21.0",
"22.0, 21.0",
"0, 7.0"
})
public void testFindTaxRate(BigDecimal input, BigDecimal expected) {
BigDecimal actual = MyTaxRateCalculator.findRate(ALLOWED_RATES, input);
Assertions.assertEquals(expected, actual);
}
In your production code, the method doesn't need to be static. It does make this unit test a little less verbose for this answer.
You may wish to sort, remove duplicates, or perform error-checking as needed based on your requirements on your allowed tax rate list. This method will also work fine if you use change the implementation from a List
to a Set
that maintains order if that works better for you. Using a Set
is really nice, but it tends to require changes elsewhere in the system, and sometimes many IMO.
Finally, be careful which type you use for this.
UPDATE:
If you are looking to always choose the higher bracket given a choice between the two nearest values in your allowed list, then your code could become something like:
public static BigDecimal findRate(List<BigDecimal> allowedRates, BigDecimal inputRate) {
BigDecimal lastBracket = allowedRates.stream().reduce((a, b) -> b).orElse(null);
if (inputRate.compareTo(lastBracket) >= 0) {
return lastBracket;
}
return allowedRates.stream()
.filter(rate -> rate.compareTo(inputRate) >= 0)
.findFirst()
.orElseThrow(IllegalArgumentException::new);
}
And your test could be:
List<BigDecimal> ALLOWED_RATES = List.of(new BigDecimal("7.0"),
new BigDecimal("9.0"),
new BigDecimal("21.0"));
@ParameterizedTest(name = "#{index}: testing {0}")
@DisplayName("Find the appropriate tax rate")
@CsvSource({
"7.0, 7.0",
"9.0, 9.0",
"21.0, 21.0",
"10.0, 21.0",
"16.0, 21.0",
"22.0, 21.0",
"0, 7.0"
})
public void testFindRate(BigDecimal input, BigDecimal expected) {
BigDecimal actual = MyTaxRateCalculator.findRate(ALLOWED_RATES, input);
Assertions.assertEquals(expected, actual);
}