I am unable to understand the behavior of BigDecimal Roundingmode HALF_EVEN when no of digits being dropped are greater than 2(non zero).
As per docs , with HALF_EVEN rounding, when left side digit to the dropped is even, it is HALF_DOWN case so left side digit is not incremented. So “1.3651” with scale 2 should result in “1.36” but result is “1.37”. Looking at BigDecimal code, It seems if number formed by digits being dropped is greater than 50, it adds an increment and thats why I am getting the result as “1.37”.
private static int longCompareMagnitude(long x, long y) {
if (x < 0)
x = -x;
if (y < 0)
y = -y;
return (x < y) ? -1 : ((x == y) ? 0 : 1);
}
Explaining X and Y in above code snippet
x : 2*(no formed by digits to be dropped).
e.g. 1.3753 and scale 2, here x would be 53*2 = 106
y : It is the bucket on length of digits dropped[1,10,100]. Y is 100 here
int drop = checkScale((long) oldScale - newScale);
// old scale is precision on current number(decimal places after . : 4)
y = LONG_TEN_POWERS_TABLE[drop]
So x==y case is when digit to be dropped is 5 or 50 etc.
otherwise if number formed by dropped digits are greater than 50, it will always add 1. so below cases seem to be working as per code. But documentation says it is HALF_DOWN case and hence should not increment[HALF_UP]
I have written following examples to verify this. can someone please explain the behavior of last 2 entries where result is marked false.
Java version: OpenLogic-OpenJDK (build 11.0.16+8), Corretto-17.0.12.7.1
@Test
fun bigDecimalTest() {
printResult("Round up ", "1.176", "1.18", 2)
//4 being dropped is less than 5, doesn't qualify for round up
printResult("Round down", "1.174", "1.17", 2)
println("Equidistant round off with HALF EVEN rounding")
printResult("HALF up case", "1.175", "1.18", 2)
printResult("HALF up case", "1.3753", "1.38", 2)
printResult("HALF down case", "1.365", "1.36", 2)
printResult("HALF down case with 2 digits dropped", "1.3650", "1.36", 2)
println("half down not happening when number formed by digits to be dropped is 5/50")
printResult("HALF down case with 2 digits dropped", "1.3651", "1.36", 2)
printResult("HALF down case with 2 digits dropped", "1.3653", "1.36", 2)
}
private fun printResult(message: String, input: String, expected: String, scale: Int) {
val actual = BigDecimal(input).setScale(scale, RoundingMode.HALF_EVEN)
val result = BigDecimal(expected) == actual
println("$message, scale: $scale: input: $input, expected :$expected, actual= $actual, Matched= $result")
}
Output :
Round up , scale: 2: input: 1.176, expected :1.18, actual= 1.18, Matched= true
Round down, scale: 2: input: 1.174, expected :1.17, actual= 1.17, Matched= true
Equidistant round off with HALF EVEN rounding
HALF up case, scale: 2: input: 1.175, expected :1.18, actual= 1.18, Matched= true
HALF up case, scale: 2: input: 1.3753, expected :1.38, actual= 1.38, Matched= true
HALF down case, scale: 2: input: 1.365, expected :1.36, actual= 1.36, Matched= true
HALF down case with 2 digits dropped, scale: 2: input: 1.3650, expected :1.36, actual= 1.36, Matched= true
half down not happening when number formed by digits to be dropped is 5/50
HALF down case with 2 digits dropped, scale: 2: input: 1.3651, expected :1.36, actual= 1.37, Matched= false
HALF down case with 2 digits dropped, scale: 2: input: 1.3653, expected :1.36, actual= 1.37, Matched= false