I am translating .NET code to Java and ran into precision not matching issue.
.NET code:
private decimal roundToPrecision(decimal number, decimal roundPrecision)
{
if (roundPrecision == 0)
return number;
decimal numberDecimalMultiplier = Math.Round(number / roundPrecision, MidpointRounding.AwayFromZero);
return numberDecimalMultiplier * roundPrecision;
}
Calling roundToPrecision(8.7250, 0.05);
function in the code above gives me 8.75
which is expected.
The conversion/translation of the function to Java is as follows. I din't find exact
Math.Round
option.
Java code:
public double roundToPrecision(double number, double roundPrecision) {
if (roundPrecision == 0)
return number;
int len = Double.toString(roundPrecision).split("\\.")[1].length();
double divisor = 0d;
switch (len) {
case 1:
divisor = 10d;
break;
case 2:
divisor = 100d;
break;
case 3:
divisor = 1000d;
break;
case 4:
divisor = 10000d;
break;
}
double numberDecimalMultiplier = Math.round(number / roundPrecision);
double res = numberDecimalMultiplier * roundPrecision;
return Math.round(res * divisor) / divisor;
}
Calling roundToPrecision(8.7250, 0.05);
in the Java code gives me 8.7
and this is not correct.
I even tried modifying code with BigDecimal
as follows in Java using the reference here C# Double Rounding but have no luck.
public double roundToPrecision(double number, double roundPrecision) {
if (roundPrecision == 0)
return number;
int len = Double.toString(roundPrecision).split("\\.")[1].length();
double divisor = 0d;
switch (len) {
case 1:
divisor = 10d;
break;
case 2:
divisor = 100d;
break;
case 3:
divisor = 1000d;
break;
case 4:
divisor = 10000d;
break;
}
BigDecimal b = new BigDecimal(number / roundPrecision);
b = b.setScale(len,BigDecimal.ROUND_UP);
double numberDecimalMultiplier = Math.round(b.doubleValue());
double res = numberDecimalMultiplier * roundPrecision;
return Math.round(res * divisor) / divisor;
}
Please guide me for what I need to do to fix this.
Here are couple of scenarios to try out.
10.05
; precision = .1
; expected = 10.1
;10.12
; precision = .01
; expected = 10.12
;8.7250
; precision = 0.05
; expected = 8.75
;10.999
; precision = 2
; expected = 10
;6.174999999999999
; precision = 0.05
; expected = 6.20
;Note: I have over 60 thousand numbers and precision can vary from 1 decimal to 4 decimal places. The output of .NET should match exactly to Java.
Round(Decimal, Int32) This method rounds a decimal value to a specified number of fractional digits.
Round() Method | Set – 2. In C#, Math. Round() is a Math class method which is used to round a value to the nearest integer or to the particular number of fractional digits. This method has another overload with which, you can specify the number of digits beyond the decimal point in the returned value.
5 to be rounded is rounded either up or down so that the result of the rounding is always an even number. Thus 2.5 rounds to 2.0, 3.5 to 4.0, 4.5 to 4.0, 5.5 to 6.0, and so on.
The problem comes from how doubles vs decimals are stored and represented in memory. See these links for more specifics: Doubles Decimals
Let's take a look at how they each work in your code. Using doubles, with arguments of 8.725 and 0.05. number / roundPrecision
gives 174.499...
, since doubles aren't able to exactly represent 174.5. With decimals number / roundPrecision
gives 174.5
, decimals are able to represent this exactly. So then when 174.499...
gets rounded, it gets rounded down to 174
instead of 175
.
Using BigDecimal
is a step in the right direction. There is an issue with how it's being used in your code however. The problem comes when you're creating the BigDecimal value.
BigDecimal b = new BigDecimal(number / roundPrecision);
The BigDecimal
is being created from a double, so the imprecision is already there. If you're able to create the BigDecimal
arguments from a string that would be much better.
public static BigDecimal roundToPrecision(BigDecimal number, BigDecimal roundPrecision) {
if (roundPrecision.signum() == 0)
return number;
BigDecimal numberDecimalMultiplier = number.divide(roundPrecision, RoundingMode.HALF_DOWN).setScale(0, RoundingMode.HALF_UP);
return numberDecimalMultiplier.multiply(roundPrecision);
}
BigDecimal n = new BigDecimal("-8.7250");
BigDecimal p = new BigDecimal("0.05");
BigDecimal r = roundToPrecision(n, p);
If the function must take in and return doubles:
public static double roundToPrecision(double number, double roundPrecision)
{
BigDecimal numberBig = new BigDecimal(number).
setScale(10, BigDecimal.ROUND_HALF_UP);
BigDecimal roundPrecisionBig = BigDecimal.valueOf(roundPrecision);
if (roundPrecisionBig.signum() == 0)
return number;
BigDecimal numberDecimalMultiplier = numberBig.divide(roundPrecisionBig, RoundingMode.HALF_DOWN).setScale(0, RoundingMode.HALF_UP);
return numberDecimalMultiplier.multiply(roundPrecisionBig).doubleValue();
}
Keep in mind that doubles cannot exactly represent the same values which decimals can. So the function returning a double cannot have the exact output as the original C# function which returns decimals.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With