Rounding mismatch between ASP .net C# Decimal to Java Double

Dinesh Devkota :

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.7and 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.

  • number = 10.05; precision = .1; expected = 10.1;
  • number = 10.12; precision = .01; expected = 10.12;
  • number = 8.7250; precision = 0.05; expected = 8.75;
  • number = 10.999; precision = 2; expected = 10;
  • number = 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.

gunnerone :

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.

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=455137&siteId=1