Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 491
Dinesh Devkota Avatar asked Jan 13 '17 22:01

Dinesh Devkota


People also ask

How do you round off decimals in asp net?

Round(Decimal, Int32) This method rounds a decimal value to a specified number of fractional digits.

How do you round maths in C#?

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.

Why does math round round down?

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.


Video Answer


1 Answers

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.

like image 72
gunnerone Avatar answered Sep 26 '22 23:09

gunnerone